diff --git a/data/widgets/new_layout.xml b/data/widgets/new_layout.xml index 5ad98cace..3e8adf845 100644 --- a/data/widgets/new_layout.xml +++ b/data/widgets/new_layout.xml @@ -5,9 +5,7 @@ diff --git a/src/app/ui/layout_selector.cpp b/src/app/ui/layout_selector.cpp index de57abed6..9678a52d5 100644 --- a/src/app/ui/layout_selector.cpp +++ b/src/app/ui/layout_selector.cpp @@ -12,12 +12,14 @@ #include "app/app.h" #include "app/i18n/strings.h" +#include "app/match_words.h" #include "app/ui/button_set.h" #include "app/ui/configure_timeline_popup.h" #include "app/ui/main_window.h" #include "app/ui/separator_in_view.h" #include "app/ui/skin/skin_theme.h" #include "fmt/format.h" +#include "ui/entry.h" #include "ui/listitem.h" #include "ui/tooltips.h" #include "ui/window.h" @@ -79,6 +81,48 @@ private: obs::scoped_connection m_timelinePosConn; }; +// TODO this combobox is similar to FileSelector::CustomFileNameEntry +// and GotoFrameCommand::TagsEntry +class LayoutsEntry : public ComboBox { +public: + LayoutsEntry(Layouts& layouts) : m_layouts(layouts) + { + setEditable(true); + getEntryWidget()->Change.connect(&LayoutsEntry::onEntryChange, this); + fill(true); + } + +private: + void fill(bool all) + { + deleteAllItems(); + + MatchWords match(getEntryWidget()->text()); + + bool matchAny = false; + for (auto& layout : m_layouts) { + if (match(layout->name())) { + matchAny = true; + break; + } + } + for (auto& layout : m_layouts) { + if (all || !matchAny || match(layout->name())) + addItem(layout->name()); + } + } + + void onEntryChange() + { + closeListBox(); + fill(false); + if (getItemCount() > 0) + openListBox(); + } + + Layouts& m_layouts; +}; + }; // namespace class LayoutSelector::LayoutItem : public ListItem { @@ -95,13 +139,17 @@ public: const std::string& text, const LayoutPtr layout = nullptr) : ListItem(text) - , m_selector(selector) , m_id(id) + , m_selector(selector) , m_layout(layout) { ASSERT((id != USER_DEFINED && layout == nullptr) || (id == USER_DEFINED && layout != nullptr)); } + Layout* layout() const { return m_layout.get(); } + + void setLayout(const LayoutPtr& layout) { m_layout = layout; } + void selectImmediately() { MainWindow* win = App::instance()->mainWindow(); @@ -126,13 +174,23 @@ public: switch (m_id) { case LayoutId::SAVE_LAYOUT: { - gen::NewLayout window; - window.name()->setText( - fmt::format("{} ({})", window.name()->text(), m_selector->m_layouts.size() + 1)); + // Select the "Layout" separator (it's like selecting nothing) + // TODO improve the ComboBox to select a real "nothing" (with + // a placeholder text) + m_selector->m_comboBox.setSelectedItemIndex(0); + gen::NewLayout window; + LayoutsEntry name(m_selector->m_layouts); + name.getEntryWidget()->setMaxTextLength(128); + name.setFocusMagnet(true); + name.setValue(fmt::format("{} ({})", + Strings::new_layout_default_name(), + m_selector->m_layouts.size() + 1)); + + window.namePlaceholder()->addChild(&name); window.openWindowInForeground(); if (window.closer() == window.ok()) { - auto layout = Layout::MakeFromDock(window.name()->text(), win->customizableDock()); + auto layout = Layout::MakeFromDock(name.getValue(), win->customizableDock()); m_selector->addLayout(layout); } @@ -195,12 +253,23 @@ LayoutSelector::~LayoutSelector() void LayoutSelector::addLayout(const LayoutPtr& layout) { - auto item = m_comboBox.addItem( - new LayoutItem(this, LayoutItem::USER_DEFINED, layout->name(), layout)); - - m_layouts.addLayout(layout); - - m_comboBox.setSelectedItemIndex(item); + bool added = m_layouts.addLayout(layout); + if (added) { + auto item = m_comboBox.addItem( + new LayoutItem(this, LayoutItem::USER_DEFINED, layout->name(), layout)); + m_comboBox.setSelectedItemIndex(item); + } + else { + for (auto item : m_comboBox) { + if (auto layoutItem = dynamic_cast(item)) { + if (layoutItem->layout() && layoutItem->layout()->name() == layout->name()) { + layoutItem->setLayout(layout); + m_comboBox.setSelectedItem(item); + break; + } + } + } + } } void LayoutSelector::onAnimationFrame() diff --git a/src/app/ui/layouts.cpp b/src/app/ui/layouts.cpp index 9089608a2..7ce5fd250 100644 --- a/src/app/ui/layouts.cpp +++ b/src/app/ui/layouts.cpp @@ -15,6 +15,7 @@ #include "app/xml_exception.h" #include "base/fs.h" +#include #include namespace app { @@ -37,9 +38,19 @@ Layouts::~Layouts() save(m_userLayoutsFilename); } -void Layouts::addLayout(const LayoutPtr& layout) +bool Layouts::addLayout(const LayoutPtr& layout) { - m_layouts.push_back(layout); + auto it = std::find_if(m_layouts.begin(), m_layouts.end(), [layout](const LayoutPtr& l) { + return l->name() == layout->name(); + }); + if (it != m_layouts.end()) { + *it = layout; // Replace existent layout + return false; + } + else { + m_layouts.push_back(layout); + return true; + } } void Layouts::load(const std::string& fn) diff --git a/src/app/ui/layouts.h b/src/app/ui/layouts.h index 6fd5e8bfb..7beff41b5 100644 --- a/src/app/ui/layouts.h +++ b/src/app/ui/layouts.h @@ -22,7 +22,9 @@ public: size_t size() const { return m_layouts.size(); } - void addLayout(const LayoutPtr& layout); + // Returns true if the layout is added, or false if it was + // replaced. + bool addLayout(const LayoutPtr& layout); // To iterate layouts using List = std::vector;