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
28const int HANDLE_W = 3;
29const int HANDLE_H = 3; // size above/below the slider
30
31// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
32TimeLineWidget::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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
55void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
68void TimeLineWidget::setMinValue(uInt32 value)
69{
70 _valueMin = value;
71}
72
73// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
74void TimeLineWidget::setMaxValue(uInt32 value)
75{
76 _valueMax = value;
77}
78
79// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
80void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
106void TimeLineWidget::handleMouseMoved(int x, int y)
107{
108 if(isEnabled() && _isDragging && x >= int(_labelWidth))
109 setValue(posToValue(x - _labelWidth));
110}
111
112// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
113void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
123void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
132void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
144void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
209uInt32 TimeLineWidget::valueToPos(uInt32 value)
210{
211 return _stepValue[BSPF::clamp(value, _valueMin, _valueMax)];
212}
213
214// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
215uInt32 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