From 35438c9b690c55be57b6620afab56ccb4695a99e Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 13 Oct 2021 10:50:42 -0300 Subject: [PATCH 01/20] Add Dock widget, initial & basic version of dockable elements (#518) Some missing features so far: 1) Restore old layout configuration (color bar split pos, timeline pos, etc.) and migrate to new Dock layout 2) Load/saving Dock layout 3) Create & customize current layoout (drag-and-drop widgets, etc.) --- data/widgets/main_window.xml | 30 --- src/app/CMakeLists.txt | 2 + src/app/ui/color_bar.cpp | 4 +- src/app/ui/color_bar.h | 13 +- src/app/ui/context_bar.h | 7 +- src/app/ui/dock.cpp | 476 +++++++++++++++++++++++++++++++++ src/app/ui/dock.h | 86 ++++++ src/app/ui/dockable.h | 33 +++ src/app/ui/layout_selector.cpp | 134 ++++++++++ src/app/ui/layout_selector.h | 54 ++++ src/app/ui/main_menu_bar.h | 7 +- src/app/ui/main_window.cpp | 146 +++++----- src/app/ui/main_window.h | 16 +- src/app/ui/notifications.h | 7 +- src/app/ui/status_bar.h | 7 +- src/app/ui/tabs.h | 7 +- src/app/ui/timeline/timeline.h | 10 +- src/app/ui/toolbar.h | 11 + 18 files changed, 942 insertions(+), 108 deletions(-) delete mode 100644 data/widgets/main_window.xml create mode 100644 src/app/ui/dock.cpp create mode 100644 src/app/ui/dock.h create mode 100644 src/app/ui/dockable.h create mode 100644 src/app/ui/layout_selector.cpp create mode 100644 src/app/ui/layout_selector.h diff --git a/data/widgets/main_window.xml b/data/widgets/main_window.xml deleted file mode 100644 index 9c3098dd9..000000000 --- a/data/widgets/main_window.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index cdb2acd4d..46d8b38fb 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -607,6 +607,7 @@ target_sources(app-lib PRIVATE ui/context_bar.cpp ui/dithering_selector.cpp ui/doc_view.cpp + ui/dock.cpp ui/drop_down_button.cpp ui/dynamics_popup.cpp ui/editor/brush_preview.cpp @@ -651,6 +652,7 @@ target_sources(app-lib PRIVATE ui/input_chain.cpp ui/keyboard_shortcuts.cpp ui/layer_frame_comboboxes.cpp + ui/layout_selector.cpp ui/main_menu_bar.cpp ui/main_window.cpp ui/mini_help_button.cpp diff --git a/src/app/ui/color_bar.cpp b/src/app/ui/color_bar.cpp index 64008b002..f48b0a567 100644 --- a/src/app/ui/color_bar.cpp +++ b/src/app/ui/color_bar.cpp @@ -138,8 +138,8 @@ void ColorBar::ScrollableView::onInitTheme(InitThemeEvent& ev) ColorBar* ColorBar::m_instance = NULL; -ColorBar::ColorBar(int align, TooltipManager* tooltipManager) - : Box(align) +ColorBar::ColorBar(TooltipManager* tooltipManager) + : Box(VERTICAL) , m_editPal(1) , m_buttons(int(PalButton::MAX)) , m_tilesButton(1) diff --git a/src/app/ui/color_bar.h b/src/app/ui/color_bar.h index 9895db55b..799631fd2 100644 --- a/src/app/ui/color_bar.h +++ b/src/app/ui/color_bar.h @@ -17,6 +17,7 @@ #include "app/tileset_mode.h" #include "app/ui/button_set.h" #include "app/ui/color_button.h" +#include "app/ui/dockable.h" #include "app/ui/input_chain_element.h" #include "app/ui/palette_view.h" #include "app/ui/tile_button.h" @@ -50,7 +51,8 @@ class ColorBar : public ui::Box, public PaletteViewDelegate, public ContextObserver, public DocObserver, - public InputChainElement { + public InputChainElement, + public Dockable { static ColorBar* m_instance; public: @@ -65,7 +67,7 @@ public: static ColorBar* instance() { return m_instance; } - ColorBar(int align, ui::TooltipManager* tooltipManager); + ColorBar(ui::TooltipManager* tooltipManager); ~ColorBar(); void setPixelFormat(doc::PixelFormat pixelFormat); @@ -123,6 +125,13 @@ public: bool onClear(Context* ctx) override; void onCancel(Context* ctx) override; + // Dockable impl + int dockableAt() const override + { + // TODO split the ColorBar in different dockable widgets + return ui::LEFT | ui::RIGHT | ui::EXPANSIVE; + } + obs::signal ChangeSelection; protected: diff --git a/src/app/ui/context_bar.h b/src/app/ui/context_bar.h index 1bb0d3496..554ebde17 100644 --- a/src/app/ui/context_bar.h +++ b/src/app/ui/context_bar.h @@ -17,6 +17,7 @@ #include "app/tools/tool_loop_modifiers.h" #include "app/ui/context_bar_observer.h" #include "app/ui/doc_observer_widget.h" +#include "app/ui/dockable.h" #include "app/ui/font_entry.h" #include "doc/brush.h" #include "obs/connection.h" @@ -60,7 +61,8 @@ class Transformation; class ContextBar : public DocObserverWidget, public obs::observable, - public tools::ActiveToolObserver { + public tools::ActiveToolObserver, + public Dockable { public: ContextBar(ui::TooltipManager* tooltipManager, ColorBar* colorBar); ~ContextBar(); @@ -99,6 +101,9 @@ public: // For freehand with dynamics const tools::DynamicsOptions& getDynamics() const; + // Dockable impl + int dockableAt() const override { return ui::TOP | ui::BOTTOM; } + // Signals obs::signal BrushChange; obs::signal FontChange; diff --git a/src/app/ui/dock.cpp b/src/app/ui/dock.cpp new file mode 100644 index 000000000..de5fdde15 --- /dev/null +++ b/src/app/ui/dock.cpp @@ -0,0 +1,476 @@ +// Aseprite +// Copyright (C) 2021 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "app/ui/dock.h" + +#include "app/ui/dockable.h" +#include "app/ui/skin/skin_theme.h" +#include "ui/cursor_type.h" +#include "ui/message.h" +#include "ui/paint_event.h" +#include "ui/resize_event.h" +#include "ui/scale.h" +#include "ui/size_hint_event.h" +#include "ui/system.h" +#include "ui/widget.h" + +namespace app { + +using namespace app::skin; +using namespace ui; + +namespace { + +enum { kTopIndex, kBottomIndex, kLeftIndex, kRightIndex, kCenterIndex }; + +int side_index(int side) +{ + switch (side) { + case ui::TOP: return kTopIndex; + case ui::BOTTOM: return kBottomIndex; + case ui::LEFT: return kLeftIndex; + case ui::RIGHT: return kRightIndex; + } + return kCenterIndex; // ui::CENTER +} + +} // anonymous namespace + +void DockTabs::onSizeHint(ui::SizeHintEvent& ev) +{ + gfx::Size sz; + for (auto child : children()) { + if (child->isVisible()) + sz |= child->sizeHint(); + } + sz.h += textHeight(); + ev.setSizeHint(sz); +} + +void DockTabs::onResize(ui::ResizeEvent& ev) +{ + auto bounds = ev.bounds(); + setBoundsQuietly(bounds); + bounds = childrenBounds(); + bounds.y += textHeight(); + bounds.h -= textHeight(); + + for (auto child : children()) { + child->setBounds(bounds); + } +} + +void DockTabs::onPaint(ui::PaintEvent& ev) +{ + Graphics* g = ev.graphics(); + g->fillRect(gfx::rgba(0, 0, 255), clientBounds()); +} + +Dock::Dock() +{ + for (int i = 0; i < kSides; ++i) { + m_sides[i] = nullptr; + m_aligns[i] = 0; + m_sizes[i] = gfx::Size(0, 0); + } + + InitTheme.connect([this] { + if (auto p = parent()) + setBgColor(p->bgColor()); + }); + initTheme(); +} + +void Dock::reset() +{ + for (int i = 0; i < kSides; ++i) { + auto child = m_sides[i]; + if (!child) + continue; + else if (auto subdock = dynamic_cast(child)) { + subdock->reset(); + } + else if (auto tabs = dynamic_cast(child)) { + for (auto child2 : tabs->children()) { + if (auto subdock2 = dynamic_cast(child2)) + subdock2->reset(); + } + } + m_sides[i] = nullptr; + } + removeAllChildren(); +} + +void Dock::dock(int side, ui::Widget* widget, const gfx::Size& prefSize) +{ + ASSERT(widget); + + const int i = side_index(side); + if (!m_sides[i]) { + setSide(i, widget); + addChild(widget); + + if (prefSize != gfx::Size(0, 0)) + m_sizes[i] = prefSize; + } + else if (auto subdock = dynamic_cast(m_sides[i])) { + subdock->dock(CENTER, widget, prefSize); + } + else if (auto tabs = dynamic_cast(m_sides[i])) { + tabs->addChild(widget); + } + // If this side already contains a widget, we create a DockTabs in + // this side. + else { + auto oldWidget = m_sides[i]; + auto newTabs = new DockTabs; + replaceChild(oldWidget, newTabs); + newTabs->addChild(oldWidget); + newTabs->addChild(widget); + setSide(i, newTabs); + } +} + +void Dock::dockRelativeTo(ui::Widget* relative, + int side, + ui::Widget* widget, + const gfx::Size& prefSize) +{ + ASSERT(relative); + + Widget* parent = relative->parent(); + ASSERT(parent); + + Dock* subdock = new Dock; + parent->replaceChild(relative, subdock); + subdock->dock(CENTER, relative); + subdock->dock(side, widget, prefSize); + + // Fix the m_sides item if the parent is a Dock + if (auto relativeDock = dynamic_cast(parent)) { + for (int i = 0; i < kSides; ++i) { + if (relativeDock->m_sides[i] == relative) { + relativeDock->setSide(i, subdock); + break; + } + } + } +} + +void Dock::undock(Widget* widget) +{ + Widget* parent = widget->parent(); + if (!parent) + return; // Already undocked + + if (auto parentDock = dynamic_cast(parent)) { + parentDock->removeChild(widget); + + for (int i = 0; i < kSides; ++i) { + if (parentDock->m_sides[i] == widget) { + parentDock->setSide(i, nullptr); + break; + } + } + + if (parentDock != this && parentDock->children().empty()) { + undock(parentDock); + } + } + else if (auto parentTabs = dynamic_cast(parent)) { + parentTabs->removeChild(widget); + + if (parentTabs->children().empty()) { + undock(parentTabs); + } + } + else { + parent->removeChild(widget); + } +} + +Dock* Dock::subdock(int side) +{ + int i = side_index(side); + if (auto subdock = dynamic_cast(m_sides[i])) + return subdock; + + auto oldWidget = m_sides[i]; + auto newSubdock = new Dock; + setSide(i, newSubdock); + + if (oldWidget) { + replaceChild(oldWidget, newSubdock); + newSubdock->dock(CENTER, oldWidget); + } + else + addChild(newSubdock); + + return newSubdock; +} + +void Dock::onSizeHint(ui::SizeHintEvent& ev) +{ + gfx::Size sz = border().size(); + + if (m_sides[kLeftIndex]) + sz.w += m_sides[kLeftIndex]->sizeHint().w + childSpacing(); + if (m_sides[kRightIndex]) + sz.w += m_sides[kRightIndex]->sizeHint().w + childSpacing(); + if (m_sides[kTopIndex]) + sz.h += m_sides[kTopIndex]->sizeHint().h + childSpacing(); + if (m_sides[kBottomIndex]) + sz.h += m_sides[kBottomIndex]->sizeHint().h + childSpacing(); + if (m_sides[kCenterIndex]) { + sz += m_sides[kCenterIndex]->sizeHint(); + } + + ev.setSizeHint(sz); +} + +void Dock::onResize(ui::ResizeEvent& ev) +{ + auto bounds = ev.bounds(); + setBoundsQuietly(bounds); + bounds = childrenBounds(); + + updateDockVisibility(); + + forEachSide(bounds, + [bounds](ui::Widget* widget, + const gfx::Rect& widgetBounds, + const gfx::Rect& separator, + const int index) { widget->setBounds(widgetBounds); }); +} + +void Dock::onPaint(ui::PaintEvent& ev) +{ + Graphics* g = ev.graphics(); + g->fillRect(bgColor(), clientBounds()); +} + +void Dock::onInitTheme(ui::InitThemeEvent& ev) +{ + Widget::onInitTheme(ev); + setBorder(gfx::Border(0)); + setChildSpacing(4 * ui::guiscale()); +} + +bool Dock::onProcessMessage(ui::Message* msg) +{ + switch (msg->type()) { + case kMouseDownMessage: { + const gfx::Point pos = static_cast(msg)->position(); + + m_capturedSide = -1; + forEachSide(childrenBounds(), + [this, pos](ui::Widget* widget, + const gfx::Rect& widgetBounds, + const gfx::Rect& separator, + const int index) { + if (separator.contains(pos)) { + m_capturedWidget = widget; + m_capturedSide = index; + m_startSize = m_sizes[index]; + m_startPos = pos; + } + }); + + if (m_capturedSide >= 0) { + captureMouse(); + return true; + } + break; + } + + case kMouseMoveMessage: { + if (hasCapture()) { + if (m_capturedSide >= 0) { + const gfx::Point pos = static_cast(msg)->position(); + gfx::Size& sz = m_sizes[m_capturedSide]; + + switch (m_capturedSide) { + case kTopIndex: sz.h = (m_startSize.h + pos.y - m_startPos.y); break; + case kBottomIndex: sz.h = (m_startSize.h - pos.y + m_startPos.y); break; + case kLeftIndex: sz.w = (m_startSize.w + pos.x - m_startPos.x); break; + case kRightIndex: sz.w = (m_startSize.w - pos.x + m_startPos.x); break; + } + + layout(); + } + } + break; + } + + case kMouseUpMessage: { + if (hasCapture()) { + releaseMouse(); + } + break; + } + + case kSetCursorMessage: { + const gfx::Point pos = static_cast(msg)->position(); + ui::CursorType cursor = ui::kArrowCursor; + forEachSide(childrenBounds(), + [pos, &cursor](ui::Widget* widget, + const gfx::Rect& widgetBounds, + const gfx::Rect& separator, + const int index) { + if (separator.contains(pos)) { + if (index == kTopIndex || index == kBottomIndex) { + cursor = ui::kSizeNSCursor; + } + else if (index == kLeftIndex || index == kRightIndex) { + cursor = ui::kSizeWECursor; + } + } + }); + ui::set_mouse_cursor(cursor); + return true; + } + } + return Widget::onProcessMessage(msg); +} + +void Dock::setSide(const int i, Widget* newWidget) +{ + m_sides[i] = newWidget; + m_aligns[i] = calcAlign(i); + + if (newWidget) { + m_sizes[i] = newWidget->sizeHint(); + } +} + +int Dock::calcAlign(const int i) +{ + Widget* widget = m_sides[i]; + int align = 0; + if (!widget) { + // Do nothing + } + else if (auto subdock = dynamic_cast(widget)) { + align = subdock->calcAlign(i); + } + else if (auto tabs = dynamic_cast(widget)) { + for (auto child : tabs->children()) { + if (auto subdock2 = dynamic_cast(widget)) + align |= subdock2->calcAlign(i); + else if (auto dockable = dynamic_cast(child)) { + align = dockable->dockableAt(); + } + } + } + else if (auto dockable2 = dynamic_cast(widget)) { + align = dockable2->dockableAt(); + } + return align; +} + +void Dock::updateDockVisibility() +{ + bool visible = false; + setVisible(true); + for (int i = 0; i < kSides; ++i) { + Widget* widget = m_sides[i]; + if (!widget) + continue; + + if (auto subdock = dynamic_cast(widget)) { + subdock->updateDockVisibility(); + } + else if (auto tabs = dynamic_cast(widget)) { + bool visible2 = false; + for (auto child : tabs->children()) { + if (auto subdock2 = dynamic_cast(widget)) { + subdock2->updateDockVisibility(); + } + if (child->isVisible()) { + visible2 = true; + } + } + tabs->setVisible(visible2); + if (visible2) + visible = true; + } + + if (widget->isVisible()) { + visible = true; + } + } + setVisible(visible); +} + +void Dock::forEachSide(gfx::Rect bounds, + std::function f) +{ + for (int i = 0; i < kSides; ++i) { + auto widget = m_sides[i]; + if (!widget || !widget->isVisible()) { + continue; + } + + int spacing = (m_aligns[i] & EXPANSIVE ? childSpacing() : 0); + + const gfx::Size sz = (m_aligns[i] & EXPANSIVE ? m_sizes[i] : widget->sizeHint()); + gfx::Rect rc, separator; + switch (i) { + case kTopIndex: + rc = gfx::Rect(bounds.x, bounds.y, bounds.w, sz.h); + bounds.y += rc.h; + bounds.h -= rc.h; + + if (spacing > 0) { + separator = gfx::Rect(bounds.x, bounds.y, bounds.w, spacing); + bounds.y += spacing; + bounds.h -= spacing; + } + break; + case kBottomIndex: + rc = gfx::Rect(bounds.x, bounds.y2() - sz.h, bounds.w, sz.h); + bounds.h -= rc.h; + + if (spacing > 0) { + separator = gfx::Rect(bounds.x, bounds.y2() - spacing, bounds.w, spacing); + bounds.h -= spacing; + } + break; + case kLeftIndex: + rc = gfx::Rect(bounds.x, bounds.y, sz.w, bounds.h); + bounds.x += rc.w; + bounds.w -= rc.w; + + if (spacing > 0) { + separator = gfx::Rect(bounds.x, bounds.y, spacing, bounds.h); + bounds.x += spacing; + bounds.w -= spacing; + } + break; + case kRightIndex: + rc = gfx::Rect(bounds.x2() - sz.w, bounds.y, sz.w, bounds.h); + bounds.w -= rc.w; + + if (spacing > 0) { + separator = gfx::Rect(bounds.x2() - spacing, bounds.y, spacing, bounds.h); + bounds.w -= spacing; + } + break; + case kCenterIndex: rc = bounds; break; + } + + f(widget, rc, separator, i); + } +} + +} // namespace app diff --git a/src/app/ui/dock.h b/src/app/ui/dock.h new file mode 100644 index 000000000..231b45dd3 --- /dev/null +++ b/src/app/ui/dock.h @@ -0,0 +1,86 @@ +// Aseprite +// Copyright (C) 2021 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_UI_DOCK_H_INCLUDED +#define APP_UI_DOCK_H_INCLUDED +#pragma once + +#include "gfx/rect.h" +#include "gfx/size.h" +#include "ui/widget.h" + +#include +#include +#include +#include + +namespace app { + +class DockTabs : public ui::Widget { +public: +protected: + void onSizeHint(ui::SizeHintEvent& ev) override; + void onResize(ui::ResizeEvent& ev) override; + void onPaint(ui::PaintEvent& ev) override; +}; + +class Dock : public ui::Widget { +public: + static constexpr const int kSides = 5; + + Dock(); + + void reset(); + + // side = ui::LEFT, or ui::RIGHT, etc. + void dock(int side, ui::Widget* widget, const gfx::Size& prefSize = gfx::Size()); + + void dockRelativeTo(ui::Widget* relative, + int side, + ui::Widget* widget, + const gfx::Size& prefSize = gfx::Size()); + + void undock(ui::Widget* widget); + + Dock* subdock(int side); + + Dock* top() { return subdock(ui::TOP); } + Dock* bottom() { return subdock(ui::BOTTOM); } + Dock* left() { return subdock(ui::LEFT); } + Dock* right() { return subdock(ui::RIGHT); } + Dock* center() { return subdock(ui::CENTER); } + +protected: + void onSizeHint(ui::SizeHintEvent& ev) override; + void onResize(ui::ResizeEvent& ev) override; + void onPaint(ui::PaintEvent& ev) override; + void onInitTheme(ui::InitThemeEvent& ev) override; + bool onProcessMessage(ui::Message* msg) override; + +private: + void setSide(const int i, ui::Widget* newWidget); + int calcAlign(const int i); + void updateDockVisibility(); + void forEachSide(gfx::Rect bounds, + std::function f); + + std::array m_sides; + std::array m_aligns; + std::array m_sizes; + + // Used to drag-and-drop sides. + ui::Widget* m_capturedWidget = nullptr; + int m_capturedSide; + gfx::Size m_startSize; + gfx::Point m_startPos; +}; + +} // namespace app + +#endif diff --git a/src/app/ui/dockable.h b/src/app/ui/dockable.h new file mode 100644 index 000000000..5b1eaf186 --- /dev/null +++ b/src/app/ui/dockable.h @@ -0,0 +1,33 @@ +// Aseprite +// Copyright (C) 2021 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_UI_DOCKABLE_H_INCLUDED +#define APP_UI_DOCKABLE_H_INCLUDED +#pragma once + +#include "ui/base.h" + +namespace app { + +class Dockable { +public: + virtual ~Dockable() {} + + // LEFT = can be docked at the left side + // TOP = can be docked at the top + // RIGHT = can be docked at the right side + // BOTTOM = can be docked at the bottom + // CENTER = can be docked at the center + // EXPANSIVE = can be resized (e.g. add a splitter when docked at sides) + virtual int dockableAt() const + { + return ui::LEFT | ui::TOP | ui::RIGHT | ui::BOTTOM | ui::CENTER | ui::EXPANSIVE; + } +}; + +} // namespace app + +#endif diff --git a/src/app/ui/layout_selector.cpp b/src/app/ui/layout_selector.cpp new file mode 100644 index 000000000..16e8ed0e7 --- /dev/null +++ b/src/app/ui/layout_selector.cpp @@ -0,0 +1,134 @@ +// Aseprite +// Copyright (C) 2021 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "app/ui/layout_selector.h" + +#include "app/app.h" +#include "app/ui/main_window.h" +#include "app/ui/separator_in_view.h" +#include "app/ui/skin/skin_theme.h" +#include "ui/listitem.h" +#include "ui/window.h" + +#define ANI_TICKS 5 + +namespace app { + +using namespace app::skin; +using namespace ui; + +namespace { + +enum class LayoutId { DEFAULT, DEFAULT_MIRROR, CUSTOMIZE }; + +class LayoutItem : public ListItem { +public: + LayoutItem(const LayoutId id, const std::string& text) : ListItem(text), m_id(id) {} + + void select() + { + MainWindow* win = App::instance()->mainWindow(); + + switch (m_id) { + case LayoutId::DEFAULT: win->setDefaultLayout(); break; + case LayoutId::DEFAULT_MIRROR: win->setDefaultMirrorLayout(); break; + case LayoutId::CUSTOMIZE: + // TODO + break; + } + } + +private: + LayoutId m_id; +}; + +}; // namespace + +void LayoutSelector::LayoutComboBox::onChange() +{ + if (auto item = dynamic_cast(getSelectedItem())) { + item->select(); + } +} + +LayoutSelector::LayoutSelector() : m_button("", "\xc3\xb7") +{ + m_button.Click.connect([this]() { switchSelector(); }); + + m_comboBox.setVisible(false); + + addChild(&m_comboBox); + addChild(&m_button); +} + +LayoutSelector::~LayoutSelector() +{ + stopAnimation(); +} + +void LayoutSelector::onAnimationFrame() +{ + switch (animation()) { + case ANI_NONE: break; + case ANI_EXPANDING: + case ANI_COLLAPSING: { + const double t = animationTime(); + m_comboBox.setSizeHint(gfx::Size((1.0 - t) * m_startSize.w + t * m_endSize.w, + (1.0 - t) * m_startSize.h + t * m_endSize.h)); + break; + } + } + + if (auto win = window()) + win->layout(); +} + +void LayoutSelector::onAnimationStop(int animation) +{ + switch (animation) { + case ANI_EXPANDING: m_comboBox.setSizeHint(m_endSize); break; + case ANI_COLLAPSING: + m_comboBox.setVisible(false); + m_comboBox.setSizeHint(m_endSize); + break; + } + + if (auto win = window()) + win->layout(); +} + +void LayoutSelector::switchSelector() +{ + bool expand; + if (!m_comboBox.isVisible()) { + expand = true; + + // Create the combobox for first time + if (m_comboBox.getItemCount() == 0) { + m_comboBox.addItem(new LayoutItem(LayoutId::DEFAULT, "Default")); + m_comboBox.addItem(new LayoutItem(LayoutId::DEFAULT_MIRROR, "Default / Mirror")); + } + + m_comboBox.setVisible(true); + m_comboBox.resetSizeHint(); + m_startSize = gfx::Size(0, 0); + m_endSize = m_comboBox.sizeHint(); + } + else { + expand = false; + m_startSize = m_comboBox.bounds().size(); + m_endSize = gfx::Size(0, 0); + } + + m_comboBox.setSizeHint(m_startSize); + startAnimation((expand ? ANI_EXPANDING : ANI_COLLAPSING), ANI_TICKS); +} + +} // namespace app diff --git a/src/app/ui/layout_selector.h b/src/app/ui/layout_selector.h new file mode 100644 index 000000000..cedbf8821 --- /dev/null +++ b/src/app/ui/layout_selector.h @@ -0,0 +1,54 @@ +// Aseprite +// Copyright (C) 2021-2022 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_UI_LAYOUT_SELECTOR_H_INCLUDED +#define APP_UI_LAYOUT_SELECTOR_H_INCLUDED +#pragma once + +#include "app/ui/dockable.h" +#include "ui/animated_widget.h" +#include "ui/box.h" +#include "ui/combobox.h" +#include "ui/link_label.h" + +#include + +namespace app { + +class LayoutSelector : public ui::HBox, + public ui::AnimatedWidget, + public Dockable { + enum Ani : int { + ANI_NONE, + ANI_EXPANDING, + ANI_COLLAPSING, + }; + + class LayoutComboBox : public ui::ComboBox { + void onChange() override; + }; + +public: + LayoutSelector(); + ~LayoutSelector(); + + // Dockable impl + int dockableAt() const override { return ui::TOP | ui::BOTTOM; } + +private: + void onAnimationFrame() override; + void onAnimationStop(int animation) override; + void switchSelector(); + + LayoutComboBox m_comboBox; + ui::LinkLabel m_button; + gfx::Size m_startSize; + gfx::Size m_endSize; +}; + +} // namespace app + +#endif diff --git a/src/app/ui/main_menu_bar.h b/src/app/ui/main_menu_bar.h index bfc704bec..407add2fc 100644 --- a/src/app/ui/main_menu_bar.h +++ b/src/app/ui/main_menu_bar.h @@ -9,17 +9,22 @@ #define APP_UI_MAIN_MENU_BAR_H_INCLUDED #pragma once +#include "app/ui/dockable.h" #include "obs/connection.h" #include "ui/menu.h" namespace app { -class MainMenuBar : public ui::MenuBar { +class MainMenuBar : public ui::MenuBar, + public Dockable { public: MainMenuBar(); void reload(); + // Dockable impl + int dockableAt() const override { return ui::TOP | ui::BOTTOM; } + private: obs::scoped_connection m_extKeys; obs::scoped_connection m_extScripts; diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp index 87c81c8d5..c6caa0a18 100644 --- a/src/app/ui/main_window.cpp +++ b/src/app/ui/main_window.cpp @@ -24,9 +24,11 @@ #include "app/ui/color_bar.h" #include "app/ui/context_bar.h" #include "app/ui/doc_view.h" +#include "app/ui/dock.h" #include "app/ui/editor/editor.h" #include "app/ui/editor/editor_view.h" #include "app/ui/home_view.h" +#include "app/ui/layout_selector.h" #include "app/ui/main_menu_bar.h" #include "app/ui/notifications.h" #include "app/ui/preview_editor.h" @@ -83,7 +85,10 @@ public: }; MainWindow::MainWindow() - : m_mode(NormalMode) + : ui::Window(ui::Window::DesktopWindow) + , m_dock(new Dock) + , m_customizableDock(new Dock) + , m_mode(NormalMode) , m_homeView(nullptr) , m_scalePanic(nullptr) , m_browserView(nullptr) @@ -105,8 +110,9 @@ MainWindow::MainWindow() // Refer to https://github.com/aseprite/aseprite/issues/3914 void MainWindow::initialize() { - m_tooltipManager = new TooltipManager(); - m_menuBar = new MainMenuBar(); + m_tooltipManager = new TooltipManager; + m_menuBar = new MainMenuBar; + m_layoutSelector = new LayoutSelector; // Register commands to load menus+shortcuts for these commands Editor::registerCommands(); @@ -123,7 +129,7 @@ void MainWindow::initialize() m_tabsBar = new WorkspaceTabs(this); m_workspace = new Workspace(); m_previewEditor = new PreviewEditorWindow(); - m_colorBar = new ColorBar(colorBarPlaceholder()->align(), m_tooltipManager); + m_colorBar = new ColorBar(m_tooltipManager); m_contextBar = new ContextBar(m_tooltipManager, m_colorBar); // The timeline (AniControls) tooltips will use the keyboard @@ -148,19 +154,24 @@ void MainWindow::initialize() // Add the widgets in the boxes addChild(m_tooltipManager); - menuBarPlaceholder()->addChild(m_menuBar); - menuBarPlaceholder()->addChild(m_notifications); - contextBarPlaceholder()->addChild(m_contextBar); - colorBarPlaceholder()->addChild(m_colorBar); - toolBarPlaceholder()->addChild(m_toolBar); - statusBarPlaceholder()->addChild(m_statusBar); - tabsPlaceholder()->addChild(m_tabsBar); - workspacePlaceholder()->addChild(m_workspace); - timelinePlaceholder()->addChild(m_timeline); + addChild(m_dock); - // Default splitter positions - colorBarSplitter()->setPosition(m_colorBar->sizeHint().w); - timelineSplitter()->setPosition(75); + auto customizableDockPlaceholder = new Widget; + customizableDockPlaceholder->addChild(m_customizableDock); + customizableDockPlaceholder->InitTheme.connect([this] { + auto theme = static_cast(this->theme()); + m_customizableDock->setBgColor(theme->colors.workspace()); + }); + customizableDockPlaceholder->initTheme(); + + m_dock->top()->right()->dock(ui::RIGHT, m_notifications); + m_dock->top()->right()->dock(ui::CENTER, m_layoutSelector); + m_dock->top()->dock(ui::BOTTOM, m_tabsBar); + m_dock->top()->dock(ui::CENTER, m_menuBar); + m_dock->dock(ui::CENTER, customizableDockPlaceholder); + m_dock->dock(ui::BOTTOM, m_statusBar); + + setDefaultLayout(); // Reconfigure workspace when the timeline position is changed. auto& pref = Preferences::instance(); @@ -179,6 +190,9 @@ void MainWindow::initialize() MainWindow::~MainWindow() { + m_dock->reset(); + m_customizableDock->reset(); + delete m_scalePanic; #ifdef ENABLE_SCRIPTING @@ -254,7 +268,7 @@ void MainWindow::showNotification(INotificationDelegate* del) { m_notifications->addLink(del); m_notifications->setVisible(true); - m_notifications->parent()->layout(); + layout(); } void MainWindow::showHomeOnOpen() @@ -360,6 +374,34 @@ void MainWindow::popTimeline() setTimelineVisibility(true); } +void MainWindow::setDefaultLayout() +{ + m_customizableDock->reset(); + m_customizableDock->dock(ui::LEFT, m_colorBar); + m_customizableDock->center()->dock(ui::TOP, m_contextBar); + m_customizableDock->center()->dock(ui::RIGHT, m_toolBar); + m_customizableDock->center()->center()->dock(ui::BOTTOM, + m_timeline, + gfx::Size(64 * guiscale(), 64 * guiscale())); + m_customizableDock->center()->center()->dock(ui::CENTER, m_workspace); + + layout(); +} + +void MainWindow::setDefaultMirrorLayout() +{ + m_customizableDock->reset(); + m_customizableDock->dock(ui::RIGHT, m_colorBar); + m_customizableDock->center()->dock(ui::TOP, m_contextBar); + m_customizableDock->center()->dock(ui::LEFT, m_toolBar); + m_customizableDock->center()->center()->dock(ui::BOTTOM, + m_timeline, + gfx::Size(64 * guiscale(), 64 * guiscale())); + m_customizableDock->center()->center()->dock(ui::CENTER, m_workspace); + + layout(); +} + void MainWindow::dataRecoverySessionsAreReady() { getHomeView()->dataRecoverySessionsAreReady(); @@ -375,24 +417,15 @@ bool MainWindow::onProcessMessage(ui::Message* msg) void MainWindow::onInitTheme(ui::InitThemeEvent& ev) { - app::gen::MainWindow::onInitTheme(ev); + ui::Window::onInitTheme(ev); + noBorderNoChildSpacing(); if (m_previewEditor) m_previewEditor->initTheme(); } -void MainWindow::onSaveLayout(SaveLayoutEvent& ev) -{ - // Invert the timeline splitter position before we save the setting. - if (Preferences::instance().general.timelinePosition() == gen::TimelinePosition::LEFT) { - timelineSplitter()->setPosition(100 - timelineSplitter()->getPosition()); - } - - Window::onSaveLayout(ev); -} - void MainWindow::onResize(ui::ResizeEvent& ev) { - app::gen::MainWindow::onResize(ev); + ui::Window::onResize(ev); os::Window* nativeWindow = (display() ? display()->nativeWindow() : nullptr); if (nativeWindow && nativeWindow->screen()) { @@ -593,16 +626,21 @@ void MainWindow::configureWorkspaceLayout() bool isDoc = (getDocView() != nullptr); if (os::System::instance()->menus() == nullptr || pref.general.showMenuBar()) { - m_menuBar->resetMaxSize(); + if (!m_menuBar->parent()) + m_dock->top()->dock(CENTER, m_menuBar); } else { - m_menuBar->setMaxSize(gfx::Size(0, 0)); + if (m_menuBar->parent()) + m_dock->undock(m_menuBar); } m_menuBar->setVisible(normal); m_notifications->setVisible(normal && m_notifications->hasNotifications()); m_tabsBar->setVisible(normal); - colorBarPlaceholder()->setVisible(normal && isDoc); + + // TODO set visibility of color bar widgets + m_colorBar->setVisible(normal && isDoc); + m_toolBar->setVisible(normal && isDoc); m_statusBar->setVisible(normal); m_contextBar->setVisible(isDoc && (m_mode == NormalMode || m_mode == ContextBarAndTimelineMode)); @@ -610,41 +648,22 @@ void MainWindow::configureWorkspaceLayout() // Configure timeline { auto timelinePosition = pref.general.timelinePosition(); - bool invertWidgets = false; - int align = VERTICAL; + int side = ui::BOTTOM; + + m_customizableDock->undock(m_timeline); + switch (timelinePosition) { - case gen::TimelinePosition::LEFT: - align = HORIZONTAL; - invertWidgets = true; - break; - case gen::TimelinePosition::RIGHT: align = HORIZONTAL; break; - case gen::TimelinePosition::BOTTOM: break; + case gen::TimelinePosition::LEFT: side = ui::LEFT; break; + case gen::TimelinePosition::RIGHT: side = ui::RIGHT; break; + case gen::TimelinePosition::BOTTOM: side = ui::BOTTOM; break; } - timelineSplitter()->setAlign(align); - timelinePlaceholder()->setVisible( - isDoc && (m_mode == NormalMode || m_mode == ContextBarAndTimelineMode) && - pref.general.visibleTimeline()); + m_customizableDock->center()->center()->dock(side, + m_timeline, + gfx::Size(64 * guiscale(), 64 * guiscale())); - bool invertSplitterPos = false; - if (invertWidgets) { - if (timelineSplitter()->firstChild() == workspacePlaceholder() && - timelineSplitter()->lastChild() == timelinePlaceholder()) { - timelineSplitter()->removeChild(workspacePlaceholder()); - timelineSplitter()->addChild(workspacePlaceholder()); - invertSplitterPos = true; - } - } - else { - if (timelineSplitter()->firstChild() == timelinePlaceholder() && - timelineSplitter()->lastChild() == workspacePlaceholder()) { - timelineSplitter()->removeChild(timelinePlaceholder()); - timelineSplitter()->addChild(timelinePlaceholder()); - invertSplitterPos = true; - } - } - if (invertSplitterPos) - timelineSplitter()->setPosition(100 - timelineSplitter()->getPosition()); + m_timeline->setVisible(isDoc && (m_mode == NormalMode || m_mode == ContextBarAndTimelineMode) && + pref.general.visibleTimeline()); } if (m_contextBar->isVisible()) { @@ -652,7 +671,6 @@ void MainWindow::configureWorkspaceLayout() } layout(); - invalidate(); } } // namespace app diff --git a/src/app/ui/main_window.h b/src/app/ui/main_window.h index 118c52db4..39be6ab90 100644 --- a/src/app/ui/main_window.h +++ b/src/app/ui/main_window.h @@ -12,7 +12,7 @@ #include "app/ui/tabs.h" #include "ui/window.h" -#include "main_window.xml.h" +#include namespace ui { class Splitter; @@ -30,13 +30,16 @@ class ColorBar; class ContextBar; class DevConsoleView; class DocView; +class Dock; class HomeView; class INotificationDelegate; class MainMenuBar; +class LayoutSelector; class Notifications; class PreviewEditorWindow; class StatusBar; class Timeline; +class ToolBar; class Workspace; class WorkspaceTabs; @@ -44,7 +47,7 @@ namespace crash { class DataRecovery; } -class MainWindow : public app::gen::MainWindow, +class MainWindow : public ui::Window, public TabsDelegate { public: enum Mode { NormalMode, ContextBarAndTimelineMode, EditorOnlyMode }; @@ -83,6 +86,9 @@ public: void setTimelineVisibility(bool visible); void popTimeline(); + void setDefaultLayout(); + void setDefaultMirrorLayout(); + // When crash::DataRecovery finish to search for sessions, this // function is called. void dataRecoverySessionsAreReady(); @@ -109,7 +115,6 @@ public: protected: bool onProcessMessage(ui::Message* msg) override; void onInitTheme(ui::InitThemeEvent& ev) override; - void onSaveLayout(ui::SaveLayoutEvent& ev) override; void onResize(ui::ResizeEvent& ev) override; void onBeforeViewChange(); void onActiveViewChange(); @@ -123,11 +128,14 @@ private: void configureWorkspaceLayout(); ui::TooltipManager* m_tooltipManager; + Dock* m_dock; + Dock* m_customizableDock; MainMenuBar* m_menuBar; + LayoutSelector* m_layoutSelector; StatusBar* m_statusBar; ColorBar* m_colorBar; ContextBar* m_contextBar; - ui::Widget* m_toolBar; + ToolBar* m_toolBar; WorkspaceTabs* m_tabsBar; Mode m_mode; Timeline* m_timeline; diff --git a/src/app/ui/notifications.h b/src/app/ui/notifications.h index ecd79890e..d39e27bda 100644 --- a/src/app/ui/notifications.h +++ b/src/app/ui/notifications.h @@ -9,6 +9,7 @@ #define APP_UI_NOTIFICATIONS_H_INCLUDED #pragma once +#include "app/ui/dockable.h" #include "ui/button.h" #include "ui/menu.h" @@ -19,13 +20,17 @@ class Style; namespace app { class INotificationDelegate; -class Notifications : public ui::Button { +class Notifications : public ui::Button, + public Dockable { public: Notifications(); void addLink(INotificationDelegate* del); bool hasNotifications() const { return m_popup.hasChildren(); } + // Dockable impl + int dockableAt() const override { return ui::TOP | ui::BOTTOM | ui::LEFT | ui::RIGHT; } + protected: void onSizeHint(ui::SizeHintEvent& ev) override; void onPaint(ui::PaintEvent& ev) override; diff --git a/src/app/ui/status_bar.h b/src/app/ui/status_bar.h index 90075fe72..7a4308642 100644 --- a/src/app/ui/status_bar.h +++ b/src/app/ui/status_bar.h @@ -13,6 +13,7 @@ #include "app/context_observer.h" #include "app/tools/active_tool_observer.h" #include "app/ui/doc_observer_widget.h" +#include "app/ui/dockable.h" #include "base/time.h" #include "doc/tile.h" #include "ui/base.h" @@ -44,7 +45,8 @@ class Tool; } class StatusBar : public DocObserverWidget, - public tools::ActiveToolObserver { + public tools::ActiveToolObserver, + public Dockable { static StatusBar* m_instance; public: @@ -72,6 +74,9 @@ public: void showBackupIcon(BackupIcon icon); + // Dockable impl + int dockableAt() const override { return ui::TOP | ui::BOTTOM; } + protected: void onInitTheme(ui::InitThemeEvent& ev) override; void onResize(ui::ResizeEvent& ev) override; diff --git a/src/app/ui/tabs.h b/src/app/ui/tabs.h index d24887335..5a6ee743d 100644 --- a/src/app/ui/tabs.h +++ b/src/app/ui/tabs.h @@ -9,6 +9,7 @@ #define APP_UI_TABS_H_INCLUDED #pragma once +#include "app/ui/dockable.h" #include "base/ref.h" #include "text/fwd.h" #include "ui/animated_widget.h" @@ -118,7 +119,8 @@ public: // Tabs control. Used to show opened documents. class Tabs : public ui::Widget, - public ui::AnimatedWidget { + public ui::AnimatedWidget, + public Dockable { struct Tab { TabView* view; std::string text; @@ -181,6 +183,9 @@ public: void removeDropViewPreview(); int getDropTabIndex() const { return m_dropNewIndex; } + // Dockable impl + int dockableAt() const override { return ui::TOP | ui::BOTTOM; } + protected: bool onProcessMessage(ui::Message* msg) override; void onInitTheme(ui::InitThemeEvent& ev) override; diff --git a/src/app/ui/timeline/timeline.h b/src/app/ui/timeline/timeline.h index be231cd3d..0dd02538f 100644 --- a/src/app/ui/timeline/timeline.h +++ b/src/app/ui/timeline/timeline.h @@ -13,6 +13,7 @@ #include "app/docs_observer.h" #include "app/loop_tag.h" #include "app/pref/preferences.h" +#include "app/ui/dockable.h" #include "app/ui/editor/editor_observer.h" #include "app/ui/input_chain_element.h" #include "app/ui/timeline/ani_controls.h" @@ -72,7 +73,8 @@ class Timeline : public ui::Widget, public DocObserver, public EditorObserver, public InputChainElement, - public TagProvider { + public TagProvider, + public Dockable { public: using Range = view::Range; using RealRange = view::RealRange; @@ -152,6 +154,12 @@ public: void clearAndInvalidateRange(); + // Dockable impl + int dockableAt() const override + { + return ui::TOP | ui::BOTTOM | ui::LEFT | ui::RIGHT | ui::EXPANSIVE; + } + protected: bool onProcessMessage(ui::Message* msg) override; void onInitTheme(ui::InitThemeEvent& ev) override; diff --git a/src/app/ui/toolbar.h b/src/app/ui/toolbar.h index 9b1f988ea..bcfe61b89 100644 --- a/src/app/ui/toolbar.h +++ b/src/app/ui/toolbar.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -9,6 +10,7 @@ #pragma once #include "app/tools/active_tool_observer.h" +#include "app/ui/dockable.h" #include "app/ui/skin/skin_part.h" #include "gfx/point.h" #include "obs/connection.h" @@ -31,6 +33,7 @@ class ToolGroup; // Class to show selected tools for each tool (vertically) class ToolBar : public ui::Widget, + public Dockable, public tools::ActiveToolObserver { static ToolBar* m_instance; @@ -51,6 +54,14 @@ public: void openTipWindow(tools::ToolGroup* toolGroup, tools::Tool* tool); void closeTipWindow(); + // Dockable impl + int dockableAt() const override + { + // TODO add future support to dock the tool bar at the + // top/bottom sides + return ui::LEFT | ui::RIGHT; + } + protected: bool onProcessMessage(ui::Message* msg) override; void onSizeHint(ui::SizeHintEvent& ev) override; From a8069287dbea9e71daec409520b7d384778667f4 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 28 Mar 2022 16:02:27 -0300 Subject: [PATCH 02/20] Fix memory leaks in MainWindow Temporary/created subdocks must be deleted automatically, and children that are not part of the window hierarchy must be deleted explicitly now (using some std::unique_ptrs). --- src/app/ui/dock.cpp | 17 +++-- src/app/ui/dock.h | 5 +- src/app/ui/main_window.cpp | 140 ++++++++++++++++++------------------- src/app/ui/main_window.h | 45 ++++++------ 4 files changed, 108 insertions(+), 99 deletions(-) diff --git a/src/app/ui/dock.cpp b/src/app/ui/dock.cpp index de5fdde15..1cffa9f10 100644 --- a/src/app/ui/dock.cpp +++ b/src/app/ui/dock.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2021 Igara Studio S.A. +// Copyright (C) 2021-2022 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -88,19 +88,24 @@ Dock::Dock() initTheme(); } -void Dock::reset() +void Dock::resetDocks() { for (int i = 0; i < kSides; ++i) { auto child = m_sides[i]; if (!child) continue; else if (auto subdock = dynamic_cast(child)) { - subdock->reset(); + subdock->resetDocks(); + if (subdock->m_autoDelete) + delete subdock; } else if (auto tabs = dynamic_cast(child)) { for (auto child2 : tabs->children()) { - if (auto subdock2 = dynamic_cast(child2)) - subdock2->reset(); + if (auto subdock2 = dynamic_cast(child2)) { + subdock2->resetDocks(); + if (subdock2->m_autoDelete) + delete subdock2; + } } } m_sides[i] = nullptr; @@ -149,6 +154,7 @@ void Dock::dockRelativeTo(ui::Widget* relative, ASSERT(parent); Dock* subdock = new Dock; + subdock->m_autoDelete = true; parent->replaceChild(relative, subdock); subdock->dock(CENTER, relative); subdock->dock(side, widget, prefSize); @@ -204,6 +210,7 @@ Dock* Dock::subdock(int side) auto oldWidget = m_sides[i]; auto newSubdock = new Dock; + newSubdock->m_autoDelete = true; setSide(i, newSubdock); if (oldWidget) { diff --git a/src/app/ui/dock.h b/src/app/ui/dock.h index 231b45dd3..821e780b8 100644 --- a/src/app/ui/dock.h +++ b/src/app/ui/dock.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2021 Igara Studio S.A. +// Copyright (C) 2021-2022 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -33,7 +33,7 @@ public: Dock(); - void reset(); + void resetDocks(); // side = ui::LEFT, or ui::RIGHT, etc. void dock(int side, ui::Widget* widget, const gfx::Size& prefSize = gfx::Size()); @@ -73,6 +73,7 @@ private: std::array m_sides; std::array m_aligns; std::array m_sizes; + bool m_autoDelete = false; // Used to drag-and-drop sides. ui::Widget* m_capturedWidget = nullptr; diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp index c6caa0a18..d9197273b 100644 --- a/src/app/ui/main_window.cpp +++ b/src/app/ui/main_window.cpp @@ -86,8 +86,10 @@ public: MainWindow::MainWindow() : ui::Window(ui::Window::DesktopWindow) + , m_tooltipManager(new TooltipManager) , m_dock(new Dock) , m_customizableDock(new Dock) + , m_layoutSelector(new LayoutSelector) , m_mode(NormalMode) , m_homeView(nullptr) , m_scalePanic(nullptr) @@ -110,9 +112,8 @@ MainWindow::MainWindow() // Refer to https://github.com/aseprite/aseprite/issues/3914 void MainWindow::initialize() { - m_tooltipManager = new TooltipManager; - m_menuBar = new MainMenuBar; - m_layoutSelector = new LayoutSelector; + m_menuBar = std::make_unique(); + m_layoutSelector = std::make_unique(); // Register commands to load menus+shortcuts for these commands Editor::registerCommands(); @@ -123,20 +124,20 @@ void MainWindow::initialize() // Setup the main menubar m_menuBar->setMenu(AppMenus::instance()->getRootMenu()); - m_notifications = new Notifications(); - m_statusBar = new StatusBar(m_tooltipManager); - m_toolBar = new ToolBar(); - m_tabsBar = new WorkspaceTabs(this); - m_workspace = new Workspace(); - m_previewEditor = new PreviewEditorWindow(); - m_colorBar = new ColorBar(m_tooltipManager); - m_contextBar = new ContextBar(m_tooltipManager, m_colorBar); + m_notifications = std::make_unique(); + m_statusBar = std::make_unique(m_tooltipManager); + m_toolBar = std::make_unique(); + m_tabsBar = std::make_unique(this); + m_workspace = std::make_unique(); + m_previewEditor = std::make_unique(); + m_colorBar = std::make_unique(m_tooltipManager); + m_contextBar = std::make_unique(m_tooltipManager, m_colorBar.get()); // The timeline (AniControls) tooltips will use the keyboard // shortcuts loaded above. - m_timeline = new Timeline(m_tooltipManager); + m_timeline = std::make_unique(m_tooltipManager); - m_workspace->setTabsBar(m_tabsBar); + m_workspace->setTabsBar(m_tabsBar.get()); m_workspace->BeforeViewChanged.connect(&MainWindow::onBeforeViewChange, this); m_workspace->ActiveViewChanged.connect(&MainWindow::onActiveViewChange, this); @@ -156,20 +157,20 @@ void MainWindow::initialize() addChild(m_tooltipManager); addChild(m_dock); - auto customizableDockPlaceholder = new Widget; - customizableDockPlaceholder->addChild(m_customizableDock); - customizableDockPlaceholder->InitTheme.connect([this] { + m_customizableDockPlaceholder = std::make_unique(); + m_customizableDockPlaceholder->addChild(m_customizableDock); + m_customizableDockPlaceholder->InitTheme.connect([this] { auto theme = static_cast(this->theme()); m_customizableDock->setBgColor(theme->colors.workspace()); }); - customizableDockPlaceholder->initTheme(); + m_customizableDockPlaceholder->initTheme(); - m_dock->top()->right()->dock(ui::RIGHT, m_notifications); - m_dock->top()->right()->dock(ui::CENTER, m_layoutSelector); - m_dock->top()->dock(ui::BOTTOM, m_tabsBar); - m_dock->top()->dock(ui::CENTER, m_menuBar); - m_dock->dock(ui::CENTER, customizableDockPlaceholder); - m_dock->dock(ui::BOTTOM, m_statusBar); + m_dock->top()->right()->dock(ui::RIGHT, m_notifications.get()); + m_dock->top()->right()->dock(ui::CENTER, m_layoutSelector.get()); + m_dock->top()->dock(ui::BOTTOM, m_tabsBar.get()); + m_dock->top()->dock(ui::CENTER, m_menuBar.get()); + m_dock->dock(ui::CENTER, m_customizableDockPlaceholder.get()); + m_dock->dock(ui::BOTTOM, m_statusBar.get()); setDefaultLayout(); @@ -190,45 +191,43 @@ void MainWindow::initialize() MainWindow::~MainWindow() { - m_dock->reset(); - m_customizableDock->reset(); + m_dock->resetDocks(); + m_customizableDock->resetDocks(); - delete m_scalePanic; + m_layoutSelector.reset(); + m_scalePanic.reset(); #ifdef ENABLE_SCRIPTING if (m_devConsoleView) { if (m_devConsoleView->parent() && m_workspace) - m_workspace->removeView(m_devConsoleView); - delete m_devConsoleView; + m_workspace->removeView(m_devConsoleView.get()); + m_devConsoleView.reset(); } #endif if (m_browserView) { if (m_browserView->parent() && m_workspace) - m_workspace->removeView(m_browserView); - delete m_browserView; + m_workspace->removeView(m_browserView.get()); + m_browserView.reset(); } if (m_homeView) { if (m_homeView->parent() && m_workspace) - m_workspace->removeView(m_homeView); - delete m_homeView; + m_workspace->removeView(m_homeView.get()); + m_homeView.reset(); } - if (m_contextBar) - delete m_contextBar; - if (m_previewEditor) - delete m_previewEditor; + m_contextBar.reset(); + m_previewEditor.reset(); // Destroy the workspace first so ~Editor can dettach slots from // ColorBar. TODO this is a terrible hack for slot/signal stuff, // connections should be handle in a better/safer way. - if (m_workspace) - delete m_workspace; + m_workspace.reset(); // Remove the root-menu from the menu-bar (because the rootmenu // module should destroy it). if (m_menuBar) - m_menuBar->setMenu(NULL); + m_menuBar->setMenu(nullptr); } void MainWindow::onLanguageChange() @@ -246,8 +245,8 @@ DocView* MainWindow::getDocView() HomeView* MainWindow::getHomeView() { if (!m_homeView) - m_homeView = new HomeView; - return m_homeView; + m_homeView = std::make_unique(); + return m_homeView.get(); } #ifdef ENABLE_UPDATER @@ -284,20 +283,20 @@ void MainWindow::showHomeOnOpen() // Show "Home" tab in the first position, and select it only if // there is no other view selected. - m_workspace->addView(m_homeView, 0); + m_workspace->addView(m_homeView.get(), 0); if (selectedTab) m_tabsBar->selectTab(selectedTab); else - m_tabsBar->selectTab(m_homeView); + m_tabsBar->selectTab(m_homeView.get()); } } void MainWindow::showHome() { if (!getHomeView()->parent()) { - m_workspace->addView(m_homeView, 0); + m_workspace->addView(m_homeView.get(), 0); } - m_tabsBar->selectTab(m_homeView); + m_tabsBar->selectTab(m_homeView.get()); } void MainWindow::showDefaultStatusBar() @@ -312,19 +311,19 @@ void MainWindow::showDefaultStatusBar() bool MainWindow::isHomeSelected() const { - return (m_homeView && m_workspace->activeView() == m_homeView); + return (m_homeView && m_workspace->activeView() == m_homeView.get()); } void MainWindow::showBrowser(const std::string& filename, const std::string& section) { if (!m_browserView) - m_browserView = new BrowserView; + m_browserView = std::make_unique(); m_browserView->loadFile(filename, section); if (!m_browserView->parent()) { - m_workspace->addView(m_browserView); - m_tabsBar->selectTab(m_browserView); + m_workspace->addView(m_browserView.get()); + m_tabsBar->selectTab(m_browserView.get()); } } @@ -332,11 +331,11 @@ void MainWindow::showDevConsole() { #ifdef ENABLE_SCRIPTING if (!m_devConsoleView) - m_devConsoleView = new DevConsoleView; + m_devConsoleView = std::make_unique(); if (!m_devConsoleView->parent()) { - m_workspace->addView(m_devConsoleView); - m_tabsBar->selectTab(m_devConsoleView); + m_workspace->addView(m_devConsoleView.get()); + m_tabsBar->selectTab(m_devConsoleView.get()); } #endif } @@ -376,28 +375,28 @@ void MainWindow::popTimeline() void MainWindow::setDefaultLayout() { - m_customizableDock->reset(); - m_customizableDock->dock(ui::LEFT, m_colorBar); - m_customizableDock->center()->dock(ui::TOP, m_contextBar); - m_customizableDock->center()->dock(ui::RIGHT, m_toolBar); + m_customizableDock->resetDocks(); + m_customizableDock->dock(ui::LEFT, m_colorBar.get()); + m_customizableDock->center()->dock(ui::TOP, m_contextBar.get()); + m_customizableDock->center()->dock(ui::RIGHT, m_toolBar.get()); m_customizableDock->center()->center()->dock(ui::BOTTOM, - m_timeline, + m_timeline.get(), gfx::Size(64 * guiscale(), 64 * guiscale())); - m_customizableDock->center()->center()->dock(ui::CENTER, m_workspace); + m_customizableDock->center()->center()->dock(ui::CENTER, m_workspace.get()); layout(); } void MainWindow::setDefaultMirrorLayout() { - m_customizableDock->reset(); - m_customizableDock->dock(ui::RIGHT, m_colorBar); - m_customizableDock->center()->dock(ui::TOP, m_contextBar); - m_customizableDock->center()->dock(ui::LEFT, m_toolBar); + m_customizableDock->resetDocks(); + m_customizableDock->dock(ui::RIGHT, m_colorBar.get()); + m_customizableDock->center()->dock(ui::TOP, m_contextBar.get()); + m_customizableDock->center()->dock(ui::LEFT, m_toolBar.get()); m_customizableDock->center()->center()->dock(ui::BOTTOM, - m_timeline, + m_timeline.get(), gfx::Size(64 * guiscale(), 64 * guiscale())); - m_customizableDock->center()->center()->dock(ui::CENTER, m_workspace); + m_customizableDock->center()->center()->dock(ui::CENTER, m_workspace.get()); layout(); } @@ -438,7 +437,8 @@ void MainWindow::onResize(ui::ResizeEvent& ev) if ((scale > 2) && (!m_scalePanic)) { const gfx::Size wa = nativeWindow->screen()->workarea().size(); if ((wa.w / scale < 256 || wa.h / scale < 256)) { - showNotification(m_scalePanic = new ScreenScalePanic); + m_scalePanic = std::make_unique(); + showNotification(m_scalePanic.get()); } } } @@ -541,7 +541,7 @@ void MainWindow::onContextMenuTab(Tabs* tabs, TabView* tabView) WorkspaceView* view = dynamic_cast(tabView); ASSERT(view); if (view) - view->onTabPopup(m_workspace); + view->onTabPopup(m_workspace.get()); } void MainWindow::onTabsContainerDoubleClicked(Tabs* tabs) @@ -627,11 +627,11 @@ void MainWindow::configureWorkspaceLayout() if (os::System::instance()->menus() == nullptr || pref.general.showMenuBar()) { if (!m_menuBar->parent()) - m_dock->top()->dock(CENTER, m_menuBar); + m_dock->top()->dock(CENTER, m_menuBar.get()); } else { if (m_menuBar->parent()) - m_dock->undock(m_menuBar); + m_dock->undock(m_menuBar.get()); } m_menuBar->setVisible(normal); @@ -650,7 +650,7 @@ void MainWindow::configureWorkspaceLayout() auto timelinePosition = pref.general.timelinePosition(); int side = ui::BOTTOM; - m_customizableDock->undock(m_timeline); + m_customizableDock->undock(m_timeline.get()); switch (timelinePosition) { case gen::TimelinePosition::LEFT: side = ui::LEFT; break; @@ -659,7 +659,7 @@ void MainWindow::configureWorkspaceLayout() } m_customizableDock->center()->center()->dock(side, - m_timeline, + m_timeline.get(), gfx::Size(64 * guiscale(), 64 * guiscale())); m_timeline->setVisible(isDoc && (m_mode == NormalMode || m_mode == ContextBarAndTimelineMode) && diff --git a/src/app/ui/main_window.h b/src/app/ui/main_window.h index 39be6ab90..5b492397d 100644 --- a/src/app/ui/main_window.h +++ b/src/app/ui/main_window.h @@ -55,13 +55,13 @@ public: MainWindow(); ~MainWindow(); - MainMenuBar* getMenuBar() { return m_menuBar; } - ContextBar* getContextBar() { return m_contextBar; } - StatusBar* statusBar() { return m_statusBar; } - WorkspaceTabs* getTabsBar() { return m_tabsBar; } - Timeline* getTimeline() { return m_timeline; } - Workspace* getWorkspace() { return m_workspace; } - PreviewEditorWindow* getPreviewEditor() { return m_previewEditor; } + MainMenuBar* getMenuBar() { return m_menuBar.get(); } + ContextBar* getContextBar() { return m_contextBar.get(); } + StatusBar* statusBar() { return m_statusBar.get(); } + WorkspaceTabs* getTabsBar() { return m_tabsBar.get(); } + Timeline* getTimeline() { return m_timeline.get(); } + Workspace* getWorkspace() { return m_workspace.get(); } + PreviewEditorWindow* getPreviewEditor() { return m_previewEditor.get(); } #ifdef ENABLE_UPDATER CheckUpdateDelegate* getCheckUpdateDelegate(); #endif @@ -130,23 +130,24 @@ private: ui::TooltipManager* m_tooltipManager; Dock* m_dock; Dock* m_customizableDock; - MainMenuBar* m_menuBar; - LayoutSelector* m_layoutSelector; - StatusBar* m_statusBar; - ColorBar* m_colorBar; - ContextBar* m_contextBar; - ToolBar* m_toolBar; - WorkspaceTabs* m_tabsBar; + std::unique_ptr m_customizableDockPlaceholder; + std::unique_ptr m_menuBar; + std::unique_ptr m_layoutSelector; + std::unique_ptr m_statusBar; + std::unique_ptr m_colorBar; + std::unique_ptr m_contextBar; + std::unique_ptr m_toolBar; + std::unique_ptr m_tabsBar; Mode m_mode; - Timeline* m_timeline; - Workspace* m_workspace; - PreviewEditorWindow* m_previewEditor; - HomeView* m_homeView; - Notifications* m_notifications; - INotificationDelegate* m_scalePanic; - BrowserView* m_browserView; + std::unique_ptr m_timeline; + std::unique_ptr m_workspace; + std::unique_ptr m_previewEditor; + std::unique_ptr m_homeView; + std::unique_ptr m_notifications; + std::unique_ptr m_scalePanic; + std::unique_ptr m_browserView; #ifdef ENABLE_SCRIPTING - DevConsoleView* m_devConsoleView; + std::unique_ptr m_devConsoleView; #endif }; From ffadcc0c3969f3a227486a896051213e15342073 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 22 Aug 2022 14:30:46 -0300 Subject: [PATCH 03/20] Use std::unique_ptr in ToolBar members --- src/app/ui/toolbar.cpp | 20 ++++++-------------- src/app/ui/toolbar.h | 9 +++++---- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/app/ui/toolbar.cpp b/src/app/ui/toolbar.cpp index c13ecf13e..f58f48fba 100644 --- a/src/app/ui/toolbar.cpp +++ b/src/app/ui/toolbar.cpp @@ -94,9 +94,6 @@ ToolBar::ToolBar() : Widget(kGenericWidget), m_openedRecently(false), m_tipTimer m_hotTool = NULL; m_hotIndex = NoneIndex; m_openOnHot = false; - m_popupWindow = NULL; - m_currentStrip = NULL; - m_tipWindow = NULL; m_tipOpened = false; m_minHeight = 0; @@ -112,9 +109,6 @@ ToolBar::ToolBar() : Widget(kGenericWidget), m_openedRecently(false), m_tipTimer ToolBar::~ToolBar() { App::instance()->activeToolManager()->remove_observer(this); - - delete m_popupWindow; - delete m_tipWindow; } bool ToolBar::isToolVisible(Tool* tool) @@ -472,7 +466,7 @@ void ToolBar::openPopupWindow(GroupType group_type, int group_index, tools::Tool // In case this tool contains more than just one tool, show the popup window m_openOnHot = true; - m_popupWindow = new TransparentPopupWindow( + m_popupWindow = std::make_unique( PopupWindow::ClickBehavior::CloseOnClickOutsideHotRegion); m_closeConn = m_popupWindow->Close.connect([this] { onClosePopup(); }); m_openedRecently = true; @@ -493,7 +487,7 @@ void ToolBar::openPopupWindow(GroupType group_type, int group_index, tools::Tool // Set hotregion of popup window m_popupWindow->setAutoRemap(false); - ui::fit_bounds(display(), m_popupWindow, rc); + ui::fit_bounds(display(), m_popupWindow.get(), rc); m_popupWindow->setBounds(rc); Region rgn(m_popupWindow->boundsOnScreen().enlarge(16 * guiscale())); @@ -507,8 +501,7 @@ void ToolBar::closePopupWindow() { if (m_popupWindow) { m_popupWindow->closeWindow(nullptr); - delete m_popupWindow; - m_popupWindow = nullptr; + m_popupWindow.reset(); } } @@ -598,7 +591,7 @@ void ToolBar::openTipWindow(int group_index, Tool* tool) else return; - m_tipWindow = new TipWindow(tooltip); + m_tipWindow = std::make_unique(tooltip); m_tipWindow->remapWindow(); Rect toolrc = getToolGroupBounds(group_index); @@ -620,8 +613,7 @@ void ToolBar::closeTipWindow() if (m_tipWindow) { m_tipWindow->closeWindow(NULL); - delete m_tipWindow; - m_tipWindow = NULL; + m_tipWindow.reset(); } } @@ -658,7 +650,7 @@ void ToolBar::onClosePopup() m_openOnHot = false; m_hotTool = NULL; m_hotIndex = NoneIndex; - m_currentStrip = NULL; + m_currentStrip = nullptr; invalidate(); } diff --git a/src/app/ui/toolbar.h b/src/app/ui/toolbar.h index bcfe61b89..8364eda9b 100644 --- a/src/app/ui/toolbar.h +++ b/src/app/ui/toolbar.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2021 Igara Studio S.A. +// Copyright (C) 2021-2022 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -18,6 +18,7 @@ #include "ui/widget.h" #include +#include namespace ui { class CloseEvent; @@ -104,12 +105,12 @@ private: bool m_openedRecently; // Window displayed to show a tool-group - ui::PopupWindow* m_popupWindow; + std::unique_ptr m_popupWindow; class ToolStrip; - ToolStrip* m_currentStrip; + ToolStrip* m_currentStrip = nullptr; // Tool-tip window - ui::TipWindow* m_tipWindow; + std::unique_ptr m_tipWindow; ui::Timer m_tipTimer; bool m_tipOpened; From 21aa162fe480b4332464e4b72fe6a5ba45b65d1f Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 23 Aug 2022 11:39:51 -0300 Subject: [PATCH 04/20] Fix std::clamp() max bound in TipWindow::pointAt() --- src/ui/tooltips.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/tooltips.cpp b/src/ui/tooltips.cpp index 294e3a116..dcb3fe758 100644 --- a/src/ui/tooltips.cpp +++ b/src/ui/tooltips.cpp @@ -218,16 +218,16 @@ bool TipWindow::pointAt(int arrowAlign, const gfx::Rect& target, const ui::Displ if (get_multiple_displays()) { const gfx::Rect waBounds = nativeParentWindow->screen()->workarea(); gfx::Point pt = nativeParentWindow->pointToScreen(gfx::Point(x, y)); - pt.x = std::clamp(pt.x, waBounds.x, waBounds.x2() - w); - pt.y = std::clamp(pt.y, waBounds.y, waBounds.y2() - h); + pt.x = std::clamp(pt.x, waBounds.x, std::max(waBounds.x, waBounds.x2() - w)); + pt.y = std::clamp(pt.y, waBounds.y, std::max(waBounds.y, waBounds.y2() - h)); pt = nativeParentWindow->pointFromScreen(pt); x = pt.x; y = pt.y; } else { const gfx::Rect displayBounds = display->bounds(); - x = std::clamp(x, displayBounds.x, displayBounds.x2() - w); - y = std::clamp(y, displayBounds.y, displayBounds.y2() - h); + x = std::clamp(x, displayBounds.x, std::max(displayBounds.x, displayBounds.x2() - w)); + y = std::clamp(y, displayBounds.y, std::max(displayBounds.y, displayBounds.y2() - h)); } if (m_target.intersects(gfx::Rect(x, y, w, h))) { From 4d9793be70fb1cea136d075e1564603fd02b90e0 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 23 Aug 2022 11:42:55 -0300 Subject: [PATCH 05/20] Fix popups & tooltips direction when ToolBar is docked at the left side --- src/app/ui/toolbar.cpp | 39 ++++++++++++++++++++++++++++++++------- src/app/ui/toolbar.h | 1 + 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/app/ui/toolbar.cpp b/src/app/ui/toolbar.cpp index f58f48fba..205b68958 100644 --- a/src/app/ui/toolbar.cpp +++ b/src/app/ui/toolbar.cpp @@ -402,6 +402,11 @@ void ToolBar::onVisible(bool visible) } } +bool ToolBar::isDockedAtLeftSide() const +{ + return bounds().center().x < window()->bounds().center().x; +} + int ToolBar::getToolGroupIndex(ToolGroup* group) { ToolBox* toolbox = App::instance()->toolBox(); @@ -475,14 +480,18 @@ void ToolBar::openPopupWindow(GroupType group_type, int group_index, tools::Tool m_currentStrip = toolstrip; m_popupWindow->addChild(toolstrip); + const int borderWidth = border().width(); Rect rc = getToolGroupBounds(group_index); - int w = 0; + int w = borderWidth; for (const auto* tool : tools) { (void)tool; - w += bounds().w - border().width() - 1 * guiscale(); + w += bounds().w - borderWidth - 1 * guiscale(); } - rc.x -= w; + if (isDockedAtLeftSide()) + rc.x = bounds().x2() - borderWidth; + else + rc.x -= w - borderWidth; rc.w = w; // Set hotregion of popup window @@ -542,7 +551,7 @@ Point ToolBar::getToolPositionInGroup(const Tool* tool) const const auto& tools = m_currentStrip->tools(); const int nth = std::find(tools.begin(), tools.end(), tool) - tools.begin(); - return Point(iconsize.w / 2 + nth * (iconsize.w - 1 * guiscale()), iconsize.h); + return Point(iconsize.w / 2 + nth * (iconsize.w - border().right()), iconsize.h); } void ToolBar::openTipWindow(ToolGroup* tool_group, Tool* tool) @@ -596,10 +605,26 @@ void ToolBar::openTipWindow(int group_index, Tool* tool) Rect toolrc = getToolGroupBounds(group_index); Point arrow = (tool ? getToolPositionInGroup(tool) : Point(0, 0)); - if (tool && m_popupWindow && m_popupWindow->isVisible()) - toolrc.x += arrow.x - m_popupWindow->bounds().w; - m_tipWindow->pointAt(TOP | RIGHT, toolrc, ui::Manager::getDefault()->display()); + int pointAt = TOP; + if (isDockedAtLeftSide()) { + pointAt |= LEFT; + toolrc.x -= border().width(); + } + else { + pointAt |= RIGHT; + toolrc.x += border().width(); + } + + // Tooltip for subtools (tools inside groups) + if (tool && m_popupWindow && m_popupWindow->isVisible()) { + if (isDockedAtLeftSide()) + toolrc.x += arrow.x + bounds().w - border().width(); + else + toolrc.x += arrow.x - m_popupWindow->bounds().w + border().width(); + } + + m_tipWindow->pointAt(pointAt, toolrc, ui::Manager::getDefault()->display()); if (m_tipOpened) m_tipWindow->openWindow(); diff --git a/src/app/ui/toolbar.h b/src/app/ui/toolbar.h index 8364eda9b..fe485c6aa 100644 --- a/src/app/ui/toolbar.h +++ b/src/app/ui/toolbar.h @@ -72,6 +72,7 @@ protected: private: enum class GroupType { Regular, Overflow }; + bool isDockedAtLeftSide() const; int getToolGroupIndex(tools::ToolGroup* group); void openPopupWindow(GroupType group_type, int group_index = 0, From 13a8c2d5e8bfb54ad5c99d8827ef88df4548ec70 Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 24 Aug 2022 17:10:12 -0300 Subject: [PATCH 06/20] Save/restore timeline splitter position correctly --- src/app/ui/dock.cpp | 1 + src/app/ui/dock.h | 2 ++ src/app/ui/main_window.cpp | 71 +++++++++++++++++++++++++++++++++----- src/app/ui/main_window.h | 3 ++ 4 files changed, 69 insertions(+), 8 deletions(-) diff --git a/src/app/ui/dock.cpp b/src/app/ui/dock.cpp index 1cffa9f10..6614bb8dd 100644 --- a/src/app/ui/dock.cpp +++ b/src/app/ui/dock.cpp @@ -311,6 +311,7 @@ bool Dock::onProcessMessage(ui::Message* msg) } layout(); + Resize(); } } break; diff --git a/src/app/ui/dock.h b/src/app/ui/dock.h index 821e780b8..0d97d7064 100644 --- a/src/app/ui/dock.h +++ b/src/app/ui/dock.h @@ -53,6 +53,8 @@ public: Dock* right() { return subdock(ui::RIGHT); } Dock* center() { return subdock(ui::CENTER); } + obs::signal Resize; + protected: void onSizeHint(ui::SizeHintEvent& ev) override; void onResize(ui::ResizeEvent& ev) override; diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp index d9197273b..3f0b97523 100644 --- a/src/app/ui/main_window.cpp +++ b/src/app/ui/main_window.cpp @@ -59,6 +59,9 @@ namespace app { using namespace ui; +static const char* kLegacyLayoutMainWindowSection = "layout:main_window"; +static const char* kLegacyLayoutTimelineSplitter = "timeline_splitter"; + class ScreenScalePanic : public INotificationDelegate { public: std::string notificationText() override { return "Reset Scale!"; } @@ -191,6 +194,8 @@ void MainWindow::initialize() MainWindow::~MainWindow() { + m_timelineResizeConn.disconnect(); + m_dock->resetDocks(); m_customizableDock->resetDocks(); @@ -375,6 +380,8 @@ void MainWindow::popTimeline() void MainWindow::setDefaultLayout() { + m_timelineResizeConn.disconnect(); + m_customizableDock->resetDocks(); m_customizableDock->dock(ui::LEFT, m_colorBar.get()); m_customizableDock->center()->dock(ui::TOP, m_contextBar.get()); @@ -383,12 +390,13 @@ void MainWindow::setDefaultLayout() m_timeline.get(), gfx::Size(64 * guiscale(), 64 * guiscale())); m_customizableDock->center()->center()->dock(ui::CENTER, m_workspace.get()); - - layout(); + configureWorkspaceLayout(); } void MainWindow::setDefaultMirrorLayout() { + m_timelineResizeConn.disconnect(); + m_customizableDock->resetDocks(); m_customizableDock->dock(ui::RIGHT, m_colorBar.get()); m_customizableDock->center()->dock(ui::TOP, m_contextBar.get()); @@ -397,8 +405,7 @@ void MainWindow::setDefaultMirrorLayout() m_timeline.get(), gfx::Size(64 * guiscale(), 64 * guiscale())); m_customizableDock->center()->center()->dock(ui::CENTER, m_workspace.get()); - - layout(); + configureWorkspaceLayout(); } void MainWindow::dataRecoverySessionsAreReady() @@ -621,6 +628,9 @@ DropTabResult MainWindow::onDropTab(Tabs* tabs, void MainWindow::configureWorkspaceLayout() { + // First layout to get the bounds of some widgets + layout(); + const auto& pref = Preferences::instance(); bool normal = (m_mode == NormalMode); bool isDoc = (getDocView() != nullptr); @@ -647,20 +657,42 @@ void MainWindow::configureWorkspaceLayout() // Configure timeline { + const gfx::Rect workspaceBounds = m_customizableDock->center()->center()->bounds(); + // Get legacy timeline position and splitter position auto timelinePosition = pref.general.timelinePosition(); + auto timelineSplitterPos = + get_config_double(kLegacyLayoutMainWindowSection, kLegacyLayoutTimelineSplitter, 75.0) / + 100.0; int side = ui::BOTTOM; m_customizableDock->undock(m_timeline.get()); + int w, h; + w = h = 64; + switch (timelinePosition) { - case gen::TimelinePosition::LEFT: side = ui::LEFT; break; - case gen::TimelinePosition::RIGHT: side = ui::RIGHT; break; - case gen::TimelinePosition::BOTTOM: side = ui::BOTTOM; break; + case gen::TimelinePosition::LEFT: + side = ui::LEFT; + w = (workspaceBounds.w * (1.0 - timelineSplitterPos)) / guiscale(); + break; + case gen::TimelinePosition::RIGHT: + side = ui::RIGHT; + w = (workspaceBounds.w * (1.0 - timelineSplitterPos)) / guiscale(); + break; + case gen::TimelinePosition::BOTTOM: + side = ui::BOTTOM; + h = (workspaceBounds.h * (1.0 - timelineSplitterPos)) / guiscale(); + break; } + // Listen to resizing changes in the dock that contains the + // timeline (so we save the new splitter position) + m_timelineResizeConn = m_customizableDock->center()->center()->Resize.connect( + [this] { saveTimelineConfiguration(); }); + m_customizableDock->center()->center()->dock(side, m_timeline.get(), - gfx::Size(64 * guiscale(), 64 * guiscale())); + gfx::Size(w * guiscale(), h * guiscale())); m_timeline->setVisible(isDoc && (m_mode == NormalMode || m_mode == ContextBarAndTimelineMode) && pref.general.visibleTimeline()); @@ -673,4 +705,27 @@ void MainWindow::configureWorkspaceLayout() layout(); } +void MainWindow::saveTimelineConfiguration() +{ + const auto& pref = Preferences::instance(); + const gfx::Rect timelineBounds = m_timeline->bounds(); + const gfx::Rect workspaceBounds = m_customizableDock->center()->center()->bounds(); + auto timelinePosition = pref.general.timelinePosition(); + double timelineSplitterPos = 0.75; + + switch (timelinePosition) { + case gen::TimelinePosition::LEFT: + case gen::TimelinePosition::RIGHT: + timelineSplitterPos = 1.0 - double(timelineBounds.w) / workspaceBounds.w; + break; + case gen::TimelinePosition::BOTTOM: + timelineSplitterPos = 1.0 - double(timelineBounds.h) / workspaceBounds.h; + break; + } + + set_config_double(kLegacyLayoutMainWindowSection, + kLegacyLayoutTimelineSplitter, + std::clamp(timelineSplitterPos * 100.0, 1.0, 99.0)); +} + } // namespace app diff --git a/src/app/ui/main_window.h b/src/app/ui/main_window.h index 5b492397d..d5eff28a1 100644 --- a/src/app/ui/main_window.h +++ b/src/app/ui/main_window.h @@ -10,6 +10,7 @@ #pragma once #include "app/ui/tabs.h" +#include "obs/connection.h" #include "ui/window.h" #include @@ -126,6 +127,7 @@ private: DocView* getDocView(); HomeView* getHomeView(); void configureWorkspaceLayout(); + void saveTimelineConfiguration(); ui::TooltipManager* m_tooltipManager; Dock* m_dock; @@ -149,6 +151,7 @@ private: #ifdef ENABLE_SCRIPTING std::unique_ptr m_devConsoleView; #endif + obs::scoped_connection m_timelineResizeConn; }; } // namespace app From 017a79e8694901d18f05e3d43a64cafd5244dcd4 Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 24 Aug 2022 17:29:29 -0300 Subject: [PATCH 07/20] Save/restore color bar splitter position correctly --- src/app/ui/main_window.cpp | 25 +++++++++++++++++++++++-- src/app/ui/main_window.h | 2 ++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp index 3f0b97523..4c59d1c28 100644 --- a/src/app/ui/main_window.cpp +++ b/src/app/ui/main_window.cpp @@ -61,6 +61,7 @@ using namespace ui; static const char* kLegacyLayoutMainWindowSection = "layout:main_window"; static const char* kLegacyLayoutTimelineSplitter = "timeline_splitter"; +static const char* kLegacyLayoutColorBarSplitter = "color_bar_splitter"; class ScreenScalePanic : public INotificationDelegate { public: @@ -195,6 +196,7 @@ void MainWindow::initialize() MainWindow::~MainWindow() { m_timelineResizeConn.disconnect(); + m_colorBarResizeConn.disconnect(); m_dock->resetDocks(); m_customizableDock->resetDocks(); @@ -381,9 +383,14 @@ void MainWindow::popTimeline() void MainWindow::setDefaultLayout() { m_timelineResizeConn.disconnect(); + m_colorBarResizeConn.disconnect(); + + auto colorBarWidth = get_config_double(kLegacyLayoutMainWindowSection, + kLegacyLayoutColorBarSplitter, + m_colorBar->sizeHint().w); m_customizableDock->resetDocks(); - m_customizableDock->dock(ui::LEFT, m_colorBar.get()); + m_customizableDock->dock(ui::LEFT, m_colorBar.get(), gfx::Size(colorBarWidth, 0)); m_customizableDock->center()->dock(ui::TOP, m_contextBar.get()); m_customizableDock->center()->dock(ui::RIGHT, m_toolBar.get()); m_customizableDock->center()->center()->dock(ui::BOTTOM, @@ -396,9 +403,14 @@ void MainWindow::setDefaultLayout() void MainWindow::setDefaultMirrorLayout() { m_timelineResizeConn.disconnect(); + m_colorBarResizeConn.disconnect(); + + auto colorBarWidth = get_config_double(kLegacyLayoutMainWindowSection, + kLegacyLayoutColorBarSplitter, + m_colorBar->sizeHint().w); m_customizableDock->resetDocks(); - m_customizableDock->dock(ui::RIGHT, m_colorBar.get()); + m_customizableDock->dock(ui::RIGHT, m_colorBar.get(), gfx::Size(colorBarWidth, 0)); m_customizableDock->center()->dock(ui::TOP, m_contextBar.get()); m_customizableDock->center()->dock(ui::LEFT, m_toolBar.get()); m_customizableDock->center()->center()->dock(ui::BOTTOM, @@ -650,6 +662,8 @@ void MainWindow::configureWorkspaceLayout() // TODO set visibility of color bar widgets m_colorBar->setVisible(normal && isDoc); + m_colorBarResizeConn = m_customizableDock->Resize.connect( + [this] { saveColorBarConfiguration(); }); m_toolBar->setVisible(normal && isDoc); m_statusBar->setVisible(normal); @@ -728,4 +742,11 @@ void MainWindow::saveTimelineConfiguration() std::clamp(timelineSplitterPos * 100.0, 1.0, 99.0)); } +void MainWindow::saveColorBarConfiguration() +{ + set_config_double(kLegacyLayoutMainWindowSection, + kLegacyLayoutColorBarSplitter, + m_colorBar->bounds().w); +} + } // namespace app diff --git a/src/app/ui/main_window.h b/src/app/ui/main_window.h index d5eff28a1..acab9dd6f 100644 --- a/src/app/ui/main_window.h +++ b/src/app/ui/main_window.h @@ -128,6 +128,7 @@ private: HomeView* getHomeView(); void configureWorkspaceLayout(); void saveTimelineConfiguration(); + void saveColorBarConfiguration(); ui::TooltipManager* m_tooltipManager; Dock* m_dock; @@ -152,6 +153,7 @@ private: std::unique_ptr m_devConsoleView; #endif obs::scoped_connection m_timelineResizeConn; + obs::scoped_connection m_colorBarResizeConn; }; } // namespace app From 9a0bdf4710b26c760c2f363cb87e5d341ec407bd Mon Sep 17 00:00:00 2001 From: David Capello Date: Thu, 25 Aug 2022 10:14:09 -0300 Subject: [PATCH 08/20] Improve the layout selector UI Changes: * Now we use the "user data" icon as the button to expand the layouts combobox * Added a tooltip to this icon * Added buttons to configure the Timeline position in the same combobox * Fixed some bugs in Dock using space for hidden widgets --- data/strings/en.ini | 3 ++ src/app/ui/configure_timeline_popup.cpp | 11 +++-- src/app/ui/configure_timeline_popup.h | 4 +- src/app/ui/dock.cpp | 11 ++--- src/app/ui/dock.h | 2 + src/app/ui/layout_selector.cpp | 65 +++++++++++++++++++++++-- src/app/ui/layout_selector.h | 11 +++-- src/app/ui/main_window.cpp | 3 +- 8 files changed, 89 insertions(+), 21 deletions(-) diff --git a/data/strings/en.ini b/data/strings/en.ini index 85c217b17..b01354b4b 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -1193,6 +1193,9 @@ help_twitter = Twitter help_enter_license = Enter &License help_about = &About +[main_window] +layout = User Interface Layout + [mask_by_color] title = Select Color label_color = Color: diff --git a/src/app/ui/configure_timeline_popup.cpp b/src/app/ui/configure_timeline_popup.cpp index b8deb613b..4fa78735b 100644 --- a/src/app/ui/configure_timeline_popup.cpp +++ b/src/app/ui/configure_timeline_popup.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020-2021 Igara Studio S.A. +// Copyright (C) 2020-2022 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -53,7 +53,8 @@ ConfigureTimelinePopup::ConfigureTimelinePopup() m_box = new app::gen::TimelineConf(); addChild(m_box); - m_box->position()->ItemChange.connect([this] { onChangePosition(); }); + m_box->position()->ItemChange.connect( + [this] { onChangeTimelinePosition(m_box->position()->selectedItem()); }); m_box->firstFrame()->Change.connect([this] { onChangeFirstFrame(); }); m_box->merge()->Click.connect([this] { onChangeType(); }); m_box->tint()->Click.connect([this] { onChangeType(); }); @@ -143,12 +144,12 @@ bool ConfigureTimelinePopup::onProcessMessage(ui::Message* msg) return PopupWindow::onProcessMessage(msg); } -void ConfigureTimelinePopup::onChangePosition() +void ConfigureTimelinePopup::onChangeTimelinePosition(int option) { gen::TimelinePosition newTimelinePos = gen::TimelinePosition::BOTTOM; - int selITem = m_box->position()->selectedItem(); - switch (selITem) { + int selItem = option; + switch (selItem) { case 0: newTimelinePos = gen::TimelinePosition::LEFT; break; case 1: newTimelinePos = gen::TimelinePosition::RIGHT; break; case 2: newTimelinePos = gen::TimelinePosition::BOTTOM; break; diff --git a/src/app/ui/configure_timeline_popup.h b/src/app/ui/configure_timeline_popup.h index eae779bf6..0e000770f 100644 --- a/src/app/ui/configure_timeline_popup.h +++ b/src/app/ui/configure_timeline_popup.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2022 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -30,9 +31,10 @@ class ConfigureTimelinePopup : public ui::PopupWindow { public: ConfigureTimelinePopup(); + static void onChangeTimelinePosition(int option); + protected: bool onProcessMessage(ui::Message* msg) override; - void onChangePosition(); void onChangeFirstFrame(); void onChangeType(); void onOpacity(); diff --git a/src/app/ui/dock.cpp b/src/app/ui/dock.cpp index 6614bb8dd..be86a4e4b 100644 --- a/src/app/ui/dock.cpp +++ b/src/app/ui/dock.cpp @@ -227,17 +227,16 @@ void Dock::onSizeHint(ui::SizeHintEvent& ev) { gfx::Size sz = border().size(); - if (m_sides[kLeftIndex]) + if (hasVisibleSide(kLeftIndex)) sz.w += m_sides[kLeftIndex]->sizeHint().w + childSpacing(); - if (m_sides[kRightIndex]) + if (hasVisibleSide(kRightIndex)) sz.w += m_sides[kRightIndex]->sizeHint().w + childSpacing(); - if (m_sides[kTopIndex]) + if (hasVisibleSide(kTopIndex)) sz.h += m_sides[kTopIndex]->sizeHint().h + childSpacing(); - if (m_sides[kBottomIndex]) + if (hasVisibleSide(kBottomIndex)) sz.h += m_sides[kBottomIndex]->sizeHint().h + childSpacing(); - if (m_sides[kCenterIndex]) { + if (hasVisibleSide(kCenterIndex)) sz += m_sides[kCenterIndex]->sizeHint(); - } ev.setSizeHint(sz); } diff --git a/src/app/ui/dock.h b/src/app/ui/dock.h index 0d97d7064..d4dbd4920 100644 --- a/src/app/ui/dock.h +++ b/src/app/ui/dock.h @@ -72,6 +72,8 @@ private: const gfx::Rect& separator, const int index)> f); + bool hasVisibleSide(const int i) const { return (m_sides[i] && m_sides[i]->isVisible()); } + std::array m_sides; std::array m_aligns; std::array m_sizes; diff --git a/src/app/ui/layout_selector.cpp b/src/app/ui/layout_selector.cpp index 16e8ed0e7..ab58f2c71 100644 --- a/src/app/ui/layout_selector.cpp +++ b/src/app/ui/layout_selector.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2021 Igara Studio S.A. +// Copyright (C) 2021-2022 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -11,10 +11,14 @@ #include "app/ui/layout_selector.h" #include "app/app.h" +#include "app/i18n/strings.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 "ui/listitem.h" +#include "ui/tooltips.h" #include "ui/window.h" #define ANI_TICKS 5 @@ -49,6 +53,41 @@ private: LayoutId m_id; }; +// TODO Similar ButtonSet to the one in timeline_conf.xml +class TimelineButtons : public ButtonSet { +public: + TimelineButtons() : ButtonSet(2) + { + addItem(Strings::timeline_conf_left())->processMnemonicFromText(); + addItem(Strings::timeline_conf_right())->processMnemonicFromText(); + addItem(Strings::timeline_conf_bottom(), 2)->processMnemonicFromText(); + + Preferences::instance().general.timelinePosition.AfterChange.connect( + [this](gen::TimelinePosition position) { + int selItem = 0; + switch (position) { + case gen::TimelinePosition::LEFT: selItem = 0; break; + case gen::TimelinePosition::RIGHT: selItem = 1; break; + case gen::TimelinePosition::BOTTOM: selItem = 2; break; + } + setSelectedItem(selItem, false); + }); + + InitTheme.connect([this] { + auto theme = skin::SkinTheme::get(this); + setStyle(theme->styles.separatorInView()); + }); + initTheme(); + } + +private: + void onItemChange(Item* item) override + { + ButtonSet::onItemChange(item); + ConfigureTimelinePopup::onChangeTimelinePosition(selectedItem()); + } +}; + }; // namespace void LayoutSelector::LayoutComboBox::onChange() @@ -58,7 +97,8 @@ void LayoutSelector::LayoutComboBox::onChange() } } -LayoutSelector::LayoutSelector() : m_button("", "\xc3\xb7") +LayoutSelector::LayoutSelector(TooltipManager* tooltipManager) + : m_button(SkinTheme::instance()->parts.iconUserData()) { m_button.Click.connect([this]() { switchSelector(); }); @@ -66,6 +106,15 @@ LayoutSelector::LayoutSelector() : m_button("", "\xc3\xb7") addChild(&m_comboBox); addChild(&m_button); + + setupTooltips(tooltipManager); + + InitTheme.connect([this] { + noBorderNoChildSpacing(); + m_comboBox.noBorderNoChildSpacing(); + m_button.noBorderNoChildSpacing(); + }); + initTheme(); } LayoutSelector::~LayoutSelector() @@ -80,8 +129,8 @@ void LayoutSelector::onAnimationFrame() case ANI_EXPANDING: case ANI_COLLAPSING: { const double t = animationTime(); - m_comboBox.setSizeHint(gfx::Size((1.0 - t) * m_startSize.w + t * m_endSize.w, - (1.0 - t) * m_startSize.h + t * m_endSize.h)); + m_comboBox.setSizeHint(gfx::Size(int(inbetween(m_startSize.w, m_endSize.w, t)), + int(inbetween(m_startSize.h, m_endSize.h, t)))); break; } } @@ -112,8 +161,11 @@ void LayoutSelector::switchSelector() // Create the combobox for first time if (m_comboBox.getItemCount() == 0) { + m_comboBox.addItem(new SeparatorInView("Layout", HORIZONTAL)); m_comboBox.addItem(new LayoutItem(LayoutId::DEFAULT, "Default")); m_comboBox.addItem(new LayoutItem(LayoutId::DEFAULT_MIRROR, "Default / Mirror")); + m_comboBox.addItem(new SeparatorInView("Timeline", HORIZONTAL)); + m_comboBox.addItem(new TimelineButtons()); } m_comboBox.setVisible(true); @@ -131,4 +183,9 @@ void LayoutSelector::switchSelector() startAnimation((expand ? ANI_EXPANDING : ANI_COLLAPSING), ANI_TICKS); } +void LayoutSelector::setupTooltips(TooltipManager* tooltipManager) +{ + tooltipManager->addTooltipFor(&m_button, Strings::main_window_layout(), TOP); +} + } // namespace app diff --git a/src/app/ui/layout_selector.h b/src/app/ui/layout_selector.h index cedbf8821..808ae1eb2 100644 --- a/src/app/ui/layout_selector.h +++ b/src/app/ui/layout_selector.h @@ -9,13 +9,17 @@ #pragma once #include "app/ui/dockable.h" +#include "app/ui/icon_button.h" #include "ui/animated_widget.h" #include "ui/box.h" #include "ui/combobox.h" -#include "ui/link_label.h" #include +namespace ui { +class TooltipManager; +} + namespace app { class LayoutSelector : public ui::HBox, @@ -32,19 +36,20 @@ class LayoutSelector : public ui::HBox, }; public: - LayoutSelector(); + LayoutSelector(ui::TooltipManager* tooltipManager); ~LayoutSelector(); // Dockable impl int dockableAt() const override { return ui::TOP | ui::BOTTOM; } private: + void setupTooltips(ui::TooltipManager* tooltipManager); void onAnimationFrame() override; void onAnimationStop(int animation) override; void switchSelector(); LayoutComboBox m_comboBox; - ui::LinkLabel m_button; + IconButton m_button; gfx::Size m_startSize; gfx::Size m_endSize; }; diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp index 4c59d1c28..2673794f9 100644 --- a/src/app/ui/main_window.cpp +++ b/src/app/ui/main_window.cpp @@ -93,7 +93,6 @@ MainWindow::MainWindow() , m_tooltipManager(new TooltipManager) , m_dock(new Dock) , m_customizableDock(new Dock) - , m_layoutSelector(new LayoutSelector) , m_mode(NormalMode) , m_homeView(nullptr) , m_scalePanic(nullptr) @@ -117,7 +116,7 @@ MainWindow::MainWindow() void MainWindow::initialize() { m_menuBar = std::make_unique(); - m_layoutSelector = std::make_unique(); + m_layoutSelector = std::make_unique(m_tooltipManager); // Register commands to load menus+shortcuts for these commands Editor::registerCommands(); From 98346bcc50c052e8e56726ad7f9fc46633028de2 Mon Sep 17 00:00:00 2001 From: David Capello Date: Thu, 25 Aug 2022 10:22:21 -0300 Subject: [PATCH 09/20] Show the timeline when we set its position from the LayoutSelector --- src/app/ui/layout_selector.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/ui/layout_selector.cpp b/src/app/ui/layout_selector.cpp index ab58f2c71..9fa1b849c 100644 --- a/src/app/ui/layout_selector.cpp +++ b/src/app/ui/layout_selector.cpp @@ -85,6 +85,10 @@ private: { ButtonSet::onItemChange(item); ConfigureTimelinePopup::onChangeTimelinePosition(selectedItem()); + + // Show the timeline + App::instance()->mainWindow() + ->setTimelineVisibility(true); } }; From 783686b666bb80b7747cef3d4135c1f4194ce930 Mon Sep 17 00:00:00 2001 From: David Capello Date: Thu, 25 Aug 2022 10:25:41 -0300 Subject: [PATCH 10/20] Set the initial timeline position in the LayoutSelector correctly --- src/app/ui/layout_selector.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/app/ui/layout_selector.cpp b/src/app/ui/layout_selector.cpp index 9fa1b849c..9de456925 100644 --- a/src/app/ui/layout_selector.cpp +++ b/src/app/ui/layout_selector.cpp @@ -62,16 +62,11 @@ public: addItem(Strings::timeline_conf_right())->processMnemonicFromText(); addItem(Strings::timeline_conf_bottom(), 2)->processMnemonicFromText(); - Preferences::instance().general.timelinePosition.AfterChange.connect( - [this](gen::TimelinePosition position) { - int selItem = 0; - switch (position) { - case gen::TimelinePosition::LEFT: selItem = 0; break; - case gen::TimelinePosition::RIGHT: selItem = 1; break; - case gen::TimelinePosition::BOTTOM: selItem = 2; break; - } - setSelectedItem(selItem, false); - }); + auto& timelinePosOption = Preferences::instance().general.timelinePosition; + + setSelectedButtonFromTimelinePosition(timelinePosOption()); + timelinePosOption.AfterChange.connect( + [this](gen::TimelinePosition position) { setSelectedButtonFromTimelinePosition(position); }); InitTheme.connect([this] { auto theme = skin::SkinTheme::get(this); @@ -81,14 +76,24 @@ public: } private: + void setSelectedButtonFromTimelinePosition(gen::TimelinePosition pos) + { + int selItem = 0; + switch (pos) { + case gen::TimelinePosition::LEFT: selItem = 0; break; + case gen::TimelinePosition::RIGHT: selItem = 1; break; + case gen::TimelinePosition::BOTTOM: selItem = 2; break; + } + setSelectedItem(selItem, false); + } + void onItemChange(Item* item) override { ButtonSet::onItemChange(item); ConfigureTimelinePopup::onChangeTimelinePosition(selectedItem()); // Show the timeline - App::instance()->mainWindow() - ->setTimelineVisibility(true); + App::instance()->mainWindow()->setTimelineVisibility(true); } }; From 473b274f9b0d5133b6b2068290ee7b58ab6f09bd Mon Sep 17 00:00:00 2001 From: David Capello Date: Thu, 25 Aug 2022 16:31:42 -0300 Subject: [PATCH 11/20] Add option to save/restore user-defined layouts on memory This happens only in memory at the moment (layouts are not saved in disk yet), and the customization is quite simple (only size of splitters, timeline position). But in the future we should be able to dock elements in any place. --- data/strings/en.ini | 5 + data/widgets/new_layout.xml | 21 ++++ src/app/CMakeLists.txt | 1 + src/app/ui/dock.cpp | 31 ++++++ src/app/ui/dock.h | 4 + src/app/ui/layout.cpp | 181 +++++++++++++++++++++++++++++++++ src/app/ui/layout.h | 35 +++++++ src/app/ui/layout_selector.cpp | 131 +++++++++++++++++++----- src/app/ui/layout_selector.h | 10 ++ src/app/ui/main_window.cpp | 21 ++++ src/app/ui/main_window.h | 8 +- 11 files changed, 420 insertions(+), 28 deletions(-) create mode 100644 data/widgets/new_layout.xml create mode 100644 src/app/ui/layout.cpp create mode 100644 src/app/ui/layout.h diff --git a/data/strings/en.ini b/data/strings/en.ini index b01354b4b..715a4b698 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -1224,6 +1224,11 @@ name = Name: tileset = Tileset: default_new_layer_name = New Layer +[new_layout] +title = New UI Layout +name = Name: +default_name = User Layout + [news_listbox] more = More... problem_loading = Problems loading news. Please retry. diff --git a/data/widgets/new_layout.xml b/data/widgets/new_layout.xml new file mode 100644 index 000000000..5ad98cace --- /dev/null +++ b/data/widgets/new_layout.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + +