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
18namespace ui {
19
20using namespace gfx;
21
22// Internal stuff shared by all scroll-bars (as the user cannot move
23// two scroll-bars at the same time).
24int ScrollBar::m_wherepos = 0;
25int ScrollBar::m_whereclick = 0;
26
27ScrollBar::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
39void ScrollBar::setPos(int pos)
40{
41 if (m_pos != pos) {
42 m_pos = pos;
43 invalidate();
44 }
45}
46
47void ScrollBar::setSize(int size)
48{
49 if (m_size != size) {
50 m_size = size;
51 invalidate();
52 }
53}
54
55void ScrollBar::getScrollBarThemeInfo(int* pos, int* len)
56{
57 getScrollBarInfo(pos, len, nullptr, nullptr);
58}
59
60bool 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
183void ScrollBar::onInitTheme(InitThemeEvent& ev)
184{
185 Widget::onInitTheme(ev);
186 m_barWidth = theme()->getScrollbarSize();
187}
188
189void 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
202void 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