Add support to dock tabs as other Workspace panels

This commit is contained in:
David Capello 2015-03-31 17:31:45 -03:00
parent 6d107734f8
commit 86afa3a568
17 changed files with 723 additions and 222 deletions

View File

@ -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

View File

@ -183,6 +183,7 @@ Editor::Editor(Document* document, EditorFlags flags)
Editor::~Editor()
{
m_observers.notifyDestroyEditor(this);
m_document->removeObserver(this);
setCustomizationDelegate(NULL);

View File

@ -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) { }

View File

@ -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);

View File

@ -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);

View File

@ -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<WorkspaceView*>(tabView));
return DropTabResult::IGNORE;
m_workspace->removeDropViewPreview();
if (m_workspace->dropViewAt(pos, dynamic_cast<WorkspaceView*>(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

View File

@ -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:

View File

@ -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<MouseMessage*>(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();

View File

@ -15,6 +15,7 @@
#include "ui/widget.h"
#include <vector>
#include <memory>
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<Tab*> TabsList;
typedef std::shared_ptr<Tab> TabPtr;
typedef std::vector<TabPtr> 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<ui::Overlay> m_floatingOverlay;
};
} // namespace app

View File

@ -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.

View File

@ -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 {

View File

@ -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 <algorithm>
#include <queue>
#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<SkinTheme*>(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<TabView*>(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<TabView*>(view));
TabView* tabView = m_tabsBar->getSelectedTab();
setActiveView(dynamic_cast<WorkspaceView*>(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<WorkspacePanel*>(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<WorkspacePanel*>(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

View File

@ -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 <map>
#include <vector>
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<void> 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

View File

@ -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<SkinTheme*>(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<TabView*>(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<TabView*>(view), true);
// The selected
TabView* tabView = m_tabs->getSelectedTab();
view = dynamic_cast<WorkspaceView*>(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<TabView*>(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<Workspace*>(widget);
widget = widget->getParent();
}
return nullptr;
}
} // namespace app

View File

@ -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 <map>
#include <vector>
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<WorkspacePanel*> WorkspacePanels;
} // namespace app
#endif

View File

@ -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<SkinTheme*>(getTheme());
setBgColor(theme->colors.workspace());
}
WorkspaceTabs::~WorkspaceTabs()
{
}
} // namespace app

View File

@ -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