Add a text effect
Writes a line or several over its input in a chosen family, weight, size and colour, optionally on a box filled with a colour or with a picture from disk. Like the blur and the overlay it needs no sources of its own, so it can lead a chain; given sources it fills the canvas from a randomly chosen one first. Two param types rather than effect-specific branches in the front-ends: Text, free-form text the user types, and Color, a colour written "#rrggbb". Both travel as strings, so the interface gets an entry and a colour button, the command line gets a flag (a colour that does not parse is refused there rather than drawn as black), and a project keeps them across a round trip. The glyphs come from Pango, which draws through Cairo without a display, so the core stays headless — but they are the one thing here that is not byte-identical across machines: a machine without the chosen family renders the substitute its fontconfig picks. Everything the effect is handed, the rng included, is used exactly as every other effect uses it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
|
||||
@@ -315,6 +317,34 @@ void EffectorWindow::buildParamWidgets() {
|
||||
widget = std::move(box);
|
||||
break;
|
||||
}
|
||||
case ParamSpec::Type::Text: {
|
||||
auto entry = std::make_unique<Gtk::Entry>();
|
||||
entry->set_text(paramText(stage.values, spec.key, spec.defaultChoice));
|
||||
// Re-rendering on every keystroke would start a render per
|
||||
// letter typed, so the text is taken when the entry is left or
|
||||
// Enter is pressed.
|
||||
entry->signal_activate().connect(
|
||||
sigc::mem_fun(*this, &EffectorWindow::onParamChanged));
|
||||
auto focus = Gtk::EventControllerFocus::create();
|
||||
focus->signal_leave().connect(
|
||||
sigc::mem_fun(*this, &EffectorWindow::onParamChanged));
|
||||
entry->add_controller(focus);
|
||||
widget = std::move(entry);
|
||||
break;
|
||||
}
|
||||
case ParamSpec::Type::Color: {
|
||||
auto button = std::make_unique<Gtk::ColorDialogButton>(Gtk::ColorDialog::create());
|
||||
button->set_halign(Gtk::Align::START);
|
||||
std::uint8_t rgb[3];
|
||||
paramColor(stage.values, spec.key, spec.defaultChoice, rgb);
|
||||
Gdk::RGBA colour;
|
||||
colour.set_rgba(rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0, 1.0);
|
||||
button->set_rgba(colour);
|
||||
button->property_rgba().signal_changed().connect(
|
||||
sigc::mem_fun(*this, &EffectorWindow::onParamChanged));
|
||||
widget = std::move(button);
|
||||
break;
|
||||
}
|
||||
case ParamSpec::Type::Choice: {
|
||||
std::vector<Glib::ustring> choices(spec.choices.begin(), spec.choices.end());
|
||||
auto dropdown = std::make_unique<Gtk::DropDown>();
|
||||
@@ -358,9 +388,19 @@ ParamValues EffectorWindow::paramValues() const {
|
||||
values[entry.spec.key] = static_cast<Gtk::SpinButton*>(entry.widget)->get_value();
|
||||
break;
|
||||
case ParamSpec::Type::Path:
|
||||
case ParamSpec::Type::Text:
|
||||
values[entry.spec.key] =
|
||||
std::string(static_cast<Gtk::Entry*>(entry.widget)->get_text());
|
||||
break;
|
||||
case ParamSpec::Type::Color: {
|
||||
const auto colour = static_cast<Gtk::ColorDialogButton*>(entry.widget)->get_rgba();
|
||||
const std::uint8_t rgb[3] = {
|
||||
static_cast<std::uint8_t>(std::lround(colour.get_red() * 255.0)),
|
||||
static_cast<std::uint8_t>(std::lround(colour.get_green() * 255.0)),
|
||||
static_cast<std::uint8_t>(std::lround(colour.get_blue() * 255.0))};
|
||||
values[entry.spec.key] = formatColor(rgb);
|
||||
break;
|
||||
}
|
||||
case ParamSpec::Type::Choice: {
|
||||
const auto index = static_cast<Gtk::DropDown*>(entry.widget)->get_selected();
|
||||
if (index < entry.spec.choices.size()) values[entry.spec.key] = entry.spec.choices[index];
|
||||
|
||||
Reference in New Issue
Block a user