1 | // Aseprite UI Library |
2 | // Copyright (C) 2019-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2017 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "gfx/size.h" |
13 | #include "ui/message.h" |
14 | #include "ui/paint_event.h" |
15 | #include "ui/scroll_bar.h" |
16 | #include "ui/theme.h" |
17 | |
18 | namespace ui { |
19 | |
20 | using namespace gfx; |
21 | |
22 | // Internal stuff shared by all scroll-bars (as the user cannot move |
23 | // two scroll-bars at the same time). |
24 | int ScrollBar::m_wherepos = 0; |
25 | int ScrollBar::m_whereclick = 0; |
26 | |
27 | ScrollBar::ScrollBar(int align, ScrollableViewDelegate* delegate) |
28 | : Widget(kViewScrollbarWidget) |
29 | , m_delegate(delegate) |
30 | , m_thumbStyle(nullptr) |
31 | , m_barWidth(0) |
32 | , m_pos(0) |
33 | , m_size(0) |
34 | { |
35 | setAlign(align); |
36 | initTheme(); |
37 | } |
38 | |
39 | void ScrollBar::setPos(int pos) |
40 | { |
41 | if (m_pos != pos) { |
42 | m_pos = pos; |
43 | invalidate(); |
44 | } |
45 | } |
46 | |
47 | void ScrollBar::setSize(int size) |
48 | { |
49 | if (m_size != size) { |
50 | m_size = size; |
51 | invalidate(); |
52 | } |
53 | } |
54 | |
55 | void ScrollBar::getScrollBarThemeInfo(int* pos, int* len) |
56 | { |
57 | getScrollBarInfo(pos, len, nullptr, nullptr); |
58 | } |
59 | |
60 | bool ScrollBar::onProcessMessage(Message* msg) |
61 | { |
62 | #define MOUSE_IN(x1, y1, x2, y2) \ |
63 | ((mousePos.x >= (x1)) && (mousePos.x <= (x2)) && \ |
64 | (mousePos.y >= (y1)) && (mousePos.y <= (y2))) |
65 | |
66 | switch (msg->type()) { |
67 | |
68 | case kMouseDownMessage: { |
69 | gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position(); |
70 | int x1, y1, x2, y2; |
71 | int u1, v1, u2, v2; |
72 | bool ret = false; |
73 | int pos, len; |
74 | |
75 | getScrollBarThemeInfo(&pos, &len); |
76 | |
77 | m_wherepos = pos; |
78 | m_whereclick = (align() & HORIZONTAL) ? |
79 | mousePos.x: |
80 | mousePos.y; |
81 | |
82 | x1 = bounds().x; |
83 | y1 = bounds().y; |
84 | x2 = bounds().x2()-1; |
85 | y2 = bounds().y2()-1; |
86 | |
87 | u1 = x1 + border().left(); |
88 | v1 = y1 + border().top(); |
89 | u2 = x2 - border().right(); |
90 | v2 = y2 - border().bottom(); |
91 | |
92 | Point scroll = m_delegate->viewScroll(); |
93 | |
94 | if (align() & HORIZONTAL) { |
95 | // in the bar |
96 | if (MOUSE_IN(u1+pos, v1, u1+pos+len-1, v2)) { |
97 | // capture mouse |
98 | } |
99 | // left |
100 | else if (MOUSE_IN(x1, y1, u1+pos-1, y2)) { |
101 | scroll.x -= m_delegate->visibleSize().w/2; |
102 | ret = true; |
103 | } |
104 | // right |
105 | else if (MOUSE_IN(u1+pos+len, y1, x2, y2)) { |
106 | scroll.x += m_delegate->visibleSize().w/2; |
107 | ret = true; |
108 | } |
109 | } |
110 | else { |
111 | // in the bar |
112 | if (MOUSE_IN(u1, v1+pos, u2, v1+pos+len-1)) { |
113 | // capture mouse |
114 | } |
115 | // left |
116 | else if (MOUSE_IN(x1, y1, x2, v1+pos-1)) { |
117 | scroll.y -= m_delegate->visibleSize().h/2; |
118 | ret = true; |
119 | } |
120 | // right |
121 | else if (MOUSE_IN(x1, v1+pos+len, x2, y2)) { |
122 | scroll.y += m_delegate->visibleSize().h/2; |
123 | ret = true; |
124 | } |
125 | } |
126 | |
127 | if (ret) { |
128 | m_delegate->setViewScroll(scroll); |
129 | return ret; |
130 | } |
131 | |
132 | setSelected(true); |
133 | captureMouse(); |
134 | |
135 | // Continue to kMouseMoveMessage handler... |
136 | [[fallthrough]]; |
137 | } |
138 | |
139 | case kMouseMoveMessage: |
140 | if (hasCapture()) { |
141 | gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position(); |
142 | int pos, len, bar_size, viewport_size; |
143 | |
144 | getScrollBarInfo(&pos, &len, &bar_size, &viewport_size); |
145 | |
146 | if (bar_size > len) { |
147 | Point scroll = m_delegate->viewScroll(); |
148 | |
149 | if (align() & HORIZONTAL) { |
150 | pos = (m_wherepos + mousePos.x - m_whereclick); |
151 | pos = std::clamp(pos, 0, bar_size - len); |
152 | |
153 | scroll.x = (m_size - viewport_size) * pos / (bar_size - len); |
154 | } |
155 | else { |
156 | pos = (m_wherepos + mousePos.y - m_whereclick); |
157 | pos = std::clamp(pos, 0, bar_size - len); |
158 | |
159 | scroll.y = (m_size - viewport_size) * pos / (bar_size - len); |
160 | } |
161 | |
162 | m_delegate->setViewScroll(scroll); |
163 | } |
164 | return true; |
165 | } |
166 | break; |
167 | |
168 | case kMouseUpMessage: |
169 | setSelected(false); |
170 | releaseMouse(); |
171 | break; |
172 | |
173 | case kMouseEnterMessage: |
174 | case kMouseLeaveMessage: |
175 | // TODO add something to avoid this (theme specific stuff) |
176 | invalidate(); |
177 | break; |
178 | } |
179 | |
180 | return Widget::onProcessMessage(msg); |
181 | } |
182 | |
183 | void ScrollBar::onInitTheme(InitThemeEvent& ev) |
184 | { |
185 | Widget::onInitTheme(ev); |
186 | m_barWidth = theme()->getScrollbarSize(); |
187 | } |
188 | |
189 | void ScrollBar::onPaint(PaintEvent& ev) |
190 | { |
191 | gfx::Rect thumbBounds = clientBounds(); |
192 | if (align() & HORIZONTAL) |
193 | getScrollBarThemeInfo(&thumbBounds.x, &thumbBounds.w); |
194 | else |
195 | getScrollBarThemeInfo(&thumbBounds.y, &thumbBounds.h); |
196 | |
197 | theme()->paintScrollBar( |
198 | ev.graphics(), this, style(), thumbStyle(), |
199 | clientBounds(), thumbBounds); |
200 | } |
201 | |
202 | void ScrollBar::getScrollBarInfo(int *_pos, int *_len, int *_bar_size, int *_viewport_size) |
203 | { |
204 | int bar_size, viewport_size; |
205 | int pos, len; |
206 | int border_width; |
207 | |
208 | if (align() & HORIZONTAL) { |
209 | bar_size = bounds().w; |
210 | viewport_size = m_delegate->visibleSize().w; |
211 | border_width = border().height(); |
212 | } |
213 | else { |
214 | bar_size = bounds().h; |
215 | viewport_size = m_delegate->visibleSize().h; |
216 | border_width = border().width(); |
217 | } |
218 | |
219 | if (m_size <= viewport_size) { |
220 | len = bar_size; |
221 | pos = 0; |
222 | } |
223 | else if (m_size > 0) { |
224 | len = bar_size * viewport_size / m_size; |
225 | len = std::clamp(len, std::min(theme()->getScrollbarSize()*2-border_width, bar_size), bar_size); |
226 | pos = (bar_size-len) * m_pos / (m_size-viewport_size); |
227 | pos = std::clamp(pos, 0, bar_size-len); |
228 | } |
229 | else { |
230 | len = pos = 0; |
231 | } |
232 | |
233 | if (_pos) *_pos = pos; |
234 | if (_len) *_len = len; |
235 | if (_bar_size) *_bar_size = bar_size; |
236 | if (_viewport_size) *_viewport_size = viewport_size; |
237 | } |
238 | |
239 | } // namespace ui |
240 | |