Support to clone tabs using Ctrl+drag (close #634)

This commit is contained in:
David Capello 2015-04-20 13:49:25 -03:00
parent 441c796c1a
commit 47d2d8f902
16 changed files with 327 additions and 137 deletions

View File

@ -181,20 +181,10 @@ TabIcon DataRecoveryView::getTabIcon()
return TabIcon::NONE;
}
WorkspaceView* DataRecoveryView::cloneWorkspaceView()
{
return nullptr; // This view cannot be cloned
}
void DataRecoveryView::onWorkspaceViewSelected()
{
}
void DataRecoveryView::onClonedFrom(WorkspaceView* from)
{
ASSERT(false); // Never called
}
bool DataRecoveryView::onCloseView(Workspace* workspace)
{
workspace->removeView(this);

View File

@ -33,9 +33,7 @@ namespace app {
// WorkspaceView implementation
ui::Widget* getContentWidget() override { return this; }
WorkspaceView* cloneWorkspaceView() override;
void onWorkspaceViewSelected() override;
void onClonedFrom(WorkspaceView* from) override;
bool onCloseView(Workspace* workspace) override;
void onTabPopup(Workspace* workspace) override;

View File

@ -108,11 +108,6 @@ void DevConsoleView::onWorkspaceViewSelected()
m_entry->requestFocus();
}
void DevConsoleView::onClonedFrom(WorkspaceView* from)
{
// Do nothing
}
bool DevConsoleView::onCloseView(Workspace* workspace)
{
workspace->removeView(this);

View File

@ -30,9 +30,9 @@ namespace app {
// WorkspaceView implementation
ui::Widget* getContentWidget() override { return this; }
bool canCloneWorkspaceView() override { return true; }
WorkspaceView* cloneWorkspaceView() override;
void onWorkspaceViewSelected() override;
void onClonedFrom(WorkspaceView* from) override;
bool onCloseView(Workspace* workspace) override;
void onTabPopup(Workspace* workspace) override;

View File

@ -48,6 +48,7 @@ namespace app {
// WorkspaceView implementation
ui::Widget* getContentWidget() override { return this; }
bool canCloneWorkspaceView() override { return true; }
WorkspaceView* cloneWorkspaceView() override;
void onWorkspaceViewSelected() override;
void onClonedFrom(WorkspaceView* from) override;

View File

@ -86,16 +86,6 @@ TabIcon HomeView::getTabIcon()
return TabIcon::HOME;
}
WorkspaceView* HomeView::cloneWorkspaceView()
{
return nullptr; // This view cannot be cloned
}
void HomeView::onClonedFrom(WorkspaceView* from)
{
ASSERT(false); // Never called
}
bool HomeView::onCloseView(Workspace* workspace)
{
workspace->removeView(this);

View File

@ -50,8 +50,6 @@ namespace app {
// WorkspaceView implementation
ui::Widget* getContentWidget() override { return this; }
WorkspaceView* cloneWorkspaceView() override;
void onClonedFrom(WorkspaceView* from) override;
bool onCloseView(Workspace* workspace) override;
void onTabPopup(Workspace* workspace) override;
void onWorkspaceViewSelected() override;

View File

@ -253,7 +253,7 @@ void MainWindow::onActiveViewChange()
configureWorkspaceLayout();
}
bool MainWindow::onIsModified(Tabs* tabs, TabView* tabView)
bool MainWindow::isModifiedTab(Tabs* tabs, TabView* tabView)
{
if (DocumentView* docView = dynamic_cast<DocumentView*>(tabView)) {
Document* document = docView->getDocument();
@ -264,6 +264,14 @@ bool MainWindow::onIsModified(Tabs* tabs, TabView* tabView)
}
}
bool MainWindow::canCloneTab(Tabs* tabs, TabView* tabView)
{
ASSERT(tabView)
WorkspaceView* view = dynamic_cast<WorkspaceView*>(tabView);
return view->canCloneWorkspaceView();
}
void MainWindow::onSelectTab(Tabs* tabs, TabView* tabView)
{
if (!tabView)
@ -282,6 +290,15 @@ void MainWindow::onCloseTab(Tabs* tabs, TabView* tabView)
m_workspace->closeView(view);
}
void MainWindow::onCloneTab(Tabs* tabs, TabView* tabView, int pos)
{
WorkspaceView* view = dynamic_cast<WorkspaceView*>(tabView);
WorkspaceView* copy = view->cloneWorkspaceView();
ASSERT(copy);
m_workspace->addViewToPanel(
static_cast<WorkspaceTabs*>(tabs)->panel(), copy, true, pos);
}
void MainWindow::onContextMenuTab(Tabs* tabs, TabView* tabView)
{
WorkspaceView* view = dynamic_cast<WorkspaceView*>(tabView);
@ -315,14 +332,19 @@ void MainWindow::onDockingTab(Tabs* tabs, TabView* tabView)
m_workspace->removeDropViewPreview();
}
DropTabResult MainWindow::onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos)
DropTabResult MainWindow::onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos, bool clone)
{
m_workspace->removeDropViewPreview();
if (m_workspace->dropViewAt(pos, dynamic_cast<WorkspaceView*>(tabView)))
return DropTabResult::DOCKED_IN_OTHER_PLACE;
DropViewAtResult result =
m_workspace->dropViewAt(pos, dynamic_cast<WorkspaceView*>(tabView), clone);
if (result == DropViewAtResult::MOVED_TO_OTHER_PANEL)
return DropTabResult::REMOVE;
else if (result == DropViewAtResult::CLONED_VIEW)
return DropTabResult::DONT_REMOVE;
else
return DropTabResult::IGNORE;
return DropTabResult::NOT_HANDLED;
}
void MainWindow::configureWorkspaceLayout()

View File

@ -80,14 +80,16 @@ namespace app {
void showDataRecovery(crash::DataRecovery* dataRecovery);
// TabsDelegate implementation.
bool onIsModified(Tabs* tabs, TabView* tabView) override;
bool isModifiedTab(Tabs* tabs, TabView* tabView) override;
bool canCloneTab(Tabs* tabs, TabView* tabView) override;
void onSelectTab(Tabs* tabs, TabView* tabView) override;
void onCloseTab(Tabs* tabs, TabView* tabView) override;
void onCloneTab(Tabs* tabs, TabView* tabView, int pos) override;
void onContextMenuTab(Tabs* tabs, TabView* tabView) override;
void onMouseOverTab(Tabs* tabs, TabView* tabView) override;
DropViewPreviewResult 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;
DropTabResult onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos, bool clone) override;
protected:
bool onProcessMessage(ui::Message* msg) override;

View File

@ -48,14 +48,15 @@ WidgetType Tabs::Type()
Tabs::Tabs(TabsDelegate* delegate)
: Widget(Tabs::Type())
, m_border(2)
, m_docked(false)
, m_hot(nullptr)
, m_hotCloseButton(false)
, m_clickedCloseButton(false)
, m_selected(nullptr)
, m_docked(false)
, m_delegate(delegate)
, m_removedTab(nullptr)
, m_isDragging(false)
, m_dragCopy(false)
, m_dragTab(nullptr)
, m_floatingTab(nullptr)
, m_floatingOverlay(nullptr)
@ -99,7 +100,7 @@ void Tabs::addTab(TabView* tabView, bool from_drop, int pos)
tab->oldX = (from_drop ? m_dropNewPosX-tab->width/2: tab->x);
tab->oldWidth = tab->width;
tab->modified = (m_delegate ? m_delegate->onIsModified(this, tabView): false);
tab->modified = (m_delegate ? m_delegate->isModifiedTab(this, tabView): false);
}
void Tabs::removeTab(TabView* tabView, bool with_animation)
@ -130,7 +131,7 @@ void Tabs::removeTab(TabView* tabView, bool with_animation)
if (with_animation) {
if (m_delegate)
tab->modified = m_delegate->onIsModified(this, tabView);
tab->modified = m_delegate->isModifiedTab(this, tabView);
tab->view = nullptr; // The view will be destroyed after Tabs::removeTab() anyway
resetOldPositions();
@ -154,13 +155,17 @@ void Tabs::updateTabs()
int i = 0;
for (auto& tab : m_list) {
if (tab == m_floatingTab) {
if (tab == m_floatingTab && !m_dragCopy) {
++i;
continue;
}
if (m_dropNewTab && m_dropNewIndex == i)
if ((m_dropNewTab && m_dropNewIndex == i) ||
(m_dragTab && !m_floatingTab &&
m_dragCopy &&
m_dragCopyIndex == i)) {
x += tabWidth;
}
tab->text = tab->view->getTabText();
tab->icon = tab->view->getTabIcon();
@ -257,17 +262,15 @@ void Tabs::setDropViewPreview(const gfx::Point& pos, TabView* view)
else
newIndex = 0;
bool startAni = (m_dropNewIndex != newIndex || m_dropNewTab != view);
bool startAni = (m_dropNewIndex != newIndex ||
m_dropNewTab != view);
m_dropNewIndex = newIndex;
m_dropNewPosX = (pos.x - getBounds().x);
m_dropNewTab = view;
if (startAni) {
resetOldPositions(animationTime());
updateTabs();
startAnimation(ANI_REORDER_TABS, ANI_REORDER_TABS_TICKS);
}
if (startAni)
startReorderTabsAnimation();
else
invalidate();
}
@ -276,9 +279,7 @@ void Tabs::removeDropViewPreview()
{
m_dropNewTab = nullptr;
resetOldPositions(animationTime());
updateTabs();
startAnimation(ANI_REORDER_TABS, ANI_REORDER_TABS_TICKS);
startReorderTabsAnimation();
}
bool Tabs::onProcessMessage(Message* msg)
@ -327,7 +328,7 @@ bool Tabs::onProcessMessage(Message* msg)
if (result != DropViewPreviewResult::DROP_IN_TABS) {
if (!m_floatingOverlay)
createFloatingOverlay(m_selected.get());
m_floatingOverlay->moveOverlay(mousePos - m_dragOffset);
m_floatingOverlay->moveOverlay(mousePos - m_floatingOffset);
}
else {
destroyFloatingOverlay();
@ -343,18 +344,8 @@ bool Tabs::onProcessMessage(Message* msg)
// Docked tab
if (!m_floatingTab) {
m_selected->x = m_dragTabX + delta.x;
int i = (mousePos.x - m_border*guiscale() - getBounds().x) / m_selected->width;
i = MID(0, i, int(m_list.size())-1);
if (i != m_dragTabIndex) {
m_list.erase(m_list.begin()+m_dragTabIndex);
m_list.insert(m_list.begin()+i, m_selected);
m_dragTabIndex = i;
startDockDragTabAnimation();
}
m_dragTab->x = m_dragTabX + delta.x;
updateDragTabIndexes(mousePos.x, false);
if (justDocked)
justDocked->oldX = m_dragTabX + delta.x;
}
@ -375,7 +366,7 @@ bool Tabs::onProcessMessage(Message* msg)
if (m_hot && !hasCapture()) {
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
m_dragMousePos = mouseMsg->position();
m_dragOffset = mouseMsg->position() -
m_floatingOffset = mouseMsg->position() -
(getBounds().getOrigin() + getTabBounds(m_hot.get()).getOrigin());
if (m_hotCloseButton) {
@ -415,13 +406,13 @@ bool Tabs::onProcessMessage(Message* msg)
}
}
else {
DropTabResult result = DropTabResult::IGNORE;
DropTabResult result = DropTabResult::NOT_HANDLED;
if (m_delegate) {
ASSERT(m_selected);
result =
m_delegate->onDropTab(this, m_selected->view,
mouseMsg->position());
result = m_delegate->onDropTab(
this, m_selected->view,
mouseMsg->position(), m_dragCopy);
}
stopDrag(result);
@ -446,6 +437,25 @@ bool Tabs::onProcessMessage(Message* msg)
return true;
}
case kKeyDownMessage:
case kKeyUpMessage: {
TabPtr tab = (m_isDragging ? m_dragTab: m_hot);
bool oldDragCopy = m_dragCopy;
m_dragCopy = ((msg->ctrlPressed() || msg->altPressed()) &&
(tab && m_delegate && m_delegate->canCloneTab(this, tab->view)));
if (oldDragCopy != m_dragCopy) {
updateDragTabIndexes(get_mouse_position().x, true);
updateMouseCursor();
}
break;
}
case kSetCursorMessage:
updateMouseCursor();
return true;
}
return Widget::onProcessMessage(msg);
@ -465,13 +475,24 @@ void Tabs::onPaint(PaintEvent& ev)
// For each tab...
for (TabPtr& tab : m_list) {
if (tab == m_floatingTab)
if (tab == m_floatingTab && !m_dragCopy)
continue;
box = getTabBounds(tab.get());
if (tab != m_selected)
drawTab(g, box, tab.get(), 0, (tab == m_hot), false);
if ((!m_dragTab) ||
(tab->view != m_dragTab->view) ||
(m_dragCopy)) {
int dy = 0;
if (animation() == ANI_ADDING_TAB && tab == m_selected) {
double t = animationTime();
dy = int(box.h - box.h * t);
}
drawTab(g, box, tab.get(), dy,
(tab == m_hot),
(tab == m_selected));
}
box.x = box.x2();
}
@ -486,24 +507,16 @@ void Tabs::onPaint(PaintEvent& ev)
}
// Tab that is being dragged
if (m_selected && m_selected != m_floatingTab) {
double t = animationTime();
TabPtr tab(m_selected);
if (m_dragTab && !m_floatingTab) {
TabPtr tab(m_dragTab);
box = getTabBounds(tab.get());
int dy = 0;
if (animation() == ANI_ADDING_TAB)
dy = int(box.h - box.h * t);
drawTab(g, box, m_selected.get(), dy, (tab == m_hot), true);
drawTab(g, box, tab.get(), 0, true, true);
}
// New tab from other Tab that want to be dropped here.
if (m_dropNewTab) {
SkinTheme* theme = static_cast<SkinTheme*>(this->getTheme());
Tab newTab(m_dropNewTab);
newTab.text = m_dropNewTab->getTabText();
newTab.icon = m_dropNewTab->getTabIcon();
newTab.width = newTab.oldWidth =
(!m_list.empty() ? m_list[0]->width:
@ -608,7 +621,7 @@ void Tabs::drawTab(Graphics* g, const gfx::Rect& _box,
if (m_delegate) {
if (tab->view)
tab->modified = m_delegate->onIsModified(this, tab->view);
tab->modified = m_delegate->isModifiedTab(this, tab->view);
if (tab->modified &&
(!hover || !m_hotCloseButton)) {
@ -764,9 +777,13 @@ void Tabs::startDrag()
updateTabs();
m_isDragging = true;
m_dragTab = m_selected;
m_dragCopy = false;
m_dragTab.reset(new Tab(m_selected->view));
m_dragTab->x = m_selected->x;
m_dragTab->width = m_selected->width;
m_dragTabX = m_selected->x;
m_dragTabIndex = std::find(m_list.begin(), m_list.end(), m_selected) - m_list.begin();
m_dragTabIndex =
m_dragCopyIndex = std::find(m_list.begin(), m_list.end(), m_selected) - m_list.begin();
EditorView::SetScrollUpdateMethod(EditorView::KeepCenter);
}
@ -774,35 +791,65 @@ void Tabs::startDrag()
void Tabs::stopDrag(DropTabResult result)
{
m_isDragging = false;
ASSERT(m_dragTab);
switch (result) {
case DropTabResult::IGNORE:
case DropTabResult::NOT_HANDLED:
case DropTabResult::DONT_REMOVE: {
destroyFloatingTab();
ASSERT(m_selected);
if (m_selected) {
m_selected->oldX = m_selected->x;
m_selected->oldWidth = m_selected->width;
bool localCopy = false;
if (result == DropTabResult::NOT_HANDLED &&
m_dragTab && m_dragCopy && m_delegate) {
ASSERT(m_dragCopyIndex >= 0);
m_delegate->onCloneTab(this, m_dragTab->view, m_dragCopyIndex);
// To animate the new tab created by onCloneTab() from the
// m_dragTab position.
m_list[m_dragCopyIndex]->oldX = m_dragTab->x;
m_list[m_dragCopyIndex]->oldWidth = m_dragTab->width;
localCopy = true;
}
resetOldPositions(animationTime());
updateTabs();
startAnimation(ANI_REORDER_TABS, ANI_REORDER_TABS_TICKS);
break;
case DropTabResult::DOCKED_IN_OTHER_PLACE: {
TabPtr tab = m_dragTab;
m_dragCopy = false;
m_dragCopyIndex = -1;
m_floatingTab.reset();
m_removedTab.reset();
destroyFloatingTab();
startReorderTabsAnimation();
ASSERT(tab.get());
if (tab)
removeTab(tab->view, false);
if (m_selected && m_dragTab && !localCopy) {
if (result == DropTabResult::NOT_HANDLED) {
// To animate m_selected tab from the m_dragTab position
// when we drop the tab in the same Tabs (with no copy)
m_selected->oldX = m_dragTab->x;
m_selected->oldWidth = m_dragTab->width;
}
else {
ASSERT(result == DropTabResult::DONT_REMOVE);
// In this case the tab was copied to other Tabs, so we
// avoid any kind of animation for the m_selected (it stays
// were it's).
m_selected->oldX = m_selected->x;
m_selected->oldWidth = m_selected->width;
}
}
break;
}
case DropTabResult::REMOVE:
m_floatingTab.reset();
m_removedTab.reset();
m_dragCopy = false;
m_dragCopyIndex = -1;
destroyFloatingTab();
ASSERT(m_dragTab.get());
if (m_dragTab)
removeTab(m_dragTab->view, false);
break;
}
m_dragTab.reset();
@ -831,7 +878,7 @@ gfx::Rect Tabs::getTabBounds(Tab* tab)
return box;
}
void Tabs::startDockDragTabAnimation()
void Tabs::startReorderTabsAnimation()
{
resetOldPositions(animationTime());
updateTabs();
@ -903,4 +950,39 @@ void Tabs::destroyFloatingOverlay()
}
}
void Tabs::updateMouseCursor()
{
if (m_dragCopy)
ui::set_mouse_cursor(kArrowPlusCursor);
else
ui::set_mouse_cursor(kArrowCursor);
}
void Tabs::updateDragTabIndexes(int mouseX, bool startAni)
{
if (m_dragTab) {
int i = (mouseX - m_border*guiscale() - getBounds().x) / m_dragTab->width;
if (m_dragCopy) {
i = MID(0, i, int(m_list.size()));
if (i != m_dragCopyIndex) {
m_dragCopyIndex = i;
startAni = true;
}
}
else if (hasMouseOver()) {
i = MID(0, i, int(m_list.size())-1);
if (i != m_dragTabIndex) {
m_list.erase(m_list.begin()+m_dragTabIndex);
m_list.insert(m_list.begin()+i, m_selected);
m_dragTabIndex = i;
startAni = true;
}
}
}
if (startAni)
startReorderTabsAnimation();
}
} // namespace app

View File

@ -45,8 +45,18 @@ namespace app {
};
enum class DropTabResult {
IGNORE,
DOCKED_IN_OTHER_PLACE,
// The operation should be handled inside the Tabs widget (if the
// tab was dropped in the same Tabs, it will be moved or copied
// depending on what the user wants).
NOT_HANDLED,
// The tab was docked in other place, so it must be removed from
// the specific Tabs widget.
REMOVE,
// The operation was already handled, but the tab must not be
// removed from the Tabs (e.g. because it was cloned).
DONT_REMOVE,
};
enum class DropViewPreviewResult {
@ -62,7 +72,10 @@ namespace app {
virtual ~TabsDelegate() { }
// Returns true if the tab represent a modified document.
virtual bool onIsModified(Tabs* tabs, TabView* tabView) = 0;
virtual bool isModifiedTab(Tabs* tabs, TabView* tabView) = 0;
// Returns true if the tab can be cloned.
virtual bool canCloneTab(Tabs* tabs, TabView* tabView) = 0;
// Called when the user selected the tab with the left mouse button.
virtual void onSelectTab(Tabs* tabs, TabView* tabView) = 0;
@ -70,6 +83,10 @@ namespace app {
// When the tab close button is pressed (or middle mouse button is used to close it).
virtual void onCloseTab(Tabs* tabs, TabView* tabView) = 0;
// When the tab is cloned (this only happens when the user
// drag-and-drop the tab into the same Tabs with the Ctrl key).
virtual void onCloneTab(Tabs* tabs, TabView* tabView, int pos) = 0;
// When the right-click is pressed in the tab.
virtual void onContextMenuTab(Tabs* tabs, TabView* tabView) = 0;
@ -84,7 +101,7 @@ namespace app {
// Called when the user is dragging a tab inside the Tabs bar.
virtual void onDockingTab(Tabs* tabs, TabView* tabView) = 0;
virtual DropTabResult onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos) = 0;
virtual DropTabResult onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos, bool clone) = 0;
};
// Tabs control. Used to show opened documents.
@ -99,6 +116,9 @@ namespace app {
bool modified;
Tab(TabView* view) : view(view) {
ASSERT(view);
text = view->getTabText();
icon = view->getTabIcon();
}
};
@ -165,23 +185,35 @@ namespace app {
void startDrag();
void stopDrag(DropTabResult result);
gfx::Rect getTabBounds(Tab* tab);
void startDockDragTabAnimation();
void startReorderTabsAnimation();
void startRemoveDragTabAnimation();
void createFloatingOverlay(Tab* tab);
void destroyFloatingTab();
void destroyFloatingOverlay();
void updateMouseCursor();
void updateDragTabIndexes(int mouseX, bool force_animation);
int m_border;
// Specific variables about the style
int m_border; // Pixels used from the left side to draw the first tab
bool m_docked; // True if tabs are inside the workspace (not the main tabs panel)
int m_tabsHeight; // Number of pixels in Y-axis for each Tab
int m_tabsBottomHeight; // Number of pixels in the bottom part of Tabs widget
// List of tabs (pointers to Tab instances).
TabsList m_list;
TabPtr m_hot;
bool m_hotCloseButton;
bool m_clickedCloseButton;
TabPtr m_selected;
// Style
bool m_docked;
int m_tabsHeight;
int m_tabsBottomHeight;
// Which tab has the mouse over.
TabPtr m_hot;
// True if the mouse is above the close button of m_hot tab.
bool m_hotCloseButton;
// True if the user clicked over the close button of m_hot.
bool m_clickedCloseButton;
// Current active tab. When this tab changes, the
// TabsDelegate::onSelectTab() is called.
TabPtr m_selected;
// Delegate of notifications
TabsDelegate* m_delegate;
@ -189,17 +221,68 @@ namespace app {
// Variables for animation purposes
TabPtr m_removedTab;
////////////////////////////////////////
// Drag-and-drop
// True when the user is dragging a tab (the mouse must be
// captured when this flag is true). The dragging process doesn't
// start immediately, when the user clicks a tab the m_selected
// tab is changed, and then there is a threshold after we start
// dragging the tab.
bool m_isDragging;
// True when the user is dragging the tab as a copy inside this
// same Tabs (e.g. using Ctrl key) This can be true only if
// TabsDelegate::canCloneTab() returns true for the tab being
// dragged.
bool m_dragCopy;
// A copy of m_selected used only in the dragging process. This
// tab is not pointing to the same tab as m_selected. It's a copy
// because if m_dragCopy is true, we've to paint m_selected and
// m_dragTab separately (as two different tabs).
TabPtr m_dragTab;
// Initial X position where m_dragTab/m_selected was when we
// started (mouse down) the drag process.
int m_dragTabX;
// Initial mouse position when we start the dragging process.
gfx::Point m_dragMousePos;
gfx::Point m_dragOffset;
// New position where m_selected is being dragged. It's used to
// change the position of m_selected inside the m_list vector in
// real-time while the mouse is moved.
int m_dragTabIndex;
// New position where a copyt of m_dragTab will be dropped. It
// only makes sense when m_dragCopy is true (the user is pressing
// Ctrl key and want to create a copy of the tab).
int m_dragCopyIndex;
// Null if we are dragging the m_dragTab inside this Tabs widget,
// or non-null (equal to m_selected indeed), if we're moving the
// tab outside the Tabs widget (e.g. to dock the tabs in other
// location).
TabPtr m_floatingTab;
// Overlay used to show the floating tab outside the Tabs widget
// (this overlay floats next to the mouse cursor). It's destroyed
// and recreated every time the tab is put inside or outside the
// Tabs widget.
base::UniquePtr<ui::Overlay> m_floatingOverlay;
// Relative mouse position inside the m_dragTab (used to adjust
// the m_floatingOverlay precisely).
gfx::Point m_floatingOffset;
////////////////////////////////////////
// Drop new tabs
// Non-null when a foreign tab (a "TabView" from other "Tabs"
// widget), will be dropped in this specific Tabs widget. It's
// used only for feedback/UI purposes when the
// setDropViewPreview() is called.
TabView* m_dropNewTab;
int m_dropNewIndex;
int m_dropNewPosX;

View File

@ -198,14 +198,14 @@ void Workspace::removeDropViewPreview()
}
}
bool Workspace::dropViewAt(const gfx::Point& pos, WorkspaceView* view)
DropViewAtResult Workspace::dropViewAt(const gfx::Point& pos, WorkspaceView* view, bool clone)
{
WorkspaceTabs* tabs = getTabsAt(pos);
WorkspacePanel* panel = getPanelAt(pos);
if (panel) {
// Create new panel
return panel->dropViewAt(pos, getViewPanel(view), view);
return panel->dropViewAt(pos, getViewPanel(view), view, clone);
}
else if (tabs && tabs != getViewPanel(view)->tabs()) {
// Dock tab in other tabs
@ -213,13 +213,22 @@ bool Workspace::dropViewAt(const gfx::Point& pos, WorkspaceView* view)
ASSERT(dropPanel);
int pos = tabs->getDropTabIndex();
DropViewAtResult result;
if (clone) {
view = view->cloneWorkspaceView();
result = DropViewAtResult::CLONED_VIEW;
}
else {
removeView(view);
result = DropViewAtResult::MOVED_TO_OTHER_PANEL;
}
removeView(view);
addViewToPanel(dropPanel, view, true, pos);
return true;
return result;
}
else
return false;
return DropViewAtResult::NOTHING;
}
void Workspace::addViewToPanel(WorkspacePanel* panel, WorkspaceView* view, bool from_drop, int pos)

View File

@ -32,6 +32,7 @@ namespace app {
iterator end() { return m_views.end(); }
void addView(WorkspaceView* view, int pos = -1);
void addViewToPanel(WorkspacePanel* panel, WorkspaceView* view, bool from_drop, int pos);
void removeView(WorkspaceView* view);
// Closes the given view. Returns false if the user cancels the
@ -53,7 +54,7 @@ namespace app {
void removeDropViewPreview();
// Returns true if the view was docked inside the workspace.
bool dropViewAt(const gfx::Point& pos, WorkspaceView* view);
DropViewAtResult dropViewAt(const gfx::Point& pos, WorkspaceView* view, bool clone);
Signal0<void> ActiveViewChanged;
@ -62,7 +63,6 @@ namespace app {
void onResize(ui::ResizeEvent& ev) override;
private:
void addViewToPanel(WorkspacePanel* panel, WorkspaceView* view, bool from_drop, int pos);
WorkspacePanel* getViewPanel(WorkspaceView* view);
WorkspacePanel* getPanelAt(const gfx::Point& pos);
WorkspaceTabs* getTabsAt(const gfx::Point& pos);

View File

@ -225,24 +225,33 @@ void WorkspacePanel::adjustTime(int& time, int flag)
--time;
}
bool WorkspacePanel::dropViewAt(const gfx::Point& pos, WorkspacePanel* from, WorkspaceView* view)
DropViewAtResult WorkspacePanel::dropViewAt(const gfx::Point& pos, WorkspacePanel* from, WorkspaceView* view, bool clone)
{
int dropArea = calculateDropArea(pos);
if (!dropArea)
return false;
return DropViewAtResult::NOTHING;
// 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;
if (!clone && from == this && m_views.size() == 1)
return DropViewAtResult::NOTHING;
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);
DropViewAtResult result;
Workspace* workspace = getWorkspace();
if (clone) {
view = view->cloneWorkspaceView();
result = DropViewAtResult::CLONED_VIEW;
}
else {
workspace->removeView(view);
result = DropViewAtResult::MOVED_TO_OTHER_PANEL;
}
WorkspaceTabs* newTabs = new WorkspaceTabs(m_tabs->getDelegate());
WorkspacePanel* newPanel = new WorkspacePanel(SUB_PANEL);
@ -294,9 +303,9 @@ bool WorkspacePanel::dropViewAt(const gfx::Point& pos, WorkspacePanel* from, Wor
break;
}
newPanel->addView(view, true);
workspace->addViewToPanel(newPanel, view, true, -1);
parent->layout();
return true;
return result;
}
int WorkspacePanel::calculateDropArea(const gfx::Point& pos) const

View File

@ -21,6 +21,12 @@ namespace app {
class Workspace;
class WorkspaceTabs;
enum class DropViewAtResult {
NOTHING,
MOVED_TO_OTHER_PANEL,
CLONED_VIEW,
};
class WorkspacePanel : public ui::Widget
, public AnimatedWidget {
enum Ani : int {
@ -60,7 +66,7 @@ namespace app {
void removeDropViewPreview();
// Returns true if the view was docked inside the panel.
bool dropViewAt(const gfx::Point& pos, WorkspacePanel* from, WorkspaceView* view);
DropViewAtResult dropViewAt(const gfx::Point& pos, WorkspacePanel* from, WorkspaceView* view, bool clone);
protected:
void onPaint(ui::PaintEvent& ev) override;

View File

@ -21,13 +21,18 @@ namespace app {
virtual ~WorkspaceView() { }
virtual ui::Widget* getContentWidget() = 0;
virtual WorkspaceView* cloneWorkspaceView() = 0;
virtual void onWorkspaceViewSelected() = 0;
virtual bool canCloneWorkspaceView() { return false; }
virtual WorkspaceView* cloneWorkspaceView() { return nullptr; }
// Called after the view is added in the correct position inside
// the workspace. It can be used to copy/clone scroll position
// from the original view.
virtual void onClonedFrom(WorkspaceView* from) = 0;
virtual void onClonedFrom(WorkspaceView* from) {
// Do nothing
}
virtual void onWorkspaceViewSelected() = 0;
// Returns true if the view was closed successfully or false if
// the user cancels the operation.