Let a chain stage be switched off without taking it apart
Trying a stage out meant removing it and building it again from scratch, settings and all, to see what the picture looked like without it. A stage now carries an `enabled` flag and can simply be switched off: it keeps its place in the chain and everything it was set to. `enabledStages` is the one place that reads the flag, and renderChain, chainNeedsSources, chainTitle and chainSlug all go through it. A disabled stage is skipped whole — handed no image and drawing nothing from the shared rng — so switching one off renders byte for byte what the chain rendered before that stage was added, which is the only thing that makes the switch worth anything while comparing. Whichever stage runs first is the one handed the sources, so disabling a leading scanlines lets blur lead and the chain needs no images at all. The status line and the generated file name follow too: they name what was rendered, not what sits in the sidebar. A chain with every stage disabled throws rather than quietly rendering nothing. In the interface it is a check on the chain row: the row dims, the params heading says "(off)" and the settings stay editable — a stage being worked on is not the same as one being deleted. On the command line it is `--disable NAME`, which picks its stage by effect name exactly as the params do. A project writes `"enabled": false` only for a stage that is off, so existing project files are unchanged and an ordinary one stays uncluttered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+41
-4
@@ -366,8 +366,10 @@ void EffectorWindow::buildParamWidgets() {
|
||||
return;
|
||||
}
|
||||
const auto& stage = stages_[static_cast<std::size_t>(selectedStage_)];
|
||||
// The settings of a stage that is switched off stay editable: it is still
|
||||
// being worked on, it just is not in the picture for the moment.
|
||||
paramHeading_.set_text(std::to_string(selectedStage_ + 1) + ". " + stage.effect->title() +
|
||||
" settings");
|
||||
(stage.enabled ? " settings" : " settings (off)"));
|
||||
|
||||
int row = 0;
|
||||
for (const auto& spec : stage.effect->params()) {
|
||||
@@ -913,6 +915,12 @@ void EffectorWindow::rebuildChainRows() {
|
||||
// removing it would leave the empty list blank.
|
||||
while (auto* row = chainList_.get_row_at_index(0)) chainList_.remove(*row);
|
||||
|
||||
// The first stage that actually runs is the one handed the sources, which
|
||||
// is not stage 1 once stage 1 is switched off.
|
||||
const auto running = enabledStages(stages_);
|
||||
const int firstRunning =
|
||||
running.empty() ? -1 : static_cast<int>(running.front() - stages_.data());
|
||||
|
||||
for (std::size_t i = 0; i < stages_.size(); ++i) {
|
||||
const int index = static_cast<int>(i);
|
||||
auto* row = Gtk::make_managed<Gtk::ListBoxRow>();
|
||||
@@ -922,13 +930,28 @@ void EffectorWindow::rebuildChainRows() {
|
||||
box->set_margin_start(8);
|
||||
box->set_margin_end(4);
|
||||
|
||||
// The check is what switches a stage off: it keeps its settings and
|
||||
// its place in the chain, and the render simply skips it.
|
||||
auto* check = Gtk::make_managed<Gtk::CheckButton>();
|
||||
check->set_active(stages_[i].enabled);
|
||||
check->set_tooltip_text("Apply this effect");
|
||||
check->set_valign(Gtk::Align::CENTER);
|
||||
// Connected after set_active, so filling in the rebuilt rows does not
|
||||
// look like the user reaching for the check.
|
||||
check->signal_toggled().connect([this, index, check] {
|
||||
setStageEnabled(index, check->get_active());
|
||||
});
|
||||
box->append(*check);
|
||||
|
||||
auto* label = Gtk::make_managed<Gtk::Label>(std::to_string(index + 1) + ". " +
|
||||
stages_[i].effect->title());
|
||||
label->set_xalign(0.0F);
|
||||
label->set_hexpand(true);
|
||||
label->set_ellipsize(Pango::EllipsizeMode::END);
|
||||
label->set_tooltip_text(index == 0 ? "Applied to the source images"
|
||||
: "Applied to the output of the stage above");
|
||||
if (!stages_[i].enabled) label->add_css_class("dim-label");
|
||||
label->set_tooltip_text(!stages_[i].enabled ? "Skipped while it is switched off"
|
||||
: index == firstRunning ? "Applied to the source images"
|
||||
: "Applied to the output so far");
|
||||
box->append(*label);
|
||||
|
||||
struct Action {
|
||||
@@ -965,6 +988,19 @@ void EffectorWindow::rebuildChainRows() {
|
||||
updateSourcesVisible();
|
||||
}
|
||||
|
||||
// Switching a stage off leaves everything about it alone -- its settings, its
|
||||
// place in the chain, the stage the params below are showing -- so the only
|
||||
// visible change is the dimmed row and the picture the render comes back with.
|
||||
void EffectorWindow::setStageEnabled(int index, bool enabled) {
|
||||
if (index < 0 || static_cast<std::size_t>(index) >= stages_.size()) return;
|
||||
auto& stage = stages_[static_cast<std::size_t>(index)];
|
||||
if (stage.enabled == enabled) return;
|
||||
stage.enabled = enabled;
|
||||
rebuildChainRows(); // the row dims, and another stage may now lead the chain
|
||||
if (index == selectedStage_) buildParamWidgets(); // the heading says "off"
|
||||
requestRender();
|
||||
}
|
||||
|
||||
void EffectorWindow::selectStage(int index) {
|
||||
if (auto* row = chainList_.get_row_at_index(index)) chainList_.select_row(*row);
|
||||
}
|
||||
@@ -1039,12 +1075,13 @@ void EffectorWindow::onRecreate() {
|
||||
// ever handed the sources, so an effect that does not want them (blur) renders
|
||||
// happily with none loaded.
|
||||
bool EffectorWindow::canRender() const {
|
||||
if (stages_.empty()) return false;
|
||||
if (enabledStages(stages_).empty()) return false;
|
||||
return !sources_->empty() || !chainNeedsSources(stages_);
|
||||
}
|
||||
|
||||
std::string EffectorWindow::missingText() const {
|
||||
if (stages_.empty()) return "Add an effect to start.";
|
||||
if (enabledStages(stages_).empty()) return "Every effect is switched off.";
|
||||
return "Add source images to start.";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user