mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-16 10:20:50 +00:00
Support to clone tabs using Ctrl+drag (close #634)
This commit is contained in:
parent
441c796c1a
commit
47d2d8f902
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user