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:
Si
2026-09-19 21:11:17 +02:00
co-authored by Claude Opus 5
parent f61161aef0
commit eb417bb41a
9 changed files with 185 additions and 28 deletions
+27 -11
View File
@@ -2,14 +2,31 @@
namespace effector {
std::vector<const ChainStage*> enabledStages(const std::vector<ChainStage>& stages) {
std::vector<const ChainStage*> running;
running.reserve(stages.size());
for (const auto& stage : stages) {
if (stage.enabled && stage.effect) running.push_back(&stage);
}
return running;
}
Image renderChain(const std::vector<ChainStage>& stages, const std::vector<Image>& sources,
int width, int height, Rng& rng) {
if (stages.empty()) throw EffectError("The effect chain is empty.");
for (const auto& stage : stages) {
if (!stage.effect) throw EffectError("The effect chain has an empty stage.");
}
// A disabled stage never reaches the render at all: it is not handed an
// image and draws nothing from the shared rng, so switching one off gives
// back exactly the picture the chain made before it was added.
const auto running = enabledStages(stages);
if (running.empty()) throw EffectError("Every effect in the chain is disabled.");
Image image;
for (std::size_t i = 0; i < stages.size(); ++i) {
const auto& stage = stages[i];
if (!stage.effect) throw EffectError("The effect chain has an empty stage.");
for (std::size_t i = 0; i < running.size(); ++i) {
const auto& stage = *running[i];
auto values = stage.effect->defaults();
for (const auto& [key, value] : stage.values) values[key] = value;
@@ -27,26 +44,25 @@ Image renderChain(const std::vector<ChainStage>& stages, const std::vector<Image
}
bool chainNeedsSources(const std::vector<ChainStage>& stages) {
if (stages.empty() || !stages.front().effect) return false;
return stages.front().effect->needsSources();
const auto running = enabledStages(stages);
if (running.empty()) return false;
return running.front()->effect->needsSources();
}
std::string chainTitle(const std::vector<ChainStage>& stages) {
std::string text;
for (const auto& stage : stages) {
if (!stage.effect) continue;
for (const auto* stage : enabledStages(stages)) {
if (!text.empty()) text += " → ";
text += stage.effect->title();
text += stage->effect->title();
}
return text;
}
std::string chainSlug(const std::vector<ChainStage>& stages) {
std::string text;
for (const auto& stage : stages) {
if (!stage.effect) continue;
for (const auto* stage : enabledStages(stages)) {
if (!text.empty()) text += "-";
text += stage.effect->name();
text += stage->effect->name();
}
return text;
}