1// Aseprite
2// Copyright (C) 2019-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#ifndef APP_UI_TABS_H_INCLUDED
9#define APP_UI_TABS_H_INCLUDED
10#pragma once
11
12#include "base/ref.h"
13#include "ui/animated_widget.h"
14#include "ui/widget.h"
15
16#include <memory>
17#include <vector>
18
19namespace ui {
20 class Graphics;
21 class Overlay;
22}
23
24namespace app {
25 class Tabs;
26
27 enum class TabIcon {
28 NONE,
29 HOME,
30 };
31
32 // Required interface to be implemented by each new tab that is added
33 // in the Tabs widget.
34 class TabView {
35 public:
36 virtual ~TabView() { }
37
38 // Returns the text to be shown in the tab.
39 virtual std::string getTabText() = 0;
40
41 // Returns the icon to be shown in the tab
42 virtual TabIcon getTabIcon() = 0;
43
44 // Returns the tab background color
45 virtual gfx::Color getTabColor() = 0;
46 };
47
48 enum class DropTabResult {
49 // The operation should be handled inside the Tabs widget (if the
50 // tab was dropped in the same Tabs, it will be moved or copied
51 // depending on what the user wants).
52 NOT_HANDLED,
53
54 // The tab was docked in other place, so it must be removed from
55 // the specific Tabs widget.
56 REMOVE,
57
58 // The operation was already handled, but the tab must not be
59 // removed from the Tabs (e.g. because it was cloned).
60 DONT_REMOVE,
61 };
62
63 enum class DropViewPreviewResult {
64 DROP_IN_PANEL,
65 DROP_IN_TABS,
66 FLOATING,
67 };
68
69 // Interface used to control notifications from the Tabs widget.
70 class TabsDelegate {
71 public:
72
73 virtual ~TabsDelegate() { }
74
75 // Returns true if the tab represent a modified document.
76 virtual bool isTabModified(Tabs* tabs, TabView* tabView) = 0;
77
78 // Returns true if the tab can be cloned.
79 virtual bool canCloneTab(Tabs* tabs, TabView* tabView) = 0;
80
81 // Called when the user selected the tab with the left mouse button.
82 virtual void onSelectTab(Tabs* tabs, TabView* tabView) = 0;
83
84 // When the tab close button is pressed (or middle mouse button is used to close it).
85 virtual void onCloseTab(Tabs* tabs, TabView* tabView) = 0;
86
87 // When the tab is cloned (this only happens when the user
88 // drag-and-drop the tab into the same Tabs with the Ctrl key).
89 virtual void onCloneTab(Tabs* tabs, TabView* tabView, int pos) = 0;
90
91 // When the right-click is pressed in the tab.
92 virtual void onContextMenuTab(Tabs* tabs, TabView* tabView) = 0;
93
94 // When the tab bar background is double-clicked.
95 virtual void onTabsContainerDoubleClicked(Tabs* tabs) = 0;
96
97 // Called when the mouse is over a tab (the data can be null if the
98 // mouse just leave all tabs)
99 virtual void onMouseOverTab(Tabs* tabs, TabView* tabView) = 0;
100
101 // Called when the mouse is leaving a tab
102 virtual void onMouseLeaveTab() = 0;
103
104 // Called when the user is dragging a tab outside the Tabs
105 // bar.
106 virtual DropViewPreviewResult onFloatingTab(Tabs* tabs,
107 TabView* tabView,
108 const gfx::Point& screenPos) = 0;
109
110 // Called when the user is dragging a tab inside the Tabs bar.
111 virtual void onDockingTab(Tabs* tabs, TabView* tabView) = 0;
112
113 virtual DropTabResult onDropTab(Tabs* tabs,
114 TabView* tabView,
115 const gfx::Point& screenPos,
116 bool clone) = 0;
117 };
118
119 // Tabs control. Used to show opened documents.
120 class Tabs : public ui::Widget
121 , public ui::AnimatedWidget {
122 struct Tab {
123 TabView* view;
124 std::string text;
125 TabIcon icon;
126 gfx::Color color;
127 int x, width;
128 int oldX, oldWidth;
129 bool modified;
130
131 Tab(TabView* view) : view(view) {
132 ASSERT(view);
133 text = view->getTabText();
134 icon = view->getTabIcon();
135 color = view->getTabColor();
136
137 x = width = oldX = oldWidth =
138#if _DEBUG
139 0xfefefefe;
140#else
141 0;
142#endif
143
144 modified = false;
145 }
146 };
147
148 typedef std::shared_ptr<Tab> TabPtr;
149
150 typedef std::vector<TabPtr> TabsList;
151 typedef TabsList::iterator TabsListIterator;
152
153 enum Ani : int {
154 ANI_NONE,
155 ANI_ADDING_TAB,
156 ANI_REMOVING_TAB,
157 ANI_REORDER_TABS
158 };
159
160 public:
161 static ui::WidgetType Type();
162
163 Tabs(TabsDelegate* delegate);
164 ~Tabs();
165
166 TabsDelegate* getDelegate() { return m_delegate; }
167
168 void addTab(TabView* tabView, bool from_drop, int pos = -1);
169 void removeTab(TabView* tabView, bool with_animation);
170 void updateTabs();
171
172 // Returns true if the user can select other tab.
173 bool canSelectOtherTab() const;
174
175 void selectTab(TabView* tabView);
176 void selectNextTab();
177 void selectPreviousTab();
178 TabView* getSelectedTab();
179
180 void setDockedStyle();
181
182 // Drop TabViews into this Tabs widget
183 void setDropViewPreview(const gfx::Point& screenPos,
184 TabView* view);
185 void removeDropViewPreview();
186 int getDropTabIndex() const { return m_dropNewIndex; }
187
188 protected:
189 bool onProcessMessage(ui::Message* msg) override;
190 void onInitTheme(ui::InitThemeEvent& ev) override;
191 void onPaint(ui::PaintEvent& ev) override;
192 void onResize(ui::ResizeEvent& ev) override;
193 void onSizeHint(ui::SizeHintEvent& ev) override;
194 void onAnimationFrame() override;
195 void onAnimationStop(int animation) override;
196
197 private:
198 void resetOldPositions();
199 void resetOldPositions(double t);
200
201 void selectTabInternal(TabPtr& tab);
202 void drawTab(ui::Graphics* g, const gfx::Rect& box, Tab* tab, int dy, bool hover, bool selected);
203 void drawFiller(ui::Graphics* g, const gfx::Rect& box);
204 TabsListIterator getTabIteratorByView(TabView* tabView);
205 TabPtr getTabByView(TabView* tabView);
206 int getMaxScrollX();
207 void makeTabVisible(Tab* tab);
208 void calculateHot();
209 gfx::Rect getTabCloseButtonBounds(Tab* tab, const gfx::Rect& box);
210 void startDrag();
211 void stopDrag(DropTabResult result);
212 gfx::Rect getTabBounds(Tab* tab);
213 void startReorderTabsAnimation();
214 void startRemoveDragTabAnimation();
215 void createFloatingOverlay(Tab* tab);
216 void destroyFloatingTab();
217 void destroyFloatingOverlay();
218 void updateMouseCursor();
219 void updateDragTabIndexes(int mouseX, bool force_animation);
220 void updateDragCopyCursor(ui::Message* msg);
221
222 // Specific variables about the style
223 int m_border; // Pixels used from the left side to draw the first tab
224 bool m_docked; // True if tabs are inside the workspace (not the main tabs panel)
225 int m_tabsHeight; // Number of pixels in Y-axis for each Tab
226 int m_tabsBottomHeight; // Number of pixels in the bottom part of Tabs widget
227
228 // List of tabs (pointers to Tab instances).
229 TabsList m_list;
230
231 // Which tab has the mouse over.
232 TabPtr m_hot;
233
234 // True if the mouse is above the close button of m_hot tab.
235 bool m_hotCloseButton;
236
237 // True if the user clicked over the close button of m_hot.
238 bool m_clickedCloseButton;
239
240 // Current active tab. When this tab changes, the
241 // TabsDelegate::onSelectTab() is called.
242 TabPtr m_selected;
243
244 // Delegate of notifications
245 TabsDelegate* m_delegate;
246
247 // Variables for animation purposes
248 TabPtr m_addedTab;
249 TabPtr m_removedTab;
250
251 ////////////////////////////////////////
252 // Drag-and-drop
253
254 // True when the user is dragging a tab (the mouse must be
255 // captured when this flag is true). The dragging process doesn't
256 // start immediately, when the user clicks a tab the m_selected
257 // tab is changed, and then there is a threshold after we start
258 // dragging the tab.
259 bool m_isDragging;
260
261 // True when the user is dragging the tab as a copy inside this
262 // same Tabs (e.g. using Ctrl key) This can be true only if
263 // TabsDelegate::canCloneTab() returns true for the tab being
264 // dragged.
265 bool m_dragCopy;
266
267 // A copy of m_selected used only in the dragging process. This
268 // tab is not pointing to the same tab as m_selected. It's a copy
269 // because if m_dragCopy is true, we've to paint m_selected and
270 // m_dragTab separately (as two different tabs).
271 TabPtr m_dragTab;
272
273 // Initial X position where m_dragTab/m_selected was when we
274 // started (mouse down) the drag process.
275 int m_dragTabX;
276
277 // Initial mouse position when we start the dragging process.
278 gfx::Point m_dragMousePos;
279
280 // New position where m_selected is being dragged. It's used to
281 // change the position of m_selected inside the m_list vector in
282 // real-time while the mouse is moved.
283 int m_dragTabIndex;
284
285 // New position where a copyt of m_dragTab will be dropped. It
286 // only makes sense when m_dragCopy is true (the user is pressing
287 // Ctrl key and want to create a copy of the tab).
288 int m_dragCopyIndex;
289
290 // Null if we are dragging the m_dragTab inside this Tabs widget,
291 // or non-null (equal to m_selected indeed), if we're moving the
292 // tab outside the Tabs widget (e.g. to dock the tabs in other
293 // location).
294 TabPtr m_floatingTab;
295
296 // Overlay used to show the floating tab outside the Tabs widget
297 // (this overlay floats next to the mouse cursor). It's destroyed
298 // and recreated every time the tab is put inside or outside the
299 // Tabs widget.
300 base::Ref<ui::Overlay> m_floatingOverlay;
301
302 // Relative mouse position inside the m_dragTab (used to adjust
303 // the m_floatingOverlay precisely).
304 gfx::Point m_floatingOffset;
305
306 ////////////////////////////////////////
307 // Drop new tabs
308
309 // Non-null when a foreign tab (a "TabView" from other "Tabs"
310 // widget), will be dropped in this specific Tabs widget. It's
311 // used only for feedback/UI purposes when the
312 // setDropViewPreview() is called.
313 TabView* m_dropNewTab;
314 int m_dropNewIndex;
315 int m_dropNewPosX;
316 };
317
318} // namespace app
319
320#endif
321