1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/ui/workspace.h"
13
14#include "app/app.h"
15#include "app/ui/input_chain.h"
16#include "app/ui/skin/skin_theme.h"
17#include "app/ui/workspace_tabs.h"
18#include "app/ui/workspace_view.h"
19#include "base/remove_from_container.h"
20#include "ui/paint_event.h"
21#include "ui/resize_event.h"
22
23namespace app {
24
25using namespace app::skin;
26using namespace ui;
27
28// static
29WidgetType Workspace::Type()
30{
31 static WidgetType type = kGenericWidget;
32 if (type == kGenericWidget)
33 type = register_widget_type();
34 return type;
35}
36
37Workspace::Workspace()
38 : Widget(Workspace::Type())
39 , m_mainPanel(WorkspacePanel::MAIN_PANEL)
40 , m_tabs(nullptr)
41 , m_activePanel(&m_mainPanel)
42 , m_dropPreviewPanel(nullptr)
43 , m_dropPreviewTabs(nullptr)
44{
45 enableFlags(IGNORE_MOUSE);
46 addChild(&m_mainPanel);
47
48 InitTheme.connect(
49 [this]{
50 auto theme = SkinTheme::get(this);
51 setBgColor(theme->colors.workspace());
52 });
53 initTheme();
54}
55
56Workspace::~Workspace()
57{
58 // No views at this point.
59 ASSERT(m_views.empty());
60}
61
62void Workspace::setTabsBar(WorkspaceTabs* tabs)
63{
64 m_tabs = tabs;
65 m_mainPanel.setTabsBar(tabs);
66}
67
68void Workspace::addView(WorkspaceView* view, int pos)
69{
70 addViewToPanel(&m_mainPanel, view, false, pos);
71}
72
73void Workspace::removeView(WorkspaceView* view)
74{
75 ASSERT(view);
76 base::remove_from_container(m_views, view);
77
78 WorkspacePanel* panel = getViewPanel(view);
79 ASSERT(panel);
80 if (panel)
81 panel->removeView(view);
82
83 view->onAfterRemoveView(this);
84}
85
86bool Workspace::closeView(WorkspaceView* view, bool quitting)
87{
88 return view->onCloseView(this, quitting);
89}
90
91WorkspaceView* Workspace::activeView()
92{
93 return (m_activePanel ? m_activePanel->activeView(): nullptr);
94}
95
96void Workspace::setActiveView(WorkspaceView* view)
97{
98 m_activePanel = getViewPanel(view);
99 ASSERT(m_activePanel);
100 if (!m_activePanel)
101 return;
102
103 m_activePanel->setActiveView(view);
104
105 ActiveViewChanged(); // Fire ActiveViewChanged event
106}
107
108void Workspace::setMainPanelAsActive()
109{
110 m_activePanel = &m_mainPanel;
111
112 removeDropViewPreview();
113 m_dropPreviewPanel = nullptr;
114 m_dropPreviewTabs = nullptr;
115
116 ActiveViewChanged(); // Fire ActiveViewChanged event
117}
118
119bool Workspace::canSelectOtherTab() const
120{
121 return m_activePanel->tabs()->canSelectOtherTab();
122}
123
124void Workspace::selectNextTab()
125{
126 m_activePanel->tabs()->selectNextTab();
127}
128
129void Workspace::selectPreviousTab()
130{
131 m_activePanel->tabs()->selectPreviousTab();
132}
133
134void Workspace::duplicateActiveView()
135{
136 WorkspaceView* view = activeView();
137 if (!view)
138 return;
139
140 WorkspaceView* clone = view->cloneWorkspaceView();
141 if (!clone)
142 return;
143
144 WorkspacePanel* panel = getViewPanel(view);
145 addViewToPanel(panel, clone, false, -1);
146 clone->onClonedFrom(view);
147 setActiveView(clone);
148}
149
150void Workspace::updateTabs()
151{
152 WidgetsList children = this->children();
153 while (!children.empty()) {
154 Widget* child = children.back();
155 children.erase(--children.end());
156
157 if (child->type() == WorkspacePanel::Type())
158 static_cast<WorkspacePanel*>(child)->tabs()->updateTabs();
159
160 for (auto subchild : child->children())
161 children.push_back(subchild);
162 }
163}
164
165void Workspace::onPaint(PaintEvent& ev)
166{
167 ev.graphics()->fillRect(bgColor(), clientBounds());
168}
169
170void Workspace::onResize(ui::ResizeEvent& ev)
171{
172 setBoundsQuietly(ev.bounds());
173
174 gfx::Rect rc = childrenBounds();
175 for (auto child : children())
176 child->setBounds(rc);
177}
178
179DropViewPreviewResult Workspace::setDropViewPreview(
180 const gfx::Point& screenPos,
181 WorkspaceView* view,
182 WorkspaceTabs* tabs)
183{
184 TabView* tabView = dynamic_cast<TabView*>(view);
185 WorkspaceTabs* newTabs = nullptr;
186 WorkspacePanel* panel = getPanelAt(screenPos);
187 if (!newTabs) {
188 newTabs = getTabsAt(screenPos);
189 // Drop preview is only to drop tabs from a different WorkspaceTabs.
190 if (newTabs == tabs)
191 newTabs = nullptr;
192 }
193
194 if (m_dropPreviewPanel && m_dropPreviewPanel != panel)
195 m_dropPreviewPanel->removeDropViewPreview();
196 if (m_dropPreviewTabs && m_dropPreviewTabs != newTabs)
197 m_dropPreviewTabs->removeDropViewPreview();
198
199 m_dropPreviewPanel = panel;
200 m_dropPreviewTabs = newTabs;
201
202 if (m_dropPreviewPanel)
203 m_dropPreviewPanel->setDropViewPreview(screenPos, view);
204 if (m_dropPreviewTabs)
205 m_dropPreviewTabs->setDropViewPreview(screenPos, tabView);
206
207 if (panel)
208 return DropViewPreviewResult::DROP_IN_PANEL;
209 else if (newTabs)
210 return DropViewPreviewResult::DROP_IN_TABS;
211 else
212 return DropViewPreviewResult::FLOATING;
213}
214
215void Workspace::removeDropViewPreview()
216{
217 if (m_dropPreviewPanel) {
218 m_dropPreviewPanel->removeDropViewPreview();
219 m_dropPreviewPanel = nullptr;
220 }
221
222 if (m_dropPreviewTabs) {
223 m_dropPreviewTabs->removeDropViewPreview();
224 m_dropPreviewTabs = nullptr;
225 }
226}
227
228DropViewAtResult Workspace::dropViewAt(const gfx::Point& screenPos,
229 WorkspaceView* view,
230 const bool clone)
231{
232 WorkspaceTabs* tabs = getTabsAt(screenPos);
233 WorkspacePanel* panel = getPanelAt(screenPos);
234
235 if (panel) {
236 // Create new panel
237 return panel->dropViewAt(screenPos, getViewPanel(view), view, clone);
238 }
239 else if (tabs && tabs != getViewPanel(view)->tabs()) {
240 // Dock tab in other tabs
241 WorkspacePanel* dropPanel = tabs->panel();
242 ASSERT(dropPanel);
243
244 int pos = tabs->getDropTabIndex();
245 DropViewAtResult result;
246
247 WorkspaceView* originalView = view;
248 if (clone) {
249 view = view->cloneWorkspaceView();
250 result = DropViewAtResult::CLONED_VIEW;
251 }
252 else {
253 removeView(view);
254 result = DropViewAtResult::MOVED_TO_OTHER_PANEL;
255 }
256
257 addViewToPanel(dropPanel, view, true, pos);
258
259 if (result == DropViewAtResult::CLONED_VIEW)
260 view->onClonedFrom(originalView);
261
262 return result;
263 }
264 else
265 return DropViewAtResult::NOTHING;
266}
267
268void Workspace::addViewToPanel(WorkspacePanel* panel, WorkspaceView* view, bool from_drop, int pos)
269{
270 panel->addView(view, from_drop, pos);
271
272 m_activePanel = panel;
273 m_views.push_back(view);
274
275 setActiveView(view);
276 layout();
277}
278
279WorkspacePanel* Workspace::getViewPanel(WorkspaceView* view)
280{
281 Widget* widget = view->getContentWidget();
282 while (widget) {
283 if (widget->type() == WorkspacePanel::Type())
284 return static_cast<WorkspacePanel*>(widget);
285
286 widget = widget->parent();
287 }
288 return nullptr;
289}
290
291WorkspacePanel* Workspace::getPanelAt(const gfx::Point& screenPos)
292{
293 Widget* widget = manager()->pickFromScreenPos(screenPos);
294 while (widget) {
295 if (widget->type() == WorkspacePanel::Type())
296 return static_cast<WorkspacePanel*>(widget);
297
298 widget = widget->parent();
299 }
300 return nullptr;
301}
302
303WorkspaceTabs* Workspace::getTabsAt(const gfx::Point& screenPos)
304{
305 Widget* widget = manager()->pickFromScreenPos(screenPos);
306 while (widget) {
307 if (widget->type() == Tabs::Type())
308 return static_cast<WorkspaceTabs*>(widget);
309
310 widget = widget->parent();
311 }
312 return nullptr;
313}
314
315void Workspace::onNewInputPriority(InputChainElement* newElement,
316 const ui::Message* msg)
317{
318 WorkspaceView* view = activeView();
319 InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
320 if (activeElement)
321 activeElement->onNewInputPriority(newElement, msg);
322}
323
324bool Workspace::onCanCut(Context* ctx)
325{
326 WorkspaceView* view = activeView();
327 InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
328 if (activeElement)
329 return activeElement->onCanCut(ctx);
330 else
331 return false;
332}
333
334bool Workspace::onCanCopy(Context* ctx)
335{
336 WorkspaceView* view = activeView();
337 InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
338 if (activeElement)
339 return activeElement->onCanCopy(ctx);
340 else
341 return false;
342}
343
344bool Workspace::onCanPaste(Context* ctx)
345{
346 WorkspaceView* view = activeView();
347 InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
348 if (activeElement)
349 return activeElement->onCanPaste(ctx);
350 else
351 return false;
352}
353
354bool Workspace::onCanClear(Context* ctx)
355{
356 WorkspaceView* view = activeView();
357 InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
358 if (activeElement)
359 return activeElement->onCanClear(ctx);
360 else
361 return false;
362}
363
364bool Workspace::onCut(Context* ctx)
365{
366 WorkspaceView* view = activeView();
367 InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
368 if (activeElement)
369 return activeElement->onCut(ctx);
370 else
371 return false;
372}
373
374bool Workspace::onCopy(Context* ctx)
375{
376 WorkspaceView* view = activeView();
377 InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
378 if (activeElement)
379 return activeElement->onCopy(ctx);
380 else
381 return false;
382}
383
384bool Workspace::onPaste(Context* ctx)
385{
386 WorkspaceView* view = activeView();
387 InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
388 if (activeElement)
389 return activeElement->onPaste(ctx);
390 else
391 return false;
392}
393
394bool Workspace::onClear(Context* ctx)
395{
396 WorkspaceView* view = activeView();
397 InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
398 if (activeElement)
399 return activeElement->onClear(ctx);
400 else
401 return false;
402}
403
404void Workspace::onCancel(Context* ctx)
405{
406 WorkspaceView* view = activeView();
407 InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
408 if (activeElement)
409 activeElement->onCancel(ctx);
410}
411
412} // namespace app
413