From b6b5f94f08dcaa9b75e276f2e528ac9a0bd4faf3 Mon Sep 17 00:00:00 2001 From: Si Date: Fri, 18 Sep 2026 16:36:43 +0200 Subject: [PATCH] Put the chain first in the sidebar The source images took the top of the sidebar and most of its height, which made the list of files look like the subject of the window rather than its input. The chain is what the interface is about, so it now comes first with the selected stage's params right below it, taking the free space; the sources sit at the bottom in a collapsed expander whose label carries the count, with their own "Add images" button instead of the one that used to be in the header bar. Co-Authored-By: Claude Opus 5 --- src/gui/window.cpp | 89 +++++++++++++++++++++++++++++++++------------- src/gui/window.hpp | 2 ++ 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/gui/window.cpp b/src/gui/window.cpp index 0c01216..5c20daf 100644 --- a/src/gui/window.cpp +++ b/src/gui/window.cpp @@ -85,13 +85,6 @@ void EffectorWindow::buildUi() { menuButton_.set_popover(static_cast(buildMainMenu())); header->pack_end(menuButton_); - auto* addButton = Gtk::make_managed(); - addButton->set_icon_name("list-add-symbolic"); - addButton->set_tooltip_text("Add images"); - addButton->signal_clicked().connect(sigc::mem_fun(*this, &EffectorWindow::onAddImages)); - header->pack_start(*addButton); - addButton_ = addButton; - saveButton_.set_sensitive(false); saveButton_.signal_clicked().connect(sigc::mem_fun(*this, &EffectorWindow::onSave)); header->pack_end(saveButton_); @@ -120,24 +113,64 @@ void EffectorWindow::buildUi() { root->append(*paned); } +// The chain is what the interface is about, so it comes first and the params +// of the selected stage sit right below it. The sources are a prerequisite, +// not the subject: they live at the bottom in a collapsed expander. Gtk::Widget& EffectorWindow::buildSidebar() { auto* sidebar = Gtk::make_managed(Gtk::Orientation::VERTICAL, 12); sidebar->set_margin(12); - sidebar->append(*sectionLabel("Source images")); + sidebar->append(buildChainSection()); + + paramHeading_.set_xalign(0.0F); + paramHeading_.add_css_class("heading"); + sidebar->append(paramHeading_); + paramGrid_.set_column_spacing(8); + paramGrid_.set_row_spacing(6); + auto* paramScroller = Gtk::make_managed(); + paramScroller->set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); + paramScroller->set_child(paramGrid_); + paramScroller->set_vexpand(true); + sidebar->append(*paramScroller); + rebuildChainRows(); + buildParamWidgets(); + + sidebar->append(*Gtk::make_managed(Gtk::Orientation::HORIZONTAL)); + sourceList_.set_selection_mode(Gtk::SelectionMode::NONE); sourceList_.add_css_class("boxed-list"); auto* empty = Gtk::make_managed("No images yet"); - empty->set_margin_top(18); - empty->set_margin_bottom(18); + empty->set_margin_top(12); + empty->set_margin_bottom(12); sourceList_.set_placeholder(*empty); auto* scroller = Gtk::make_managed(); scroller->set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); scroller->set_child(sourceList_); - scroller->set_size_request(-1, 170); - scroller->set_vexpand(true); - sidebar->append(*scroller); + scroller->set_size_request(-1, 140); + scroller->set_margin_top(6); + + auto* sourcesBox = Gtk::make_managed(Gtk::Orientation::VERTICAL, 6); + sourcesBox->append(*scroller); + + // Same shape as "Add effect": the icon and the text are put together by + // hand because set_label() and set_icon_name() replace one another. + auto* addBox = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 6); + addBox->append(*Gtk::make_managed( + Gio::ThemedIcon::create("list-add-symbolic"))); + addBox->append(*Gtk::make_managed("Add images")); + auto* addButton = Gtk::make_managed(); + addButton->set_child(*addBox); + addButton->set_halign(Gtk::Align::START); + addButton->set_tooltip_text("Add source images"); + addButton->signal_clicked().connect(sigc::mem_fun(*this, &EffectorWindow::onAddImages)); + sourcesBox->append(*addButton); + addButton_ = addButton; + + sourcesExpander_.set_child(*sourcesBox); + sourcesExpander_.set_expanded(false); + updateSourcesLabel(); + sidebar->append(sourcesExpander_); sidebar->append(*sectionLabel("Canvas")); auto* grid = Gtk::make_managed(); @@ -163,17 +196,6 @@ Gtk::Widget& EffectorWindow::buildSidebar() { grid->attach(heightSpin_, 1, 1); sidebar->append(*grid); - sidebar->append(buildChainSection()); - - paramHeading_.set_xalign(0.0F); - paramHeading_.add_css_class("heading"); - sidebar->append(paramHeading_); - paramGrid_.set_column_spacing(8); - paramGrid_.set_row_spacing(6); - sidebar->append(paramGrid_); - rebuildChainRows(); - buildParamWidgets(); - auto* seedBox = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 8); auto* seedLabel = Gtk::make_managed("Seed"); seedLabel->set_xalign(0.0F); @@ -187,6 +209,15 @@ Gtk::Widget& EffectorWindow::buildSidebar() { return *sidebar; } +// The expander is collapsed by default, so its label carries the count that +// used to be plain to see in the list. +void EffectorWindow::updateSourcesLabel() { + int count = 0; + while (sourceList_.get_row_at_index(count) != nullptr) ++count; + sourcesExpander_.set_label(count == 0 ? "Source images" + : "Source images (" + std::to_string(count) + ")"); +} + Gtk::Widget& EffectorWindow::buildPreview() { picture_.set_can_shrink(true); picture_.set_content_fit(Gtk::ContentFit::CONTAIN); @@ -565,6 +596,7 @@ void EffectorWindow::appendSourceRow(const std::string& path) { row->set_child(*box); sourceList_.append(*row); + updateSourcesLabel(); } void EffectorWindow::removeSource(Gtk::ListBoxRow* row) { @@ -575,6 +607,7 @@ void EffectorWindow::removeSource(Gtk::ListBoxRow* row) { } }); sourceList_.remove(*row); + updateSourcesLabel(); requestRender(); } @@ -622,7 +655,12 @@ Gtk::Widget& EffectorWindow::buildChainSection() { chainList_.set_placeholder(*empty); chainList_.signal_row_selected().connect( [this](Gtk::ListBoxRow*) { onStageSelected(); }); - box->append(chainList_); + + auto* scroller = Gtk::make_managed(); + scroller->set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); + scroller->set_child(chainList_); + scroller->set_size_request(-1, 150); + box->append(*scroller); // set_label() and set_icon_name() replace one another, so the icon and the // text are put together by hand. @@ -995,6 +1033,7 @@ void EffectorWindow::applyProject(const Project& project, const std::string& pat void EffectorWindow::clearSources() { while (auto* row = sourceList_.get_row_at_index(0)) sourceList_.remove(*row); + updateSourcesLabel(); mutateSources([](SourceList& list) { list.clear(); }); loadQueue_.clear(); if (loading_) dropLoadedImages_ = true; diff --git a/src/gui/window.hpp b/src/gui/window.hpp index 5c70fa5..99731e5 100644 --- a/src/gui/window.hpp +++ b/src/gui/window.hpp @@ -48,6 +48,7 @@ private: void closeLoadWindow(); void cancelLoad(); void appendSourceRow(const std::string& path); + void updateSourcesLabel(); // the collapsed expander shows the count void removeSource(Gtk::ListBoxRow* row); Gtk::Widget& buildChainSection(); @@ -139,6 +140,7 @@ private: std::unique_ptr loadWindow_; Gtk::ProgressBar* loadBar_ = nullptr; Gtk::Label* loadFileLabel_ = nullptr; + Gtk::Expander sourcesExpander_; Gtk::ListBox sourceList_; Gtk::SpinButton widthSpin_; Gtk::SpinButton heightSpin_;