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 | |
19 | namespace ui { |
20 | class Graphics; |
21 | class Overlay; |
22 | } |
23 | |
24 | namespace 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 (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 | |