diff --git a/src/core/effect.hpp b/src/core/effect.hpp index 1becff7..8e305df 100644 --- a/src/core/effect.hpp +++ b/src/core/effect.hpp @@ -38,6 +38,13 @@ public: // use. An effect with no kind must declare no styled params. virtual std::string styleKind() const { return {}; } + // How a stage of this effect is named in a list of stages, given its + // values: the title unless the values say something more telling. + virtual std::string label(const ParamValues& values) const { + (void)values; + return title(); + } + virtual Image render(const std::vector& sources, int width, int height, Rng& rng, const ParamValues& values) const = 0; diff --git a/src/effects/text.cpp b/src/effects/text.cpp index 667382b..722fd2d 100644 --- a/src/effects/text.cpp +++ b/src/effects/text.cpp @@ -203,6 +203,22 @@ void TextEffect::loadBoxImage(const std::string& path, Image& image, alpha = cachedAlpha_; } +std::string TextEffect::label(const ParamValues& values) const { + // Several lines read as one: each run of whitespace becomes a single space. + std::string words; + bool gap = false; + for (const char c : paramText(values, "text", "")) { + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + gap = !words.empty(); + continue; + } + if (gap) words += ' '; + gap = false; + words += c; + } + return words.empty() ? title() : title() + " (" + words + ")"; +} + Image TextEffect::render(const std::vector& sources, int width, int height, Rng& rng, const ParamValues& values) const { if (width < 1 || height < 1) throw EffectError("Canvas size must be at least 1x1 pixels."); diff --git a/src/effects/text.hpp b/src/effects/text.hpp index 00af2af..2cb4d10 100644 --- a/src/effects/text.hpp +++ b/src/effects/text.hpp @@ -30,6 +30,8 @@ public: const std::vector& params() const override; std::string styleKind() const override { return "text"; } bool needsSources() const override { return false; } + // "Text (Hello world)", so stages writing different words tell apart. + std::string label(const ParamValues& values) const override; Image render(const std::vector& sources, int width, int height, Rng& rng, const ParamValues& values) const override; diff --git a/src/gui/window.cpp b/src/gui/window.cpp index b5ade6e..b065922 100644 --- a/src/gui/window.cpp +++ b/src/gui/window.cpp @@ -1313,6 +1313,7 @@ void EffectorWindow::rebuildChainRows() { // Only the rows: the placeholder is a child of the list box as well, and // removing it would leave the empty list blank. while (auto* row = chainList_.get_row_at_index(0)) chainList_.remove(*row); + stageLabels_.clear(); // The first stage that actually runs is the one handed the sources, which // is not stage 1 once stage 1 is switched off. @@ -1343,8 +1344,8 @@ void EffectorWindow::rebuildChainRows() { }); box->append(*check); - auto* label = Gtk::make_managed(std::to_string(index + 1) + ". " + - stages_[i].effect->title()); + auto* label = Gtk::make_managed(stageLabel(i)); + stageLabels_.push_back(label); label->set_xalign(0.0F); label->set_hexpand(true); label->set_ellipsize(Pango::EllipsizeMode::END); @@ -1418,8 +1419,17 @@ void EffectorWindow::onStageSelected() { // ------------------------------------------------------------ rendering --- +std::string EffectorWindow::stageLabel(std::size_t index) const { + const ChainStage& stage = stages_[index]; + return std::to_string(index + 1) + ". " + stage.effect->label(stageValues(stage, styles_)); +} + void EffectorWindow::onParamChanged() { storeParams(); + // A param can be what names the stage (the words of a text stage), and a + // styled one can rename every stage sharing the style. + for (std::size_t i = 0; i < stageLabels_.size() && i < stages_.size(); ++i) + stageLabels_[i]->set_text(stageLabel(i)); requestRender(); } diff --git a/src/gui/window.hpp b/src/gui/window.hpp index 6fb4103..37b3042 100644 --- a/src/gui/window.hpp +++ b/src/gui/window.hpp @@ -95,6 +95,7 @@ private: // The check on a chain row: the stage stays where it is, and is skipped. void setStageEnabled(int index, bool enabled); void rebuildChainRows(); + std::string stageLabel(std::size_t index) const; void selectStage(int index); void onStageSelected(); Gtk::Widget& buildMainMenu(); // the header menu: new, open, save project @@ -145,6 +146,8 @@ private: StyleSet styles_; int selectedStage_ = 0; bool suspendStageSelect_ = false; // guards the rebuild of the chain rows + // The name label of each chain row, renamed in place as params change. + std::vector stageLabels_; Image result_; std::uint64_t seed_ = 0; std::string projectPath_; // empty until the project has been saved once diff --git a/tests/core_tests.cpp b/tests/core_tests.cpp index ffee49c..747c935 100644 --- a/tests/core_tests.cpp +++ b/tests/core_tests.cpp @@ -857,6 +857,13 @@ int main() { // Like blur and overlay it needs no sources of its own, and the same // settings render the same bytes twice over. check(!TextEffect().needsSources(), "text can lead a chain"); + + // A text stage is named after its words, on one line. + check(TextEffect().label({{"text", std::string(" Two\nlines ")}}) == "Text (Two lines)", + "a text stage is labelled with its words"); + check(TextEffect().label({{"text", std::string(" \n")}}) == "Text", + "a text stage with no words is labelled with the title alone"); + check(effectByName("blur")->label({}) == "Blur", "other effects are labelled with their title"); const auto alone = renderText({}, 200, 120, plain); check(alone.width() == 200 && countRed(alone, 0, 0, 200, 120) > 0, "without sources it draws on an empty canvas");