Add an image overlay effect
Lays a single picture from disk over its input: fitted, filled, stretched, left at its own size or tiled, centred or pinned to the top left corner, nudged by an offset and mixed in by its opacity. The file it needs is a new kind of param, Path, so the interface shows a chooser and the CLI takes a file name, both without knowing an overlay exists. Projects write it relative to their own folder, as they do a source, and Image::load can now hand back the alpha channel instead of flattening it onto white, so a cut-out PNG keeps its cut-out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+59
-1
@@ -293,6 +293,28 @@ void EffectorWindow::buildParamWidgets() {
|
||||
widget = std::move(spin);
|
||||
break;
|
||||
}
|
||||
case ParamSpec::Type::Path: {
|
||||
// An entry holding the path, with a chooser beside it; the
|
||||
// entry is what paramValues() reads back.
|
||||
auto box = std::make_unique<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
|
||||
auto* entry = Gtk::make_managed<Gtk::Entry>();
|
||||
entry->set_hexpand(true);
|
||||
entry->set_text(paramText(stage.values, spec.key, ""));
|
||||
entry->set_placeholder_text("No image chosen");
|
||||
entry->signal_activate().connect(
|
||||
sigc::mem_fun(*this, &EffectorWindow::onParamChanged));
|
||||
auto* choose = Gtk::make_managed<Gtk::Button>("Choose…");
|
||||
const int index = selectedStage_;
|
||||
const std::string key = spec.key;
|
||||
const std::string title = spec.label;
|
||||
choose->signal_clicked().connect(
|
||||
[this, index, key, title] { onChoosePath(index, key, title); });
|
||||
box->append(*entry);
|
||||
box->append(*choose);
|
||||
paramWidgets_.push_back({spec, entry});
|
||||
widget = std::move(box);
|
||||
break;
|
||||
}
|
||||
case ParamSpec::Type::Choice: {
|
||||
std::vector<Glib::ustring> choices(spec.choices.begin(), spec.choices.end());
|
||||
auto dropdown = std::make_unique<Gtk::DropDown>();
|
||||
@@ -311,7 +333,9 @@ void EffectorWindow::buildParamWidgets() {
|
||||
widget->set_hexpand(true);
|
||||
paramGrid_.attach(*label, 0, row);
|
||||
paramGrid_.attach(*widget, 1, row);
|
||||
paramWidgets_.push_back({spec, widget.get()});
|
||||
// A path attaches a box but reads back from the entry inside it, which
|
||||
// it has registered itself.
|
||||
if (spec.type != ParamSpec::Type::Path) paramWidgets_.push_back({spec, widget.get()});
|
||||
ownedWidgets_.push_back(std::move(label));
|
||||
ownedWidgets_.push_back(std::move(widget));
|
||||
++row;
|
||||
@@ -333,6 +357,10 @@ ParamValues EffectorWindow::paramValues() const {
|
||||
case ParamSpec::Type::Double:
|
||||
values[entry.spec.key] = static_cast<Gtk::SpinButton*>(entry.widget)->get_value();
|
||||
break;
|
||||
case ParamSpec::Type::Path:
|
||||
values[entry.spec.key] =
|
||||
std::string(static_cast<Gtk::Entry*>(entry.widget)->get_text());
|
||||
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];
|
||||
@@ -809,6 +837,36 @@ void EffectorWindow::onParamChanged() {
|
||||
requestRender();
|
||||
}
|
||||
|
||||
void EffectorWindow::onChoosePath(int stage, const std::string& key, const std::string& label) {
|
||||
auto dialog = Gtk::FileDialog::create();
|
||||
dialog->set_title("Select the " + label);
|
||||
|
||||
auto filter = Gtk::FileFilter::create();
|
||||
filter->set_name("Images");
|
||||
for (const char* pattern : kImagePatterns) filter->add_pattern(pattern);
|
||||
auto filters = Gio::ListStore<Gtk::FileFilter>::create();
|
||||
filters->append(filter);
|
||||
dialog->set_filters(filters);
|
||||
dialog->set_default_filter(filter);
|
||||
|
||||
dialog->open(*this, [this, dialog, stage, key](const Glib::RefPtr<Gio::AsyncResult>& result) {
|
||||
std::string path;
|
||||
try {
|
||||
const auto file = dialog->open_finish(result);
|
||||
if (file) path = file->get_path();
|
||||
} catch (const Glib::Error&) {
|
||||
return; // cancelled
|
||||
}
|
||||
if (path.empty() || stage < 0 || static_cast<std::size_t>(stage) >= stages_.size()) return;
|
||||
// The widgets may be showing another stage by now: store what they hold
|
||||
// first, then write the path into the stage that asked for it.
|
||||
storeParams();
|
||||
stages_[static_cast<std::size_t>(stage)].values[key] = path;
|
||||
if (stage == selectedStage_) buildParamWidgets();
|
||||
requestRender();
|
||||
});
|
||||
}
|
||||
|
||||
void EffectorWindow::onSeedEntered() {
|
||||
try {
|
||||
seed_ = std::stoull(seedEntry_.get_text());
|
||||
|
||||
Reference in New Issue
Block a user