Let several text stages share one named look

A picture with a title, a caption and a credit meant typing the same
font, size and colour into every text stage, and changing the look meant
typing them all again. A project now holds named styles: a stage follows
one, and editing it moves every stage that follows it at once.

The front-ends learn nothing effect-specific. A ParamSpec can be marked
.styled(), saying a style carries it rather than the stage, and
Effect::styleKind() names the pool it comes from -- so styleSpecs(kind)
is to a style what params() is to a stage, and a second text-ish effect
would slot into the styles that already exist. The look is what a style
carries; the words and the placement stay on the stage, which is what
lets two stages share a typography in two different corners.

stageValues() resolves one in a single place, and a stage keeps a full
set of values of its own beside the style, so switching it to Custom,
dropping the style, or opening the project in a build that does not know
it renders exactly the same picture. A style moves stages together; it
never holds the only copy of anything.

The command line builds no styles and passes none, so it renders what it
always did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Si
2026-09-19 21:55:55 +02:00
co-authored by Claude Opus 5
parent eb417bb41a
commit 3aade6f361
17 changed files with 890 additions and 131 deletions
+33 -1
View File
@@ -7,6 +7,7 @@
#include <memory>
#include <mutex>
#include <optional>
#include <functional>
#include <string>
#include <thread>
#include <vector>
@@ -16,6 +17,7 @@
#include "core/effect.hpp"
#include "core/image.hpp"
#include "core/project.hpp"
#include "core/style.hpp"
namespace effector {
@@ -30,7 +32,29 @@ private:
Gtk::Widget& buildPreview();
void buildParamWidgets();
ParamValues paramValues() const;
void storeParams(); // widgets -> the selected stage's values
void storeParams(); // widgets -> the selected stage, and its style
// The style row above the params of a stage whose effect takes one.
std::unique_ptr<Gtk::Widget> buildStyleRow(const ChainStage& stage);
// The names of the pool the selected stage draws from, in the order the
// dropdown shows them.
std::vector<std::string> styleNames(const std::string& kind) const;
// The style the selected stage follows, or nullptr when it stands alone.
Style* selectedStyle();
const Style* selectedStyle() const;
void onStyleSelected(); // the dropdown: adopt, drop, or make a new one
void adoptStyle(const std::string& name);
void onNewStyle();
void onRenameStyle();
void onRemoveStyle();
// The little modal that asks for a style name, used by both New and
// Rename. Built from scratch each time, like the progress window.
void askStyleName(const std::string& title, const std::string& initial,
const std::function<void(const std::string&)>& accept);
void closeStyleWindow();
// A name that is empty, or already used in the pool, is refused in place.
bool styleNameTaken(const std::string& kind, const std::string& name) const;
void applyInitial(const Options& initial);
bool canRender() const; // a chain, and sources if the chain needs them
@@ -114,6 +138,9 @@ private:
// The cascade: stage 0 works on the sources, every later stage on the
// output of the one before it.
std::vector<ChainStage> stages_;
// The named looks the stages share. Edited through the params of whichever
// stage follows one, and saved with the project.
StyleSet styles_;
int selectedStage_ = 0;
bool suspendStageSelect_ = false; // guards the rebuild of the chain rows
Image result_;
@@ -166,6 +193,11 @@ private:
// Set while a format fills the spins, so their own handler does not queue
// a second render on top of the one the format change asks for.
bool suspendCanvasSync_ = false;
Gtk::DropDown* styleDrop_ = nullptr; // null while the stage takes no style
bool suspendStyleSelect_ = false; // guards filling the dropdown
std::shared_ptr<Gtk::Window> styleWindow_;
Gtk::Entry* styleNameEntry_ = nullptr; // nulled with the window
Gtk::Label* styleNameError_ = nullptr;
Gtk::Grid paramGrid_;
Gtk::Label paramHeading_;
Gtk::Entry seedEntry_;