1// Aseprite UI Library
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2001-2016 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 "ui/slider.h"
13
14#include "os/font.h"
15#include "ui/message.h"
16#include "ui/size_hint_event.h"
17#include "ui/system.h"
18#include "ui/theme.h"
19#include "ui/widget.h"
20
21#include <algorithm>
22#include <cstdio>
23#include <cstdlib>
24
25namespace ui {
26
27static int slider_press_x;
28static int slider_press_value;
29static bool slider_press_left;
30
31Slider::Slider(int min, int max, int value, SliderDelegate* delegate)
32 : Widget(kSliderWidget)
33 , m_min(min)
34 , m_max(max)
35 , m_value(std::clamp(value, min, max))
36 , m_readOnly(false)
37 , m_delegate(delegate)
38{
39 setFocusStop(true);
40 initTheme();
41}
42
43void Slider::setRange(int min, int max)
44{
45 m_min = min;
46 m_max = max;
47 m_value = std::clamp(m_value, min, max);
48
49 invalidate();
50}
51
52void Slider::setValue(int value)
53{
54 int old_value = m_value;
55
56 m_value = std::clamp(value, m_min, m_max);
57
58 if (m_value != old_value)
59 invalidate();
60
61 // It DOES NOT emit CHANGE signal! to avoid recursive calls.
62}
63
64void Slider::getSliderThemeInfo(int* min, int* max, int* value) const
65{
66 if (min) *min = m_min;
67 if (max) *max = m_max;
68 if (value) *value = m_value;
69}
70
71std::string Slider::convertValueToText(int value) const
72{
73 if (m_delegate)
74 return m_delegate->onGetTextFromValue(value);
75 else {
76 char buf[128];
77 std::sprintf(buf, "%d", value);
78 return buf;
79 }
80}
81
82int Slider::convertTextToValue(const std::string& text) const
83{
84 if (m_delegate)
85 return m_delegate->onGetValueFromText(text);
86 else {
87 return std::strtol(text.c_str(), nullptr, 10);
88 }
89}
90
91bool Slider::onProcessMessage(Message* msg)
92{
93 switch (msg->type()) {
94
95 case kFocusEnterMessage:
96 case kFocusLeaveMessage:
97 if (isEnabled())
98 invalidate();
99 break;
100
101 case kMouseDownMessage:
102 if (!isEnabled() || isReadOnly())
103 return true;
104
105 setSelected(true);
106 captureMouse();
107
108 {
109 gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
110 slider_press_x = mousePos.x;
111 slider_press_value = m_value;
112 slider_press_left = static_cast<MouseMessage*>(msg)->left();
113 }
114
115 setupSliderCursor();
116
117 [[fallthrough]];
118
119 case kMouseMoveMessage:
120 if (hasCapture()) {
121 int value, accuracy, range;
122 gfx::Rect rc = childrenBounds();
123 gfx::Point mousePos = static_cast<MouseMessage*>(msg)->positionForDisplay(display());
124
125 range = m_max - m_min + 1;
126
127 // With left click
128 if (slider_press_left) {
129 value = m_min + range * (mousePos.x - rc.x) / rc.w;
130 }
131 // With right click
132 else {
133 accuracy = std::clamp(rc.w / range, 1, rc.w);
134
135 value = slider_press_value +
136 (mousePos.x - slider_press_x) / accuracy;
137 }
138
139 value = std::clamp(value, m_min, m_max);
140 if (m_value != value) {
141 setValue(value);
142 onChange();
143 }
144
145 return true;
146 }
147 break;
148
149 case kMouseUpMessage:
150 if (hasCapture()) {
151 setSelected(false);
152 releaseMouse();
153 setupSliderCursor();
154
155 onSliderReleased();
156 }
157 break;
158
159 case kMouseEnterMessage:
160 case kMouseLeaveMessage:
161 // TODO theme stuff
162 if (isEnabled())
163 invalidate();
164 break;
165
166 case kKeyDownMessage:
167 if (hasFocus() && !isReadOnly()) {
168 int value = m_value;
169
170 switch (static_cast<KeyMessage*>(msg)->scancode()) {
171 case kKeyLeft: --value; break;
172 case kKeyRight: ++value; break;
173 case kKeyPageDown: value -= (m_max-m_min+1)/4; break;
174 case kKeyPageUp: value += (m_max-m_min+1)/4; break;
175 case kKeyHome: value = m_min; break;
176 case kKeyEnd: value = m_max; break;
177 default:
178 goto not_used;
179 }
180
181 value = std::clamp(value, m_min, m_max);
182 if (m_value != value) {
183 setValue(value);
184 onChange();
185 }
186
187 return true;
188 }
189 break;
190
191 case kMouseWheelMessage:
192 if (isEnabled() && !isReadOnly()) {
193 int value = m_value
194 + static_cast<MouseMessage*>(msg)->wheelDelta().x
195 - static_cast<MouseMessage*>(msg)->wheelDelta().y;
196
197 value = std::clamp(value, m_min, m_max);
198
199 if (m_value != value) {
200 this->setValue(value);
201 onChange();
202 }
203 return true;
204 }
205 break;
206
207 case kSetCursorMessage:
208 setupSliderCursor();
209 return true;
210 }
211
212not_used:;
213 return Widget::onProcessMessage(msg);
214}
215
216void Slider::onPaint(PaintEvent& ev)
217{
218 theme()->paintSlider(ev);
219}
220
221void Slider::onChange()
222{
223 Change(); // Emit Change signal
224}
225
226void Slider::onSliderReleased()
227{
228 SliderReleased();
229}
230
231void Slider::setupSliderCursor()
232{
233 if (hasCapture()) {
234 if (slider_press_left)
235 set_mouse_cursor(kArrowCursor);
236 else
237 set_mouse_cursor(kSizeWECursor);
238 }
239 else
240 set_mouse_cursor(kArrowCursor);
241}
242
243} // namespace ui
244