1 | //============================================================================ |
2 | // |
3 | // SSSS tt lll lll |
4 | // SS SS tt ll ll |
5 | // SS tttttt eeee ll ll aaaa |
6 | // SSSS tt ee ee ll ll aa |
7 | // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" |
8 | // SS SS tt ee ll ll aa aa |
9 | // SSSS ttt eeeee llll llll aaaaa |
10 | // |
11 | // Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony |
12 | // and the Stella Team |
13 | // |
14 | // See the file "License.txt" for information on usage and redistribution of |
15 | // this file, and for a DISCLAIMER OF ALL WARRANTIES. |
16 | //============================================================================ |
17 | |
18 | #include "bspf.hxx" |
19 | #include "Command.hxx" |
20 | #include "Dialog.hxx" |
21 | #include "Font.hxx" |
22 | #include "FBSurface.hxx" |
23 | #include "GuiObject.hxx" |
24 | #include "OSystem.hxx" |
25 | |
26 | #include "TimeLineWidget.hxx" |
27 | |
28 | const int HANDLE_W = 3; |
29 | const int HANDLE_H = 3; // size above/below the slider |
30 | |
31 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
32 | TimeLineWidget::TimeLineWidget(GuiObject* boss, const GUI::Font& font, |
33 | int x, int y, int w, int h, |
34 | const string& label, uInt32 labelWidth, int cmd) |
35 | : ButtonWidget(boss, font, x, y, w, h, label, cmd), |
36 | _value(0), |
37 | _valueMin(0), |
38 | _valueMax(0), |
39 | _isDragging(false), |
40 | _labelWidth(labelWidth) |
41 | { |
42 | _flags = Widget::FLAG_ENABLED | Widget::FLAG_TRACK_MOUSE; |
43 | _bgcolor = kDlgColor; |
44 | _bgcolorhi = kDlgColor; |
45 | |
46 | if(!_label.empty() && _labelWidth == 0) |
47 | _labelWidth = _font.getStringWidth(_label); |
48 | |
49 | _w = w + _labelWidth; |
50 | |
51 | _stepValue.reserve(100); |
52 | } |
53 | |
54 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
55 | void TimeLineWidget::setValue(uInt32 value) |
56 | { |
57 | value = BSPF::clamp(value, _valueMin, _valueMax); |
58 | |
59 | if(value != _value) |
60 | { |
61 | _value = value; |
62 | setDirty(); |
63 | sendCommand(_cmd, _value, _id); |
64 | } |
65 | } |
66 | |
67 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
68 | void TimeLineWidget::setMinValue(uInt32 value) |
69 | { |
70 | _valueMin = value; |
71 | } |
72 | |
73 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
74 | void TimeLineWidget::setMaxValue(uInt32 value) |
75 | { |
76 | _valueMax = value; |
77 | } |
78 | |
79 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
80 | void TimeLineWidget::setStepValues(const IntArray& steps) |
81 | { |
82 | _stepValue.clear(); |
83 | |
84 | // If no steps are defined, just use the maximum value |
85 | if(steps.size() > 0) |
86 | { |
87 | // Try to allocate as infrequently as possible |
88 | if(steps.size() > _stepValue.capacity()) |
89 | _stepValue.reserve(2 * steps.size()); |
90 | |
91 | double scale = (_w - _labelWidth - 2 - HANDLE_W*0) / double(steps.back()); |
92 | |
93 | // Skip the very last value; we take care of it outside the end of the loop |
94 | for(uInt32 i = 0; i < steps.size() - 1; ++i) |
95 | _stepValue.push_back(int(steps[i] * scale)); |
96 | |
97 | // Due to integer <-> double conversion, the last value is sometimes |
98 | // slightly less than the maximum value; we assign it manually to fix this |
99 | _stepValue.push_back(_w - _labelWidth - 2 - HANDLE_W*0); |
100 | } |
101 | else |
102 | _stepValue.push_back(0); |
103 | } |
104 | |
105 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
106 | void TimeLineWidget::handleMouseMoved(int x, int y) |
107 | { |
108 | if(isEnabled() && _isDragging && x >= int(_labelWidth)) |
109 | setValue(posToValue(x - _labelWidth)); |
110 | } |
111 | |
112 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
113 | void TimeLineWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount) |
114 | { |
115 | if(isEnabled() && b == MouseButton::LEFT) |
116 | { |
117 | _isDragging = true; |
118 | handleMouseMoved(x, y); |
119 | } |
120 | } |
121 | |
122 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
123 | void TimeLineWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount) |
124 | { |
125 | if(isEnabled() && _isDragging) |
126 | sendCommand(_cmd, _value, _id); |
127 | |
128 | _isDragging = false; |
129 | } |
130 | |
131 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
132 | void TimeLineWidget::handleMouseWheel(int x, int y, int direction) |
133 | { |
134 | if(isEnabled()) |
135 | { |
136 | if(direction < 0 && _value < _valueMax) |
137 | setValue(_value + 1); |
138 | else if(direction > 0 && _value > _valueMin) |
139 | setValue(_value - 1); |
140 | } |
141 | } |
142 | |
143 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
144 | void TimeLineWidget::drawWidget(bool hilite) |
145 | { |
146 | FBSurface& s = _boss->dialog().surface(); |
147 | |
148 | // Draw the label, if any |
149 | if(_labelWidth > 0) |
150 | s.drawString(_font, _label, _x, _y + 2, _labelWidth, |
151 | isEnabled() ? kTextColor : kColor, TextAlign::Left); |
152 | |
153 | int p = valueToPos(_value), |
154 | x = _x + _labelWidth, |
155 | w = _w - _labelWidth; |
156 | |
157 | // Frame the handle |
158 | const int HANDLE_W2 = (HANDLE_W + 1) / 2; |
159 | s.hLine(x + p - HANDLE_W2, _y + 0, x + p - HANDLE_W2 + HANDLE_W, kColorInfo); |
160 | s.vLine(x + p - HANDLE_W2, _y + 1, _y + _h - 2, kColorInfo); |
161 | s.hLine(x + p - HANDLE_W2 + 1, _y + _h - 1, x + p - HANDLE_W2 + 1 + HANDLE_W, kBGColor); |
162 | s.vLine(x + p - HANDLE_W2 + 1 + HANDLE_W, _y + 1, _y + _h - 2, kBGColor); |
163 | // Frame the box |
164 | s.hLine(x, _y + HANDLE_H, x + w - 2, kColorInfo); |
165 | s.vLine(x, _y + HANDLE_H, _y + _h - 2 - HANDLE_H, kColorInfo); |
166 | s.hLine(x + 1, _y + _h - 1 - HANDLE_H, x + w - 1, kBGColor); |
167 | s.vLine(x + w - 1, _y + 1 + HANDLE_H, _y + _h - 2 - HANDLE_H, kBGColor); |
168 | |
169 | // Fill the box |
170 | s.fillRect(x + 1, _y + 1 + HANDLE_H, w - 2, _h - 2 - HANDLE_H * 2, |
171 | !isEnabled() ? kSliderBGColorLo : hilite ? kSliderBGColorHi : kSliderBGColor); |
172 | // Draw the 'bar' |
173 | s.fillRect(x + 1, _y + 1 + HANDLE_H, p, _h - 2 - HANDLE_H * 2, |
174 | !isEnabled() ? kColor : hilite ? kSliderColorHi : kSliderColor); |
175 | |
176 | // Add 4 tickmarks for 5 intervals |
177 | int numTicks = std::min(5, int(_stepValue.size())); |
178 | for(int i = 1; i < numTicks; ++i) |
179 | { |
180 | int idx = int((_stepValue.size() * i + numTicks / 2) / numTicks); |
181 | if(idx > 1) |
182 | { |
183 | int xt = x + valueToPos(idx - 1); |
184 | ColorId color = kNone; |
185 | |
186 | if(isEnabled()) |
187 | { |
188 | if(xt > x + p) |
189 | color = hilite ? kSliderColorHi : kSliderColor; |
190 | else |
191 | color = hilite ? kSliderBGColorHi : kSliderBGColor; |
192 | } |
193 | else |
194 | { |
195 | if(xt > x + p) |
196 | color = kColor; |
197 | else |
198 | color = kSliderBGColorLo; |
199 | } |
200 | s.vLine(xt, _y + _h / 2, _y + _h - 2 - HANDLE_H, color); |
201 | } |
202 | } |
203 | // Draw the handle |
204 | s.fillRect(x + p + 1 - HANDLE_W2, _y + 1, HANDLE_W, _h - 2, |
205 | !isEnabled() ? kColor : hilite ? kSliderColorHi : kSliderColor); |
206 | } |
207 | |
208 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
209 | uInt32 TimeLineWidget::valueToPos(uInt32 value) |
210 | { |
211 | return _stepValue[BSPF::clamp(value, _valueMin, _valueMax)]; |
212 | } |
213 | |
214 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
215 | uInt32 TimeLineWidget::posToValue(uInt32 pos) |
216 | { |
217 | // Find the interval in which 'pos' falls, and then the endpoint which |
218 | // it is closest to |
219 | for(uInt32 i = 0; i < _stepValue.size() - 1; ++i) |
220 | if(pos >= _stepValue[i] && pos <= _stepValue[i+1]) |
221 | return (_stepValue[i+1] - pos) < (pos - _stepValue[i]) ? i+1 : i; |
222 | |
223 | return _valueMax; |
224 | } |
225 | |