Show handles in dockable areas to drag-and-drop them

This commit is contained in:
David Capello 2024-12-23 10:08:47 -03:00
parent d0457cc1f4
commit d9415fdf1b
9 changed files with 242 additions and 40 deletions

View File

@ -104,6 +104,7 @@ public:
// Dockable impl // Dockable impl
int dockableAt() const override { return ui::TOP | ui::BOTTOM; } int dockableAt() const override { return ui::TOP | ui::BOTTOM; }
int dockHandleSide() const override { return ui::LEFT; }
// Signals // Signals
obs::signal<void()> BrushChange; obs::signal<void()> BrushChange;

View File

@ -99,6 +99,22 @@ Dock::Dock()
initTheme(); initTheme();
} }
void Dock::setCustomizing(bool enable, bool doLayout)
{
m_customizing = enable;
for (int i = 0; i < kSides; ++i) {
auto child = m_sides[i];
if (!child)
continue;
else if (auto subdock = dynamic_cast<Dock*>(child))
subdock->setCustomizing(enable, false);
}
if (doLayout)
layout();
}
void Dock::resetDocks() void Dock::resetDocks()
{ {
for (int i = 0; i < kSides; ++i) { for (int i = 0; i < kSides; ++i) {
@ -166,6 +182,7 @@ void Dock::dockRelativeTo(ui::Widget* relative,
Dock* subdock = new Dock; Dock* subdock = new Dock;
subdock->m_autoDelete = true; subdock->m_autoDelete = true;
subdock->m_customizing = m_customizing;
parent->replaceChild(relative, subdock); parent->replaceChild(relative, subdock);
subdock->dock(CENTER, relative); subdock->dock(CENTER, relative);
subdock->dock(side, widget, prefSize); subdock->dock(side, widget, prefSize);
@ -241,6 +258,7 @@ Dock* Dock::subdock(int side)
auto oldWidget = m_sides[i]; auto oldWidget = m_sides[i];
auto newSubdock = new Dock; auto newSubdock = new Dock;
newSubdock->m_autoDelete = true; newSubdock->m_autoDelete = true;
newSubdock->m_customizing = m_customizing;
setSide(i, newSubdock); setSide(i, newSubdock);
if (oldWidget) { if (oldWidget) {
@ -280,16 +298,72 @@ void Dock::onResize(ui::ResizeEvent& ev)
updateDockVisibility(); updateDockVisibility();
forEachSide(bounds, forEachSide(bounds,
[bounds](ui::Widget* widget, [this](ui::Widget* widget,
const gfx::Rect& widgetBounds, const gfx::Rect& widgetBounds,
const gfx::Rect& separator, const gfx::Rect& separator,
const int index) { widget->setBounds(widgetBounds); }); const int index) {
auto rc = widgetBounds;
auto th = textHeight();
if (isCustomizing()) {
int handleSide = 0;
if (auto dockable = dynamic_cast<Dockable*>(widget))
handleSide = dockable->dockHandleSide();
switch (handleSide) {
case ui::TOP:
rc.y += th;
rc.h -= th;
break;
case ui::LEFT:
rc.x += th;
rc.w -= th;
break;
}
}
widget->setBounds(rc);
});
} }
void Dock::onPaint(ui::PaintEvent& ev) void Dock::onPaint(ui::PaintEvent& ev)
{ {
Graphics* g = ev.graphics(); Graphics* g = ev.graphics();
g->fillRect(bgColor(), clientBounds()); gfx::Rect bounds = clientBounds();
g->fillRect(bgColor(), bounds);
if (isCustomizing()) {
forEachSide(bounds,
[this, g](ui::Widget* widget,
const gfx::Rect& widgetBounds,
const gfx::Rect& separator,
const int index) {
auto rc = widgetBounds;
auto th = textHeight();
if (isCustomizing()) {
auto theme = SkinTheme::get(this);
auto color = theme->colors.workspaceText();
int handleSide = 0;
if (auto dockable = dynamic_cast<Dockable*>(widget))
handleSide = dockable->dockHandleSide();
switch (handleSide) {
case ui::TOP:
rc.h = th;
for (int y = rc.y; y + 1 < rc.y2(); y += 2)
g->drawHLine(color,
rc.x + widget->border().left(),
y,
rc.w - widget->border().width());
break;
case ui::LEFT:
rc.w = th;
for (int x = rc.x; x + 1 < rc.x2(); x += 2)
g->drawVLine(color,
x,
rc.y + widget->border().top(),
rc.h - widget->border().height());
break;
}
}
});
}
} }
void Dock::onInitTheme(ui::InitThemeEvent& ev) void Dock::onInitTheme(ui::InitThemeEvent& ev)
@ -305,22 +379,18 @@ bool Dock::onProcessMessage(ui::Message* msg)
case kMouseDownMessage: { case kMouseDownMessage: {
const gfx::Point pos = static_cast<MouseMessage*>(msg)->position(); const gfx::Point pos = static_cast<MouseMessage*>(msg)->position();
m_capturedSide = -1; if (m_hit.sideIndex >= 0 || m_hit.dockable) {
forEachSide(childrenBounds(), m_startPos = pos;
[this, pos](ui::Widget* widget,
const gfx::Rect& widgetBounds, if (m_hit.sideIndex >= 0) {
const gfx::Rect& separator, m_startSize = m_sizes[m_hit.sideIndex];
const int index) { }
if (separator.contains(pos)) {
m_capturedWidget = widget;
m_capturedSide = index;
m_startSize = m_sizes[index];
m_startPos = pos;
}
});
if (m_capturedSide >= 0) {
captureMouse(); captureMouse();
if (m_hit.dockable)
invalidate();
return true; return true;
} }
break; break;
@ -328,11 +398,11 @@ bool Dock::onProcessMessage(ui::Message* msg)
case kMouseMoveMessage: { case kMouseMoveMessage: {
if (hasCapture()) { if (hasCapture()) {
if (m_capturedSide >= 0) { if (m_hit.sideIndex >= 0) {
const gfx::Point pos = static_cast<MouseMessage*>(msg)->position(); const gfx::Point pos = static_cast<MouseMessage*>(msg)->position();
gfx::Size& sz = m_sizes[m_capturedSide]; gfx::Size& sz = m_sizes[m_hit.sideIndex];
switch (m_capturedSide) { switch (m_hit.sideIndex) {
case kTopIndex: sz.h = (m_startSize.h + pos.y - m_startPos.y); break; case kTopIndex: sz.h = (m_startSize.h + pos.y - m_startPos.y); break;
case kBottomIndex: sz.h = (m_startSize.h - pos.y + m_startPos.y); break; case kBottomIndex: sz.h = (m_startSize.h - pos.y + m_startPos.y); break;
case kLeftIndex: sz.w = (m_startSize.w + pos.x - m_startPos.x); break; case kLeftIndex: sz.w = (m_startSize.w + pos.x - m_startPos.x); break;
@ -342,6 +412,9 @@ bool Dock::onProcessMessage(ui::Message* msg)
layout(); layout();
Resize(); Resize();
} }
else if (m_hit.dockable) {
invalidate();
}
} }
break; break;
} }
@ -357,20 +430,71 @@ bool Dock::onProcessMessage(ui::Message* msg)
case kSetCursorMessage: { case kSetCursorMessage: {
const gfx::Point pos = static_cast<MouseMessage*>(msg)->position(); const gfx::Point pos = static_cast<MouseMessage*>(msg)->position();
ui::CursorType cursor = ui::kArrowCursor; ui::CursorType cursor = ui::kArrowCursor;
forEachSide(childrenBounds(),
[pos, &cursor](ui::Widget* widget, if (!hasCapture())
const gfx::Rect& widgetBounds, m_hit = calcHit(pos);
const gfx::Rect& separator,
const int index) { if (m_hit.sideIndex >= 0) {
if (separator.contains(pos)) { switch (m_hit.sideIndex) {
if (index == kTopIndex || index == kBottomIndex) { case kTopIndex:
cursor = ui::kSizeNSCursor; case kBottomIndex: cursor = ui::kSizeNSCursor; break;
} case kLeftIndex:
else if (index == kLeftIndex || index == kRightIndex) { case kRightIndex: cursor = ui::kSizeWECursor; break;
cursor = ui::kSizeWECursor; }
} }
} else if (m_hit.dockable) {
}); cursor = ui::kMoveCursor;
}
#if 0
m_hit = Hit();
forEachSide(
childrenBounds(),
[this, pos, &cursor](ui::Widget* widget,
const gfx::Rect& widgetBounds,
const gfx::Rect& separator,
const int index) {
if (separator.contains(pos)) {
m_hit.widget = widget;
m_hit.sideIndex = index;
if (index == kTopIndex || index == kBottomIndex) {
cursor = ui::kSizeNSCursor;
}
else if (index == kLeftIndex || index == kRightIndex) {
cursor = ui::kSizeWECursor;
}
}
else if (isCustomizing()) {
auto th = textHeight();
auto rc = widgetBounds;
auto theme = SkinTheme::get(this);
auto color = theme->colors.workspaceText();
if (auto dockable = dynamic_cast<Dockable*>(widget)) {
int handleSide = dockable->dockHandleSide();
switch (handleSide) {
case ui::TOP:
rc.h = th;
if (rc.contains(pos)) {
cursor = ui::kMoveCursor;
m_hit.widget = widget;
m_hit.dockable = dockable;
}
break;
case ui::LEFT:
rc.w = th;
if (rc.contains(pos)) {
cursor = ui::kMoveCursor;
m_hit.widget = widget;
m_hit.dockable = dockable;
}
break;
}
}
}
});
#endif
ui::set_mouse_cursor(cursor); ui::set_mouse_cursor(cursor);
return true; return true;
} }
@ -526,4 +650,44 @@ void Dock::forEachSide(gfx::Rect bounds,
} }
} }
Dock::Hit Dock::calcHit(const gfx::Point& pos)
{
Hit hit;
forEachSide(childrenBounds(),
[this, pos, &hit](ui::Widget* widget,
const gfx::Rect& widgetBounds,
const gfx::Rect& separator,
const int index) {
if (separator.contains(pos)) {
hit.widget = widget;
hit.sideIndex = index;
}
else if (isCustomizing()) {
auto th = textHeight();
auto rc = widgetBounds;
auto theme = SkinTheme::get(this);
if (auto dockable = dynamic_cast<Dockable*>(widget)) {
int handleSide = dockable->dockHandleSide();
switch (handleSide) {
case ui::TOP:
rc.h = th;
if (rc.contains(pos)) {
hit.widget = widget;
hit.dockable = dockable;
}
break;
case ui::LEFT:
rc.w = th;
if (rc.contains(pos)) {
hit.widget = widget;
hit.dockable = dockable;
}
break;
}
}
}
});
return hit;
}
} // namespace app } // namespace app

View File

@ -19,6 +19,8 @@
namespace app { namespace app {
class Dockable;
class DockTabs : public ui::Widget { class DockTabs : public ui::Widget {
public: public:
protected: protected:
@ -33,6 +35,9 @@ public:
Dock(); Dock();
bool isCustomizing() const { return m_customizing; }
void setCustomizing(bool enable, bool doLayout = true);
void resetDocks(); void resetDocks();
// side = ui::LEFT, or ui::RIGHT, etc. // side = ui::LEFT, or ui::RIGHT, etc.
@ -80,16 +85,28 @@ private:
bool hasVisibleSide(const int i) const { return (m_sides[i] && m_sides[i]->isVisible()); } bool hasVisibleSide(const int i) const { return (m_sides[i] && m_sides[i]->isVisible()); }
struct Hit {
ui::Widget* widget = nullptr;
Dockable* dockable = nullptr;
int sideIndex = -1;
};
Hit calcHit(const gfx::Point& pos);
std::array<Widget*, kSides> m_sides; std::array<Widget*, kSides> m_sides;
std::array<int, kSides> m_aligns; std::array<int, kSides> m_aligns;
std::array<gfx::Size, kSides> m_sizes; std::array<gfx::Size, kSides> m_sizes;
bool m_autoDelete = false; bool m_autoDelete = false;
// Used to drag-and-drop sides. // Use to drag-and-drop stuff (splitters and dockable widgets)
ui::Widget* m_capturedWidget = nullptr; Hit m_hit;
int m_capturedSide;
// Used to resize sizes splitters.
gfx::Size m_startSize; gfx::Size m_startSize;
gfx::Point m_startPos; gfx::Point m_startPos;
// True when we paint/can drag-and-drop dockable widgets from handles.
bool m_customizing = false;
}; };
} // namespace app } // namespace app

View File

@ -26,6 +26,10 @@ public:
{ {
return ui::LEFT | ui::TOP | ui::RIGHT | ui::BOTTOM | ui::CENTER | ui::EXPANSIVE; return ui::LEFT | ui::TOP | ui::RIGHT | ui::BOTTOM | ui::CENTER | ui::EXPANSIVE;
} }
// Returns the preferred side where the dock handle to move the
// widget should be.
virtual int dockHandleSide() const { return ui::TOP; }
}; };
} // namespace app } // namespace app

View File

@ -382,6 +382,9 @@ void LayoutSelector::switchSelector()
m_comboBox.setSizeHint(m_startSize); m_comboBox.setSizeHint(m_startSize);
startAnimation((expand ? ANI_EXPANDING : ANI_COLLAPSING), ANI_TICKS); startAnimation((expand ? ANI_EXPANDING : ANI_COLLAPSING), ANI_TICKS);
MainWindow* win = App::instance()->mainWindow();
win->setCustomizeDock(expand);
} }
void LayoutSelector::switchSelectorFromCommand() void LayoutSelector::switchSelectorFromCommand()

View File

@ -450,6 +450,11 @@ void MainWindow::loadUserLayout(const Layout* layout)
this->layout(); this->layout();
} }
void MainWindow::setCustomizeDock(bool enable)
{
m_customizableDock->setCustomizing(enable);
}
void MainWindow::dataRecoverySessionsAreReady() void MainWindow::dataRecoverySessionsAreReady()
{ {
getHomeView()->dataRecoverySessionsAreReady(); getHomeView()->dataRecoverySessionsAreReady();

View File

@ -96,6 +96,7 @@ public:
void setMirroredDefaultLayout(); void setMirroredDefaultLayout();
void loadUserLayout(const Layout* layout); void loadUserLayout(const Layout* layout);
const Dock* customizableDock() const { return m_customizableDock; } const Dock* customizableDock() const { return m_customizableDock; }
void setCustomizeDock(bool enable);
// When crash::DataRecovery finish to search for sessions, this // When crash::DataRecovery finish to search for sessions, this
// function is called. // function is called.

View File

@ -76,6 +76,7 @@ public:
// Dockable impl // Dockable impl
int dockableAt() const override { return ui::TOP | ui::BOTTOM; } int dockableAt() const override { return ui::TOP | ui::BOTTOM; }
int dockHandleSide() const override { return ui::LEFT; }
protected: protected:
void onInitTheme(ui::InitThemeEvent& ev) override; void onInitTheme(ui::InitThemeEvent& ev) override;

View File

@ -19,7 +19,8 @@ namespace app {
class WorkspaceTabs; class WorkspaceTabs;
class Workspace : public ui::Widget, class Workspace : public ui::Widget,
public app::InputChainElement { public app::InputChainElement,
public Dockable {
public: public:
typedef WorkspaceViews::iterator iterator; typedef WorkspaceViews::iterator iterator;
@ -75,6 +76,11 @@ public:
WorkspacePanel* mainPanel() { return &m_mainPanel; } WorkspacePanel* mainPanel() { return &m_mainPanel; }
// Dockable impl
int dockableAt() const override { return 0; }
int dockHandleSide() const override { return 0; } // No handles
// Signals
obs::signal<void()> BeforeViewChanged; obs::signal<void()> BeforeViewChanged;
obs::signal<void()> ActiveViewChanged; obs::signal<void()> ActiveViewChanged;