From 86afa3a568b4e3aefdefd91b5642eab475e12a18 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 31 Mar 2015 17:31:45 -0300 Subject: [PATCH] Add support to dock tabs as other Workspace panels --- src/app/CMakeLists.txt | 2 + src/app/ui/editor/editor.cpp | 1 + src/app/ui/editor/editor_observer.h | 2 + src/app/ui/editor/editor_observers.cpp | 5 + src/app/ui/editor/editor_observers.h | 1 + src/app/ui/main_window.cpp | 15 +- src/app/ui/main_window.h | 1 + src/app/ui/tabs.cpp | 143 ++++++----- src/app/ui/tabs.h | 28 +- src/app/ui/timeline.cpp | 9 + src/app/ui/timeline.h | 1 + src/app/ui/workspace.cpp | 208 ++++++--------- src/app/ui/workspace.h | 39 ++- src/app/ui/workspace_panel.cpp | 343 +++++++++++++++++++++++++ src/app/ui/workspace_panel.h | 90 +++++++ src/app/ui/workspace_tabs.cpp | 32 +++ src/app/ui/workspace_tabs.h | 25 ++ 17 files changed, 723 insertions(+), 222 deletions(-) create mode 100644 src/app/ui/workspace_panel.cpp create mode 100644 src/app/ui/workspace_panel.h create mode 100644 src/app/ui/workspace_tabs.cpp create mode 100644 src/app/ui/workspace_tabs.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 2968cc080..9ed368728 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -344,6 +344,8 @@ add_library(app-lib ui/timeline.cpp ui/toolbar.cpp ui/workspace.cpp + ui/workspace_panel.cpp + ui/workspace_tabs.cpp ui_context.cpp util/autocrop.cpp util/boundary.cpp diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 3e9c180f0..5424804b7 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -183,6 +183,7 @@ Editor::Editor(Document* document, EditorFlags flags) Editor::~Editor() { + m_observers.notifyDestroyEditor(this); m_document->removeObserver(this); setCustomizationDelegate(NULL); diff --git a/src/app/ui/editor/editor_observer.h b/src/app/ui/editor/editor_observer.h index ada870053..ca335f13a 100644 --- a/src/app/ui/editor/editor_observer.h +++ b/src/app/ui/editor/editor_observer.h @@ -18,6 +18,8 @@ namespace app { virtual ~EditorObserver() { } virtual void dispose() { } + virtual void onDestroyEditor(Editor* editor) { } + // Called when the editor's state changes. virtual void onStateChanged(Editor* editor) { } diff --git a/src/app/ui/editor/editor_observers.cpp b/src/app/ui/editor/editor_observers.cpp index 5626b439c..c9c3e5cb6 100644 --- a/src/app/ui/editor/editor_observers.cpp +++ b/src/app/ui/editor/editor_observers.cpp @@ -30,6 +30,11 @@ void EditorObservers::removeObserver(EditorObserver* observer) m_observers.removeObserver(observer); } +void EditorObservers::notifyDestroyEditor(Editor* editor) +{ + m_observers.notifyObservers(&EditorObserver::onDestroyEditor, editor); +} + void EditorObservers::notifyStateChanged(Editor* editor) { m_observers.notifyObservers(&EditorObserver::onStateChanged, editor); diff --git a/src/app/ui/editor/editor_observers.h b/src/app/ui/editor/editor_observers.h index 9d118f960..672a52c3c 100644 --- a/src/app/ui/editor/editor_observers.h +++ b/src/app/ui/editor/editor_observers.h @@ -22,6 +22,7 @@ namespace app { void addObserver(EditorObserver* observer); void removeObserver(EditorObserver* observer); + void notifyDestroyEditor(Editor* editor); void notifyStateChanged(Editor* editor); void notifyScrollChanged(Editor* editor); void notifyBeforeFrameChanged(Editor* editor); diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp index 86e54f6ba..0ac5987bc 100644 --- a/src/app/ui/main_window.cpp +++ b/src/app/ui/main_window.cpp @@ -302,11 +302,19 @@ void MainWindow::onFloatingTab(Tabs* tabs, TabView* tabView, const gfx::Point& p m_workspace->setDropViewPreview(pos); } +void MainWindow::onDockingTab(Tabs* tabs, TabView* tabView) +{ + m_workspace->removeDropViewPreview(); +} + DropTabResult MainWindow::onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos) { - m_workspace->removeDropViewPreview(pos); - m_workspace->dropViewAt(pos, dynamic_cast(tabView)); - return DropTabResult::IGNORE; + m_workspace->removeDropViewPreview(); + + if (m_workspace->dropViewAt(pos, dynamic_cast(tabView))) + return DropTabResult::DOCKED_IN_OTHER_PLACE; + else + return DropTabResult::IGNORE; } void MainWindow::configureWorkspaceLayout() @@ -335,6 +343,7 @@ void MainWindow::configureWorkspaceLayout() } layout(); + invalidate(); } } // namespace app diff --git a/src/app/ui/main_window.h b/src/app/ui/main_window.h index 92b53c529..809c65dd6 100644 --- a/src/app/ui/main_window.h +++ b/src/app/ui/main_window.h @@ -80,6 +80,7 @@ namespace app { void onContextMenuTab(Tabs* tabs, TabView* tabView) override; void onMouseOverTab(Tabs* tabs, TabView* tabView) override; void onFloatingTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos) override; + void onDockingTab(Tabs* tabs, TabView* tabView) override; DropTabResult onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos) override; protected: diff --git a/src/app/ui/tabs.cpp b/src/app/ui/tabs.cpp index d3dd88b66..fddc684ad 100644 --- a/src/app/ui/tabs.cpp +++ b/src/app/ui/tabs.cpp @@ -63,17 +63,12 @@ Tabs::Tabs(TabsDelegate* delegate) Tabs::~Tabs() { - if (m_removedTab) { - delete m_removedTab; - m_removedTab = nullptr; - } + m_removedTab = nullptr; // Stop animation stopAnimation(); // Remove all tabs - for (Tab* tab : m_list) - delete tab; m_list.clear(); } @@ -82,7 +77,7 @@ void Tabs::addTab(TabView* tabView, int pos) resetOldPositions(); startAnimation(ANI_ADDING_TAB, ANI_ADDING_TAB_TICKS); - Tab* tab = new Tab(tabView); + TabPtr tab(new Tab(tabView)); if (pos < 0) m_list.push_back(tab); else @@ -94,9 +89,9 @@ void Tabs::addTab(TabView* tabView, int pos) tab->modified = false; } -void Tabs::removeTab(TabView* tabView) +void Tabs::removeTab(TabView* tabView, bool with_animation) { - Tab* tab = getTabByView(tabView); + TabPtr tab(getTabByView(tabView)); if (!tab) return; @@ -118,15 +113,17 @@ void Tabs::removeTab(TabView* tabView) ASSERT(it != m_list.end() && "Removing a tab that is not part of the Tabs widget"); it = m_list.erase(it); - delete m_removedTab; m_removedTab = tab; - if (m_delegate) - tab->modified = m_delegate->onIsModified(this, tabView); - tab->view = nullptr; // The view will be destroyed after Tabs::removeTab() anyway + if (with_animation) { + if (m_delegate) + tab->modified = m_delegate->onIsModified(this, tabView); + tab->view = nullptr; // The view will be destroyed after Tabs::removeTab() anyway + + resetOldPositions(); + startAnimation(ANI_REMOVING_TAB, ANI_REMOVING_TAB_TICKS); + } - resetOldPositions(); - startAnimation(ANI_REMOVING_TAB, ANI_REMOVING_TAB_TICKS); updateTabs(); } @@ -142,7 +139,7 @@ void Tabs::updateTabs() } double x = 0.0; - for (Tab* tab : m_list) { + for (auto& tab : m_list) { if (tab == m_floatingTab) continue; @@ -159,7 +156,7 @@ void Tabs::selectTab(TabView* tabView) { ASSERT(tabView != NULL); - Tab* tab = getTabByView(tabView); + TabPtr tab(getTabByView(tabView)); if (tab) selectTabInternal(tab); } @@ -231,10 +228,13 @@ bool Tabs::onProcessMessage(Message* msg) } // We are drag a tab... else { - Tab* justDocked = nullptr; + TabPtr justDocked(nullptr); // Floating tab (to create a new window) - if (ABS(delta.y) > 16*guiscale()) { + if (!getBounds().contains(mousePos) && + (ABS(delta.y) > 16*guiscale() || + mousePos.x < getBounds().x-16*guiscale() || + mousePos.x > getBounds().x2()+16*guiscale())) { if (!m_floatingOverlay) createFloatingTab(m_selected); @@ -246,6 +246,9 @@ bool Tabs::onProcessMessage(Message* msg) else { justDocked = m_floatingTab; destroyFloatingTab(); + + if (m_delegate) + m_delegate->onDockingTab(this, m_selected->view); } // Docked tab @@ -285,7 +288,7 @@ bool Tabs::onProcessMessage(Message* msg) MouseMessage* mouseMsg = static_cast(msg); m_dragMousePos = mouseMsg->position(); m_dragOffset = mouseMsg->position() - - (getBounds().getOrigin() + getTabBounds(m_hot).getOrigin()); + (getBounds().getOrigin() + getTabBounds(m_hot.get()).getOrigin()); if (m_hotCloseButton) { if (!m_clickedCloseButton) { @@ -325,11 +328,15 @@ bool Tabs::onProcessMessage(Message* msg) releaseMouse(); if (m_isDragging) { - if (m_delegate) - m_delegate->onDropTab(this, m_selected->view, - mouseMsg->position()); + DropTabResult result = DropTabResult::IGNORE; - stopDrag(); + if (m_delegate) { + result = + m_delegate->onDropTab(this, m_selected->view, + mouseMsg->position()); + } + + stopDrag(result); } if (m_clickedCloseButton) { @@ -374,14 +381,14 @@ void Tabs::onPaint(PaintEvent& ev) drawFiller(g, box); // For each tab... - for (Tab* tab : m_list) { + for (TabPtr& tab : m_list) { if (tab == m_floatingTab) continue; - box = getTabBounds(tab); + box = getTabBounds(tab.get()); if (tab != m_selected) - drawTab(g, box, tab, 0, (tab == m_hot), false); + drawTab(g, box, tab.get(), 0, (tab == m_hot), false); box.x = box.x2(); } @@ -389,8 +396,8 @@ void Tabs::onPaint(PaintEvent& ev) // Draw deleted tab if (animation() == ANI_REMOVING_TAB && m_removedTab) { m_removedTab->width = 0; - box = getTabBounds(m_removedTab); - drawTab(g, box, m_removedTab, 0, + box = getTabBounds(m_removedTab.get()); + drawTab(g, box, m_removedTab.get(), 0, (m_removedTab == m_floatingTab), (m_removedTab == m_floatingTab)); } @@ -398,14 +405,14 @@ void Tabs::onPaint(PaintEvent& ev) // Tab that is being dragged if (m_selected && m_selected != m_floatingTab) { double t = animationTime(); - Tab* tab = m_selected; - box = getTabBounds(tab); + TabPtr tab(m_selected); + box = getTabBounds(tab.get()); int dy = 0; if (animation() == ANI_ADDING_TAB) dy = int(box.h - box.h * t); - drawTab(g, box, m_selected, dy, (tab == m_hot), true); + drawTab(g, box, m_selected.get(), dy, (tab == m_hot), true); } } @@ -428,10 +435,10 @@ void Tabs::onPreferredSize(PreferredSizeEvent& ev) ev.setPreferredSize(reqsize); } -void Tabs::selectTabInternal(Tab* tab) +void Tabs::selectTabInternal(TabPtr& tab) { m_selected = tab; - makeTabVisible(tab); + makeTabVisible(tab.get()); invalidate(); if (m_delegate) @@ -555,13 +562,13 @@ Tabs::TabsListIterator Tabs::getTabIteratorByView(TabView* tabView) return it; } -Tabs::Tab* Tabs::getTabByView(TabView* tabView) +Tabs::TabPtr Tabs::getTabByView(TabView* tabView) { TabsListIterator it = getTabIteratorByView(tabView); if (it != m_list.end()) - return *it; + return TabPtr(*it); else - return NULL; + return TabPtr(nullptr); } void Tabs::makeTabVisible(Tab* thisTab) @@ -577,11 +584,11 @@ void Tabs::calculateHot() gfx::Rect rect = getBounds(); gfx::Rect box(rect.x+m_border*guiscale(), rect.y, 0, rect.h-1); gfx::Point mousePos = ui::get_mouse_position(); - Tab* hot = NULL; + TabPtr hot(nullptr); bool hotCloseButton = false; // For each tab - for (Tab* tab : m_list) { + for (TabPtr& tab : m_list) { if (tab == m_floatingTab) continue; @@ -589,7 +596,7 @@ void Tabs::calculateHot() if (box.contains(mousePos)) { hot = tab; - hotCloseButton = getTabCloseButtonBounds(tab, box).contains(mousePos); + hotCloseButton = getTabCloseButtonBounds(tab.get(), box).contains(mousePos); break; } @@ -614,7 +621,7 @@ gfx::Rect Tabs::getTabCloseButtonBounds(Tab* tab, const gfx::Rect& box) int iconW = theme->dimensions.tabsCloseIconWidth(); int iconH = theme->dimensions.tabsCloseIconHeight(); - if (box.w-iconW > 32*ui::guiscale() || tab == m_selected) + if (box.w-iconW > 32*ui::guiscale() || tab == m_selected.get()) return gfx::Rect(box.x2()-iconW, box.y+box.h/2-iconH/2, iconW, iconH); else return gfx::Rect(); @@ -622,7 +629,7 @@ gfx::Rect Tabs::getTabCloseButtonBounds(Tab* tab, const gfx::Rect& box) void Tabs::resetOldPositions() { - for (Tab* tab : m_list) { + for (TabPtr& tab : m_list) { if (tab == m_floatingTab) continue; @@ -633,7 +640,7 @@ void Tabs::resetOldPositions() void Tabs::resetOldPositions(double t) { - for (Tab* tab : m_list) { + for (TabPtr& tab : m_list) { if (tab == m_floatingTab) continue; @@ -667,20 +674,39 @@ void Tabs::startDrag() m_dragTabIndex = std::find(m_list.begin(), m_list.end(), m_selected) - m_list.begin(); } -void Tabs::stopDrag() +void Tabs::stopDrag(DropTabResult result) { m_isDragging = false; - destroyFloatingTab(); + switch (result) { + + case DropTabResult::IGNORE: + destroyFloatingTab(); + + ASSERT(m_selected); + if (m_selected) { + m_selected->oldX = m_selected->x; + m_selected->oldWidth = m_selected->width; + } + resetOldPositions(animationTime()); + updateTabs(); + startAnimation(ANI_REORDER_TABS, ANI_REORDER_TABS_TICKS); + break; + + case DropTabResult::DOCKED_IN_OTHER_PLACE: { + TabPtr tab = m_floatingTab; + + m_floatingTab = nullptr; + m_removedTab = nullptr; + destroyFloatingTab(); + + ASSERT(tab); + if (tab) + removeTab(tab->view, false); + break; + } - if (m_selected) { - m_selected->oldX = m_selected->x; - m_selected->oldWidth = m_selected->width; } - - resetOldPositions(animationTime()); - updateTabs(); - startAnimation(ANI_REORDER_TABS, ANI_REORDER_TABS_TICKS); } gfx::Rect Tabs::getTabBounds(Tab* tab) @@ -705,7 +731,7 @@ gfx::Rect Tabs::getTabBounds(Tab* tab) return box; } -void Tabs::createFloatingTab(Tab* tab) +void Tabs::createFloatingTab(TabPtr& tab) { ASSERT(!m_floatingOverlay); @@ -717,7 +743,7 @@ void Tabs::createFloatingTab(Tab* tab) Graphics g(surface, 0, 0); g.setFont(getFont()); g.fillRect(gfx::ColorNone, g.getClipBounds()); - drawTab(&g, g.getClipBounds(), tab, 0, true, true); + drawTab(&g, g.getClipBounds(), tab.get(), 0, true, true); } // Make opaque (TODO this shouldn't be necessary) @@ -733,8 +759,8 @@ void Tabs::createFloatingTab(Tab* tab) } } - m_floatingOverlay = new Overlay(surface, gfx::Point(), Overlay::MouseZOrder-1); - OverlayManager::instance()->addOverlay(m_floatingOverlay); + m_floatingOverlay.reset(new Overlay(surface, gfx::Point(), Overlay::MouseZOrder-1)); + OverlayManager::instance()->addOverlay(m_floatingOverlay.get()); resetOldPositions(); @@ -747,13 +773,12 @@ void Tabs::createFloatingTab(Tab* tab) void Tabs::destroyFloatingTab() { if (m_floatingOverlay) { - OverlayManager::instance()->removeOverlay(m_floatingOverlay); - delete m_floatingOverlay; + OverlayManager::instance()->removeOverlay(m_floatingOverlay.get()); m_floatingOverlay = nullptr; } if (m_floatingTab) { - Tab* tab = m_floatingTab; + TabPtr tab(m_floatingTab); m_floatingTab = nullptr; resetOldPositions(); diff --git a/src/app/ui/tabs.h b/src/app/ui/tabs.h index b20796397..00fc46b08 100644 --- a/src/app/ui/tabs.h +++ b/src/app/ui/tabs.h @@ -15,6 +15,7 @@ #include "ui/widget.h" #include +#include namespace ui { class Graphics; @@ -71,6 +72,7 @@ namespace app { // Called when the user is dragging a tab outside the Tabs bar. virtual void onFloatingTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos) = 0; + virtual void onDockingTab(Tabs* tabs, TabView* tabView) = 0; virtual DropTabResult onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos) = 0; }; @@ -89,7 +91,9 @@ namespace app { } }; - typedef std::vector TabsList; + typedef std::shared_ptr TabPtr; + + typedef std::vector TabsList; typedef TabsList::iterator TabsListIterator; enum Ani : int { @@ -103,8 +107,10 @@ namespace app { Tabs(TabsDelegate* delegate); ~Tabs(); + TabsDelegate* getDelegate() { return m_delegate; } + void addTab(TabView* tabView, int pos = -1); - void removeTab(TabView* tabView); + void removeTab(TabView* tabView, bool with_animation); void updateTabs(); void selectTab(TabView* tabView); @@ -124,33 +130,33 @@ namespace app { void resetOldPositions(); void resetOldPositions(double t); - void selectTabInternal(Tab* tab); + void selectTabInternal(TabPtr& tab); void drawTab(ui::Graphics* g, const gfx::Rect& box, Tab* tab, int dy, bool hover, bool selected); void drawFiller(ui::Graphics* g, const gfx::Rect& box); TabsListIterator getTabIteratorByView(TabView* tabView); - Tab* getTabByView(TabView* tabView); + TabPtr getTabByView(TabView* tabView); int getMaxScrollX(); void makeTabVisible(Tab* tab); void calculateHot(); gfx::Rect getTabCloseButtonBounds(Tab* tab, const gfx::Rect& box); void startDrag(); - void stopDrag(); + void stopDrag(DropTabResult result); gfx::Rect getTabBounds(Tab* tab); - void createFloatingTab(Tab* tab); + void createFloatingTab(TabPtr& tab); void destroyFloatingTab(); int m_border; TabsList m_list; - Tab* m_hot; + TabPtr m_hot; bool m_hotCloseButton; bool m_clickedCloseButton; - Tab* m_selected; + TabPtr m_selected; // Delegate of notifications TabsDelegate* m_delegate; // Variables for animation purposes - Tab* m_removedTab; + TabPtr m_removedTab; // Drag-and-drop bool m_isDragging; @@ -158,8 +164,8 @@ namespace app { gfx::Point m_dragMousePos; gfx::Point m_dragOffset; int m_dragTabIndex; - Tab* m_floatingTab; - ui::Overlay* m_floatingOverlay; + TabPtr m_floatingTab; + std::unique_ptr m_floatingOverlay; }; } // namespace app diff --git a/src/app/ui/timeline.cpp b/src/app/ui/timeline.cpp index 2958c6d4c..04aabde86 100644 --- a/src/app/ui/timeline.cpp +++ b/src/app/ui/timeline.cpp @@ -988,6 +988,15 @@ void Timeline::onAfterLayerChanged(Editor* editor) invalidate(); } +void Timeline::onDestroyEditor(Editor* editor) +{ + ASSERT(m_editor == editor); + if (m_editor == editor) { + m_editor->removeObserver(this); + m_editor = nullptr; + } +} + void Timeline::setCursor(ui::Message* msg, const Hit& hit) { // Scrolling. diff --git a/src/app/ui/timeline.h b/src/app/ui/timeline.h index 3bf79a615..28df0f573 100644 --- a/src/app/ui/timeline.h +++ b/src/app/ui/timeline.h @@ -114,6 +114,7 @@ namespace app { // EditorObserver impl. void onAfterFrameChanged(Editor* editor) override; void onAfterLayerChanged(Editor* editor) override; + void onDestroyEditor(Editor* editor) override; private: struct Hit { diff --git a/src/app/ui/workspace.cpp b/src/app/ui/workspace.cpp index 914757563..2f2d03f6c 100644 --- a/src/app/ui/workspace.cpp +++ b/src/app/ui/workspace.cpp @@ -11,35 +11,38 @@ #include "app/ui/workspace.h" -#include "app/app.h" -#include "app/ui/main_window.h" #include "app/ui/skin/skin_theme.h" #include "app/ui/tabs.h" #include "app/ui/workspace_view.h" #include "base/remove_from_container.h" - -#include -#include +#include "ui/paint_event.h" +#include "ui/resize_event.h" namespace app { -#define ANI_DROPAREA_TICKS 4 - using namespace app::skin; using namespace ui; +// static +WidgetType Workspace::Type() +{ + static WidgetType type = kGenericWidget; + if (type == kGenericWidget) + type = register_widget_type(); + return type; +} + Workspace::Workspace() - : Widget(kGenericWidget) - , m_tabsBar(nullptr) - , m_activeView(nullptr) - , m_dropArea(0) - , m_leftTime(0) - , m_rightTime(0) - , m_topTime(0) - , m_bottomTime(0) + : Widget(Workspace::Type()) + , m_mainPanel(WorkspacePanel::MAIN_PANEL) + , m_tabs(nullptr) + , m_activePanel(&m_mainPanel) + , m_dropPreviewPanel(nullptr) { SkinTheme* theme = static_cast(getTheme()); setBgColor(theme->colors.workspace()); + + addChild(&m_mainPanel); } Workspace::~Workspace() @@ -48,19 +51,18 @@ Workspace::~Workspace() ASSERT(m_views.empty()); } -void Workspace::setTabsBar(Tabs* tabsBar) +void Workspace::setTabsBar(Tabs* tabs) { - m_tabsBar = tabsBar; + m_tabs = tabs; + m_mainPanel.setTabsBar(tabs); } void Workspace::addView(WorkspaceView* view, int pos) { - if (pos < 0) - m_views.push_back(view); - else - m_views.insert(m_views.begin()+pos, view); + m_mainPanel.addView(view, pos); + m_activePanel = &m_mainPanel; + m_views.push_back(view); - m_tabsBar->addTab(dynamic_cast(view), pos); setActiveView(view); } @@ -68,15 +70,10 @@ void Workspace::removeView(WorkspaceView* view) { base::remove_from_container(m_views, view); - Widget* content = view->getContentWidget(); - if (content->getParent()) - content->getParent()->removeChild(content); - - // Remove related tab. - m_tabsBar->removeTab(dynamic_cast(view)); - - TabView* tabView = m_tabsBar->getSelectedTab(); - setActiveView(dynamic_cast(tabView)); + WorkspacePanel* panel = getViewPanel(view); + ASSERT(panel); + if (panel) + panel->removeView(view); } bool Workspace::closeView(WorkspaceView* view) @@ -86,19 +83,24 @@ bool Workspace::closeView(WorkspaceView* view) WorkspaceView* Workspace::activeView() { - return m_activeView; + return (m_activePanel ? m_activePanel->activeView(): nullptr); } void Workspace::setActiveView(WorkspaceView* view) { - m_activeView = view; + m_activePanel = getViewPanel(view); + ASSERT(m_activePanel); + if (!m_activePanel) + return; - removeAllChildren(); - if (view) - addChild(view->getContentWidget()); + m_activePanel->setActiveView(view); - layout(); + ActiveViewChanged(); // Fire ActiveViewChanged event +} +void Workspace::setMainPanelAsActive() +{ + m_activePanel = &m_mainPanel; ActiveViewChanged(); // Fire ActiveViewChanged event } @@ -112,107 +114,59 @@ void Workspace::onResize(ui::ResizeEvent& ev) setBoundsQuietly(ev.getBounds()); gfx::Rect rc = getChildrenBounds(); - - // Preview to drop tabs in workspace - if (animation() == ANI_DROPAREA) { - double left = double(m_leftTime) / double(ANI_DROPAREA_TICKS); - double top = double(m_topTime) / double(ANI_DROPAREA_TICKS); - double right = double(m_rightTime) / double(ANI_DROPAREA_TICKS); - double bottom = double(m_bottomTime) / double(ANI_DROPAREA_TICKS); - double threshold = getDropThreshold(); - - rc.x += int(inbetween(0.0, threshold, left)); - rc.y += int(inbetween(0.0, threshold, top)); - rc.w -= int(inbetween(0.0, threshold, left) + inbetween(0.0, threshold, right)); - rc.h -= int(inbetween(0.0, threshold, top) + inbetween(0.0, threshold, bottom)); - } - for (Widget* child : getChildren()) child->setBounds(rc); } void Workspace::setDropViewPreview(const gfx::Point& pos) { - int newDropArea = calculateDropArea(pos); - if (newDropArea != m_dropArea) { - m_dropArea = newDropArea; - startAnimation(ANI_DROPAREA, ANI_DROPAREA_TICKS); + WorkspacePanel* panel = getPanelAt(pos); + + if (m_dropPreviewPanel && m_dropPreviewPanel != panel) + m_dropPreviewPanel->removeDropViewPreview(); + + m_dropPreviewPanel = panel; + + if (m_dropPreviewPanel) + m_dropPreviewPanel->setDropViewPreview(pos); +} + +void Workspace::removeDropViewPreview() +{ + if (m_dropPreviewPanel) + m_dropPreviewPanel->removeDropViewPreview(); +} + +bool Workspace::dropViewAt(const gfx::Point& pos, WorkspaceView* view) +{ + if (!m_dropPreviewPanel) + return false; + + return m_dropPreviewPanel->dropViewAt(pos, getViewPanel(view), view); +} + +WorkspacePanel* Workspace::getViewPanel(WorkspaceView* view) +{ + Widget* widget = view->getContentWidget(); + while (widget) { + if (widget->getType() == WorkspacePanel::Type()) + return static_cast(widget); + + widget = widget->getParent(); } + return nullptr; } -void Workspace::removeDropViewPreview(const gfx::Point& pos) +WorkspacePanel* Workspace::getPanelAt(const gfx::Point& pos) { - if (m_dropArea) { - m_dropArea = 0; - startAnimation(ANI_DROPAREA, ANI_DROPAREA_TICKS); + Widget* widget = getManager()->pick(pos); + while (widget) { + if (widget->getType() == WorkspacePanel::Type()) + return static_cast(widget); + + widget = widget->getParent(); } -} - -void Workspace::onAnimationStop() -{ - if (animation() == ANI_DROPAREA) - layout(); -} - -void Workspace::onAnimationFrame() -{ - if (animation() == ANI_DROPAREA) { - adjustTime(m_leftTime, JI_LEFT); - adjustTime(m_topTime, JI_TOP); - adjustTime(m_rightTime, JI_RIGHT); - adjustTime(m_bottomTime, JI_BOTTOM); - layout(); - } -} - -void Workspace::adjustTime(int& time, int flag) -{ - if (m_dropArea & flag) { - if (time < ANI_DROPAREA_TICKS) - ++time; - } - else if (time > 0) - --time; -} - -void Workspace::dropViewAt(const gfx::Point& pos, WorkspaceView* view) -{ -} - -int Workspace::calculateDropArea(const gfx::Point& pos) const -{ - gfx::Rect rc = getChildrenBounds(); - if (rc.contains(pos)) { - int left = ABS(rc.x - pos.x); - int top = ABS(rc.y - pos.y); - int right = ABS(rc.x + rc.w - pos.x); - int bottom = ABS(rc.y + rc.h - pos.y); - int threshold = getDropThreshold(); - - if (left < threshold && left < right && left < top && left < bottom) { - return JI_LEFT; - } - else if (top < threshold && top < left && top < right && top < bottom) { - return JI_TOP; - } - else if (right < threshold && right < left && right < top && right < bottom) { - return JI_RIGHT; - } - else if (bottom < threshold && bottom < left && bottom < top && bottom < right) { - return JI_BOTTOM; - } - } - - return 0; -} - -int Workspace::getDropThreshold() const -{ - gfx::Rect cpos = getChildrenBounds(); - int threshold = 32*guiscale(); - if (threshold > cpos.w/2) threshold = cpos.w/2; - if (threshold > cpos.h/2) threshold = cpos.h/2; - return threshold; + return nullptr; } } // namespace app diff --git a/src/app/ui/workspace.h b/src/app/ui/workspace.h index 97a05963e..a9647c3ca 100644 --- a/src/app/ui/workspace.h +++ b/src/app/ui/workspace.h @@ -9,31 +9,24 @@ #define APP_UI_WORKSPACE_H_INCLUDED #pragma once -#include "app/ui/animated_widget.h" -#include "app/ui/workspace_views.h" +#include "app/ui/workspace_panel.h" #include "base/signal.h" #include "ui/widget.h" -#include -#include - namespace app { class Tabs; - class Workspace : public ui::Widget - , public AnimatedWidget { - enum Ani : int { - ANI_NONE, - ANI_DROPAREA, - }; - + class Workspace : public ui::Widget { public: typedef WorkspaceViews::iterator iterator; + static ui::WidgetType Type(); + Workspace(); ~Workspace(); - void setTabsBar(Tabs* tabsBar); + void setTabsBar(Tabs* tabs); + iterator begin() { return m_views.begin(); } iterator end() { return m_views.end(); } @@ -46,31 +39,33 @@ namespace app { WorkspaceView* activeView(); void setActiveView(WorkspaceView* view); + void setMainPanelAsActive(); // Drop views into workspace void setDropViewPreview(const gfx::Point& pos); - void removeDropViewPreview(const gfx::Point& pos); - void dropViewAt(const gfx::Point& pos, WorkspaceView* view); + void removeDropViewPreview(); + + // Returns true if the view was docked inside the workspace. + bool dropViewAt(const gfx::Point& pos, WorkspaceView* view); Signal0 ActiveViewChanged; protected: void onPaint(ui::PaintEvent& ev) override; void onResize(ui::ResizeEvent& ev) override; - void onAnimationFrame() override; - void onAnimationStop() override; private: int calculateDropArea(const gfx::Point& pos) const; int getDropThreshold() const; void adjustTime(int& time, int flag); + WorkspacePanel* getViewPanel(WorkspaceView* view); + WorkspacePanel* getPanelAt(const gfx::Point& pos); - Tabs* m_tabsBar; + WorkspacePanel m_mainPanel; + Tabs* m_tabs; WorkspaceViews m_views; - WorkspaceView* m_activeView; - int m_dropArea; - int m_leftTime, m_rightTime; - int m_topTime, m_bottomTime; + WorkspacePanel* m_activePanel; + WorkspacePanel* m_dropPreviewPanel; }; } // namespace app diff --git a/src/app/ui/workspace_panel.cpp b/src/app/ui/workspace_panel.cpp new file mode 100644 index 000000000..ba2b4cff2 --- /dev/null +++ b/src/app/ui/workspace_panel.cpp @@ -0,0 +1,343 @@ +// Aseprite +// Copyright (C) 2001-2015 David Capello +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/ui/workspace_panel.h" + +#include "app/ui/skin/skin_theme.h" +#include "app/ui/tabs.h" +#include "app/ui/workspace.h" +#include "app/ui/workspace_tabs.h" +#include "app/ui/workspace_view.h" +#include "base/remove_from_container.h" +#include "ui/box.h" +#include "ui/paint_event.h" +#include "ui/resize_event.h" +#include "ui/splitter.h" + +namespace app { + +#define ANI_DROPAREA_TICKS 4 + +using namespace app::skin; +using namespace ui; + +// static +WidgetType WorkspacePanel::Type() +{ + static WidgetType type = kGenericWidget; + if (type == kGenericWidget) + type = register_widget_type(); + return type; +} + +WorkspacePanel::WorkspacePanel(PanelType panelType) + : Widget(WorkspacePanel::Type()) + , m_panelType(panelType) + , m_tabs(nullptr) + , m_activeView(nullptr) + , m_dropArea(0) + , m_leftTime(0) + , m_rightTime(0) + , m_topTime(0) + , m_bottomTime(0) +{ + SkinTheme* theme = static_cast(getTheme()); + setBgColor(theme->colors.workspace()); +} + +WorkspacePanel::~WorkspacePanel() +{ + // No views at this point. + ASSERT(m_views.empty()); +} + +void WorkspacePanel::setTabsBar(Tabs* tabs) +{ + m_tabs = tabs; +} + +void WorkspacePanel::addView(WorkspaceView* view, int pos) +{ + if (pos < 0) + m_views.push_back(view); + else + m_views.insert(m_views.begin()+pos, view); + + if (m_tabs) + m_tabs->addTab(dynamic_cast(view), pos); + + // Insert the view content as a hidden widget. + Widget* content = view->getContentWidget(); + content->setVisible(false); + addChild(content); + + setActiveView(view); +} + +void WorkspacePanel::removeView(WorkspaceView* view) +{ + base::remove_from_container(m_views, view); + + Widget* content = view->getContentWidget(); + ASSERT(hasChild(content)); + removeChild(content); + + // Remove related tab. + if (m_tabs) { + m_tabs->removeTab(dynamic_cast(view), true); + + // The selected + TabView* tabView = m_tabs->getSelectedTab(); + view = dynamic_cast(tabView); + } + else + view = nullptr; + + setActiveView(view); + if (!view) + getWorkspace()->setMainPanelAsActive(); + + // Destroy this panel + if (m_views.empty() && m_panelType == SUB_PANEL) { + Widget* self = getParent(); + ASSERT(self->getType() == kBoxWidget); + + Widget* splitter = self->getParent(); + ASSERT(splitter->getType() == kSplitterWidget); + + Widget* parent = splitter->getParent(); + + Widget* side = + (splitter->getFirstChild() == self ? + splitter->getLastChild(): + splitter->getFirstChild()); + + splitter->removeChild(side); + parent->replaceChild(splitter, side); + + self->deferDelete(); + + parent->layout(); + } +} + +WorkspaceView* WorkspacePanel::activeView() +{ + return m_activeView; +} + +void WorkspacePanel::setActiveView(WorkspaceView* view) +{ + m_activeView = view; + + for (auto v : m_views) + v->getContentWidget()->setVisible(v == view); + + if (m_tabs && view) + m_tabs->selectTab(dynamic_cast(view)); + + adjustActiveViewBounds(); +} + +void WorkspacePanel::onPaint(PaintEvent& ev) +{ + ev.getGraphics()->fillRect(getBgColor(), getClientBounds()); +} + +void WorkspacePanel::onResize(ui::ResizeEvent& ev) +{ + setBoundsQuietly(ev.getBounds()); + adjustActiveViewBounds(); +} + +void WorkspacePanel::adjustActiveViewBounds() +{ + gfx::Rect rc = getChildrenBounds(); + + // Preview to drop tabs in workspace + if (animation() == ANI_DROPAREA) { + double left = double(m_leftTime) / double(ANI_DROPAREA_TICKS); + double top = double(m_topTime) / double(ANI_DROPAREA_TICKS); + double right = double(m_rightTime) / double(ANI_DROPAREA_TICKS); + double bottom = double(m_bottomTime) / double(ANI_DROPAREA_TICKS); + double threshold = getDropThreshold(); + + rc.x += int(inbetween(0.0, threshold, left)); + rc.y += int(inbetween(0.0, threshold, top)); + rc.w -= int(inbetween(0.0, threshold, left) + inbetween(0.0, threshold, right)); + rc.h -= int(inbetween(0.0, threshold, top) + inbetween(0.0, threshold, bottom)); + } + + for (Widget* child : getChildren()) + if (child->isVisible()) + child->setBounds(rc); +} + +void WorkspacePanel::setDropViewPreview(const gfx::Point& pos) +{ + int newDropArea = calculateDropArea(pos); + if (newDropArea != m_dropArea) { + m_dropArea = newDropArea; + startAnimation(ANI_DROPAREA, ANI_DROPAREA_TICKS); + } +} + +void WorkspacePanel::removeDropViewPreview() +{ + if (m_dropArea) { + m_dropArea = 0; + startAnimation(ANI_DROPAREA, ANI_DROPAREA_TICKS); + } +} + +void WorkspacePanel::onAnimationStop() +{ + if (animation() == ANI_DROPAREA) + layout(); +} + +void WorkspacePanel::onAnimationFrame() +{ + if (animation() == ANI_DROPAREA) { + adjustTime(m_leftTime, JI_LEFT); + adjustTime(m_topTime, JI_TOP); + adjustTime(m_rightTime, JI_RIGHT); + adjustTime(m_bottomTime, JI_BOTTOM); + layout(); + } +} + +void WorkspacePanel::adjustTime(int& time, int flag) +{ + if (m_dropArea & flag) { + if (time < ANI_DROPAREA_TICKS) + ++time; + } + else if (time > 0) + --time; +} + +bool WorkspacePanel::dropViewAt(const gfx::Point& pos, WorkspacePanel* from, WorkspaceView* view) +{ + int dropArea = calculateDropArea(pos); + if (!dropArea) + return false; + + // If we're dropping the view in the same panel, and it's the only + // view in the panel: We cannot drop the view in the panel (because + // if we remove the last view, the panel will be destroyed). + if (from == this && m_views.size() == 1) + return false; + + int splitterAlign = 0; + if (dropArea & (JI_LEFT | JI_RIGHT)) splitterAlign = JI_HORIZONTAL; + else if (dropArea & (JI_TOP | JI_BOTTOM)) splitterAlign = JI_VERTICAL; + + ASSERT(from); + from->removeView(view); + + WorkspaceTabs* newTabs = new WorkspaceTabs(m_tabs->getDelegate()); + WorkspacePanel* newPanel = new WorkspacePanel(SUB_PANEL); + newPanel->setTabsBar(newTabs); + newPanel->setExpansive(true); + + Widget* self = this; + VBox* side = new VBox; + side->noBorderNoChildSpacing(); + side->addChild(newTabs); + side->addChild(newPanel); + + Splitter* splitter = new Splitter(Splitter::ByPercentage, splitterAlign); + splitter->setExpansive(true); + + Widget* parent = getParent(); + if (parent->getType() == kBoxWidget) { + self = parent; + parent = self->getParent(); + ASSERT(parent->getType() == kSplitterWidget); + } + if (parent->getType() == Workspace::Type() || + parent->getType() == kSplitterWidget) { + parent->replaceChild(self, splitter); + } + else { + ASSERT(false); + } + + switch (dropArea) { + case JI_LEFT: + case JI_TOP: + splitter->setPosition(30); + splitter->addChild(side); + splitter->addChild(self); + break; + case JI_RIGHT: + case JI_BOTTOM: + splitter->setPosition(70); + splitter->addChild(self); + splitter->addChild(side); + break; + } + + newPanel->addView(view); + parent->layout(); + return true; +} + +int WorkspacePanel::calculateDropArea(const gfx::Point& pos) const +{ + gfx::Rect rc = getChildrenBounds(); + if (rc.contains(pos)) { + int left = ABS(rc.x - pos.x); + int top = ABS(rc.y - pos.y); + int right = ABS(rc.x + rc.w - pos.x); + int bottom = ABS(rc.y + rc.h - pos.y); + int threshold = getDropThreshold(); + + if (left < threshold && left < right && left < top && left < bottom) { + return JI_LEFT; + } + else if (top < threshold && top < left && top < right && top < bottom) { + return JI_TOP; + } + else if (right < threshold && right < left && right < top && right < bottom) { + return JI_RIGHT; + } + else if (bottom < threshold && bottom < left && bottom < top && bottom < right) { + return JI_BOTTOM; + } + } + + return 0; +} + +int WorkspacePanel::getDropThreshold() const +{ + gfx::Rect cpos = getChildrenBounds(); + int threshold = 32*guiscale(); + if (threshold > cpos.w/2) threshold = cpos.w/2; + if (threshold > cpos.h/2) threshold = cpos.h/2; + return threshold; +} + +Workspace* WorkspacePanel::getWorkspace() +{ + Widget* widget = this; + while (widget) { + if (widget->getType() == Workspace::Type()) + return static_cast(widget); + + widget = widget->getParent(); + } + return nullptr; +} + +} // namespace app diff --git a/src/app/ui/workspace_panel.h b/src/app/ui/workspace_panel.h new file mode 100644 index 000000000..d725dca34 --- /dev/null +++ b/src/app/ui/workspace_panel.h @@ -0,0 +1,90 @@ +// Aseprite +// Copyright (C) 2001-2015 David Capello +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. + +#ifndef APP_UI_WORKSPACE_PANEL_H_INCLUDED +#define APP_UI_WORKSPACE_PANEL_H_INCLUDED +#pragma once + +#include "app/ui/animated_widget.h" +#include "app/ui/workspace_views.h" +#include "base/signal.h" +#include "ui/widget.h" + +#include +#include + +namespace app { + class Tabs; + class Workspace; + + class WorkspacePanel : public ui::Widget + , public AnimatedWidget { + enum Ani : int { + ANI_NONE, + ANI_DROPAREA, + }; + + public: + typedef WorkspaceViews::iterator iterator; + + enum PanelType { + MAIN_PANEL, + SUB_PANEL, + }; + + static ui::WidgetType Type(); + + WorkspacePanel(PanelType panelType); + ~WorkspacePanel(); + + void setTabsBar(Tabs* tabs); + + iterator begin() { return m_views.begin(); } + iterator end() { return m_views.end(); } + + bool isEmpty() const { return m_views.empty(); } + + void addView(WorkspaceView* view, int pos = -1); + void removeView(WorkspaceView* view); + + WorkspaceView* activeView(); + void setActiveView(WorkspaceView* view); + + // Drop views into workspace + void setDropViewPreview(const gfx::Point& pos); + void removeDropViewPreview(); + + // Returns true if the view was docked inside the panel. + bool dropViewAt(const gfx::Point& pos, WorkspacePanel* from, WorkspaceView* view); + + protected: + void onPaint(ui::PaintEvent& ev) override; + void onResize(ui::ResizeEvent& ev) override; + void onAnimationFrame() override; + void onAnimationStop() override; + + private: + int calculateDropArea(const gfx::Point& pos) const; + int getDropThreshold() const; + void adjustTime(int& time, int flag); + void adjustActiveViewBounds(); + Workspace* getWorkspace(); + + PanelType m_panelType; + Tabs* m_tabs; + WorkspaceViews m_views; + WorkspaceView* m_activeView; + int m_dropArea; + int m_leftTime, m_rightTime; + int m_topTime, m_bottomTime; + }; + + typedef std::vector WorkspacePanels; + +} // namespace app + +#endif diff --git a/src/app/ui/workspace_tabs.cpp b/src/app/ui/workspace_tabs.cpp new file mode 100644 index 000000000..ec012207f --- /dev/null +++ b/src/app/ui/workspace_tabs.cpp @@ -0,0 +1,32 @@ +// Aseprite +// Copyright (C) 2001-2015 David Capello +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/ui/workspace_tabs.h" + +#include "app/ui/skin/skin_theme.h" + +namespace app { + +using namespace app::skin; +using namespace ui; + +WorkspaceTabs::WorkspaceTabs(TabsDelegate* tabsDelegate) + : Tabs(tabsDelegate) +{ + SkinTheme* theme = static_cast(getTheme()); + setBgColor(theme->colors.workspace()); +} + +WorkspaceTabs::~WorkspaceTabs() +{ +} + +} // namespace app diff --git a/src/app/ui/workspace_tabs.h b/src/app/ui/workspace_tabs.h new file mode 100644 index 000000000..2b1fc306c --- /dev/null +++ b/src/app/ui/workspace_tabs.h @@ -0,0 +1,25 @@ +// Aseprite +// Copyright (C) 2001-2015 David Capello +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. + +#ifndef APP_UI_WORKSPACE_TABS_H_INCLUDED +#define APP_UI_WORKSPACE_TABS_H_INCLUDED +#pragma once + +#include "app/ui/tabs.h" + +namespace app { + class Tabs; + + class WorkspaceTabs : public Tabs { + public: + WorkspaceTabs(TabsDelegate* tabsDelegate); + ~WorkspaceTabs(); + }; + +} // namespace app + +#endif