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// Based on code from ScummVM - Scumm Interpreter
18// Copyright (C) 2002-2004 The ScummVM project
19//============================================================================
20
21#include "bspf.hxx"
22#include "Command.hxx"
23#include "Dialog.hxx"
24#include "FBSurface.hxx"
25#include "GuiObject.hxx"
26#include "OSystem.hxx"
27
28#include "Widget.hxx"
29
30// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
31Widget::Widget(GuiObject* boss, const GUI::Font& font,
32 int x, int y, int w, int h)
33 : GuiObject(boss->instance(), boss->parent(), boss->dialog(), x, y, w, h),
34 _boss(boss),
35 _font(font),
36 _id(0),
37 _flags(0),
38 _hasFocus(false),
39 _bgcolor(kWidColor),
40 _bgcolorhi(kWidColor),
41 _bgcolorlo(kBGColorLo),
42 _textcolor(kTextColor),
43 _textcolorhi(kTextColorHi),
44 _textcolorlo(kBGColorLo),
45 _shadowcolor(kShadowColor)
46{
47 // Insert into the widget list of the boss
48 _next = _boss->_firstWidget;
49 _boss->_firstWidget = this;
50
51 _fontWidth = _font.getMaxCharWidth();
52 _fontHeight = _font.getLineHeight();
53
54 setDirty();
55}
56
57// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
58Widget::~Widget()
59{
60 delete _next;
61 _next = nullptr;
62
63 _focusList.clear();
64}
65
66// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
67void Widget::setDirty()
68{
69 // A widget being dirty indicates that its parent dialog is dirty
70 // So we inform the parent about it
71 _boss->dialog().setDirty();
72}
73
74// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
75void Widget::draw()
76{
77 if(!isVisible() || !_boss->isVisible())
78 return;
79
80 FBSurface& s = _boss->dialog().surface();
81
82 bool onTop = _boss->dialog().isOnTop();
83
84 bool hasBorder = _flags & Widget::FLAG_BORDER; // currently only used by Dialog widget
85 int oldX = _x, oldY = _y;
86
87 // Account for our relative position in the dialog
88 _x = getAbsX();
89 _y = getAbsY();
90
91 // Clear background (unless alpha blending is enabled)
92 if(_flags & Widget::FLAG_CLEARBG)
93 {
94 int x = _x, y = _y, w = _w, h = _h;
95 if(hasBorder)
96 {
97 x++; y++; w-=2; h-=2;
98 }
99 s.fillRect(x, y, w, h, !onTop ? _bgcolorlo : (_flags & Widget::FLAG_HILITED) && isEnabled() ? _bgcolorhi : _bgcolor);
100 }
101
102 // Draw border
103 if(hasBorder)
104 {
105 s.frameRect(_x, _y, _w, _h, !onTop ? kColor : (_flags & Widget::FLAG_HILITED) && isEnabled() ? kWidColorHi : kColor);
106 _x += 4;
107 _y += 4;
108 _w -= 8;
109 _h -= 8;
110 }
111
112 // Now perform the actual widget draw
113 drawWidget((_flags & Widget::FLAG_HILITED) ? true : false);
114
115 // Restore x/y
116 if (hasBorder)
117 {
118 _x -= 4;
119 _y -= 4;
120 _w += 8;
121 _h += 8;
122 }
123
124 _x = oldX;
125 _y = oldY;
126
127 // Draw all children
128 Widget* w = _firstWidget;
129 while(w)
130 {
131 w->draw();
132 w = w->_next;
133 }
134}
135
136// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
137void Widget::receivedFocus()
138{
139 if(_hasFocus)
140 return;
141
142 _hasFocus = true;
143 setFlags(Widget::FLAG_HILITED);
144 receivedFocusWidget();
145}
146
147// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
148void Widget::lostFocus()
149{
150 if(!_hasFocus)
151 return;
152
153 _hasFocus = false;
154 clearFlags(Widget::FLAG_HILITED);
155 lostFocusWidget();
156}
157
158// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
159void Widget::setEnabled(bool e)
160{
161 if(e) setFlags(Widget::FLAG_ENABLED);
162 else clearFlags(Widget::FLAG_ENABLED);
163}
164
165// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
166Widget* Widget::findWidgetInChain(Widget* w, int x, int y)
167{
168 while(w)
169 {
170 // Stop as soon as we find a widget that contains the point (x,y)
171 if(x >= w->_x && x < w->_x + w->_w && y >= w->_y && y < w->_y + w->_h)
172 break;
173 w = w->_next;
174 }
175
176 if(w)
177 w = w->findWidget(x - w->_x, y - w->_y);
178
179 return w;
180}
181
182// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
183bool Widget::isWidgetInChain(Widget* w, Widget* find)
184{
185 while(w)
186 {
187 // Stop as soon as we find the widget
188 if(w == find) return true;
189 w = w->_next;
190 }
191 return false;
192}
193
194// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
195bool Widget::isWidgetInChain(WidgetArray& list, Widget* find)
196{
197 for(const auto& w: list)
198 if(w == find)
199 return true;
200
201 return false;
202}
203
204// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
205Widget* Widget::setFocusForChain(GuiObject* boss, WidgetArray& arr,
206 Widget* wid, int direction,
207 bool emitFocusEvents)
208{
209 FBSurface& s = boss->dialog().surface();
210 int size = int(arr.size()), pos = -1;
211 Widget* tmp;
212 bool onTop = boss->dialog().isOnTop();
213
214 for(int i = 0; i < size; ++i)
215 {
216 tmp = arr[i];
217
218 // Determine position of widget 'w'
219 if(wid == tmp)
220 pos = i;
221
222 // Get area around widget
223 // Note: we must use getXXX() methods and not access the variables
224 // directly, since in some cases (notably those widgets with embedded
225 // ScrollBars) the two quantities may be different
226 int x = tmp->getAbsX() - 1, y = tmp->getAbsY() - 1,
227 w = tmp->getWidth() + 2, h = tmp->getHeight() + 2;
228
229 // First clear area surrounding all widgets
230 if(tmp->_hasFocus)
231 {
232 if(emitFocusEvents)
233 tmp->lostFocus();
234 else
235 tmp->_hasFocus = false;
236
237 s.frameRect(x, y, w, h, onTop ? kDlgColor : kBGColorLo);
238
239 tmp->setDirty();
240 }
241 }
242
243 // Figure out which which should be active
244 if(pos == -1)
245 return nullptr;
246 else
247 {
248 int oldPos = pos;
249 do
250 {
251 switch(direction)
252 {
253 case -1: // previous widget
254 pos--;
255 if(pos < 0)
256 pos = size - 1;
257 break;
258
259 case +1: // next widget
260 pos++;
261 if(pos >= size)
262 pos = 0;
263 break;
264
265 default:
266 // pos already set
267 break;
268 }
269 // break if all widgets should be disabled
270 if(oldPos == pos)
271 break;
272 } while(!arr[pos]->isEnabled());
273 }
274
275 // Now highlight the active widget
276 tmp = arr[pos];
277
278 // Get area around widget
279 // Note: we must use getXXX() methods and not access the variables
280 // directly, since in some cases (notably those widgets with embedded
281 // ScrollBars) the two quantities may be different
282 int x = tmp->getAbsX() - 1, y = tmp->getAbsY() - 1,
283 w = tmp->getWidth() + 2, h = tmp->getHeight() + 2;
284
285 if(emitFocusEvents)
286 tmp->receivedFocus();
287 else {
288 tmp->_hasFocus = true;
289 tmp->setFlags(Widget::FLAG_HILITED);
290 }
291
292 if (onTop)
293 s.frameRect(x, y, w, h, kWidFrameColor, FrameStyle::Dashed);
294
295 tmp->setDirty();
296
297 return tmp;
298}
299
300// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
301void Widget::setDirtyInChain(Widget* start)
302{
303 while(start)
304 {
305 start->setDirty();
306 start = start->_next;
307 }
308}
309
310// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
311StaticTextWidget::StaticTextWidget(GuiObject* boss, const GUI::Font& font,
312 int x, int y, int w, int h,
313 const string& text, TextAlign align,
314 ColorId shadowColor)
315 : Widget(boss, font, x, y, w, h),
316 _align(align)
317{
318 _flags = Widget::FLAG_ENABLED;
319 _bgcolor = kDlgColor;
320 _bgcolorhi = kDlgColor;
321 _textcolor = kTextColor;
322 _textcolorhi = kTextColor;
323 _shadowcolor = shadowColor;
324
325 _label = text;
326 _editable = false;
327}
328
329// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
330StaticTextWidget::StaticTextWidget(GuiObject* boss, const GUI::Font& font,
331 int x, int y,
332 const string& text, TextAlign align,
333 ColorId shadowColor)
334 : StaticTextWidget(boss, font, x, y, font.getStringWidth(text), font.getLineHeight(),
335 text, align, shadowColor)
336{
337}
338
339// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
340void StaticTextWidget::setValue(int value)
341{
342 char buf[256];
343 std::snprintf(buf, 255, "%d", value);
344 _label = buf;
345
346 setDirty();
347}
348
349// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
350void StaticTextWidget::setLabel(const string& label)
351{
352 _label = label;
353
354 setDirty();
355}
356
357// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
358void StaticTextWidget::drawWidget(bool hilite)
359{
360 FBSurface& s = _boss->dialog().surface();
361 bool onTop = _boss->dialog().isOnTop();
362 s.drawString(_font, _label, _x, _y, _w,
363 isEnabled() && onTop ? _textcolor : kColor, _align, 0, true, _shadowcolor);
364
365 setDirty();
366}
367
368// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
369ButtonWidget::ButtonWidget(GuiObject* boss, const GUI::Font& font,
370 int x, int y, int w, int h,
371 const string& label, int cmd, bool repeat)
372 : StaticTextWidget(boss, font, x, y, w, h, label, TextAlign::Center),
373 CommandSender(boss),
374 _cmd(cmd),
375 _repeat(repeat),
376 _useBitmap(false),
377 _bitmap(nullptr),
378 _bmw(0),
379 _bmh(0)
380{
381 _flags = Widget::FLAG_ENABLED | Widget::FLAG_CLEARBG;
382 _bgcolor = kBtnColor;
383 _bgcolorhi = kBtnColorHi;
384 _bgcolorlo = kColor;
385 _textcolor = kBtnTextColor;
386 _textcolorhi = kBtnTextColorHi;
387 _textcolorlo = kBGColorLo;
388
389 _editable = false;
390}
391
392// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
393ButtonWidget::ButtonWidget(GuiObject* boss, const GUI::Font& font,
394 int x, int y, int dw,
395 const string& label, int cmd, bool repeat)
396 : ButtonWidget(boss, font, x, y, font.getStringWidth(label) + dw, font.getLineHeight() + 4, label, cmd, repeat)
397{
398}
399
400// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
401ButtonWidget::ButtonWidget(GuiObject* boss, const GUI::Font& font,
402 int x, int y,
403 const string& label, int cmd, bool repeat)
404 : ButtonWidget(boss, font, x, y, 20, label, cmd, repeat)
405{
406}
407
408// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
409ButtonWidget::ButtonWidget(GuiObject* boss, const GUI::Font& font,
410 int x, int y, int w, int h,
411 uInt32* bitmap, int bmw, int bmh,
412 int cmd, bool repeat)
413 : ButtonWidget(boss, font, x, y, w, h, "", cmd, repeat)
414{
415 _bitmap = bitmap;
416 _bmh = bmh;
417 _bmw = bmw;
418 _useBitmap = true;
419}
420
421// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
422void ButtonWidget::handleMouseEntered()
423{
424 setFlags(Widget::FLAG_HILITED);
425}
426
427// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
428void ButtonWidget::handleMouseLeft()
429{
430 clearFlags(Widget::FLAG_HILITED);
431}
432
433// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
434bool ButtonWidget::handleEvent(Event::Type e)
435{
436 if(!isEnabled())
437 return false;
438
439 switch(e)
440 {
441 case Event::UISelect:
442 // Simulate mouse event
443 handleMouseUp(0, 0, MouseButton::LEFT, 0);
444 return true;
445 default:
446 return false;
447 }
448}
449
450bool ButtonWidget::handleMouseClicks(int x, int y, MouseButton b)
451{
452 return _repeat;
453}
454
455// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
456void ButtonWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount)
457{
458 if(_repeat && isEnabled() && x >= 0 && x < _w && y >= 0 && y < _h)
459 {
460 clearFlags(Widget::FLAG_HILITED);
461 sendCommand(_cmd, 0, _id);
462 }
463}
464
465// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
466void ButtonWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount)
467{
468 if (!_repeat && isEnabled() && x >= 0 && x < _w && y >= 0 && y < _h)
469 {
470 clearFlags(Widget::FLAG_HILITED);
471 sendCommand(_cmd, 0, _id);
472 }
473}
474
475// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
476void ButtonWidget::setBitmap(uInt32* bitmap, int bmw, int bmh)
477{
478 _bitmap = bitmap;
479 _bmh = bmh;
480 _bmw = bmw;
481 _useBitmap = true;
482
483 setDirty();
484}
485
486// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
487void ButtonWidget::drawWidget(bool hilite)
488{
489 FBSurface& s = _boss->dialog().surface();
490 bool onTop = _boss->dialog().isOnTop();
491
492 s.frameRect(_x, _y, _w, _h, !onTop ? kShadowColor : hilite && isEnabled() ? kBtnBorderColorHi : kBtnBorderColor);
493
494 if (!_useBitmap)
495 s.drawString(_font, _label, _x, _y + (_h - _fontHeight)/2 + 1, _w,
496 !(isEnabled() && onTop) ? _textcolorlo :
497 hilite ? _textcolorhi : _textcolor, _align);
498 else
499 s.drawBitmap(_bitmap, _x + (_w - _bmw) / 2, _y + (_h - _bmh) / 2,
500 !(isEnabled() && onTop) ? _textcolorlo :
501 hilite ? _textcolorhi : _textcolor,
502 _bmw, _bmh);
503
504 setDirty();
505}
506
507// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
508/* 8x8 checkbox bitmap */
509static uInt32 checked_img_active[10] =
510{
511 0b1111111111,
512 0b1111111111,
513 0b1111111111,
514 0b1111111111,
515 0b1111111111,
516 0b1111111111,
517 0b1111111111,
518 0b1111111111,
519 0b1111111111,
520 0b1111111111
521};
522
523static uInt32 checked_img_inactive[10] =
524{
525 0b1111111111,
526 0b1111111111,
527 0b1111001111,
528 0b1110000111,
529 0b1100000011,
530 0b1100000011,
531 0b1110000111,
532 0b1111001111,
533 0b1111111111,
534 0b1111111111
535};
536
537static uInt32 checked_img_circle[10] =
538{
539 0b0001111000,
540 0b0111111110,
541 0b0111111110,
542 0b1111111111,
543 0b1111111111,
544 0b1111111111,
545 0b1111111111,
546 0b0111111110,
547 0b0111111110,
548 0b0001111000
549};
550
551// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
552CheckboxWidget::CheckboxWidget(GuiObject* boss, const GUI::Font& font,
553 int x, int y, const string& label,
554 int cmd)
555 : ButtonWidget(boss, font, x, y, 16, 16, label, cmd),
556 _state(false),
557 _holdFocus(true),
558 _drawBox(true),
559 _changed(false),
560 _fillColor(kColor),
561 _boxY(0),
562 _textY(0)
563{
564 _flags = Widget::FLAG_ENABLED;
565 _bgcolor = _bgcolorhi = kWidColor;
566 _bgcolorlo = kDlgColor;
567
568 _editable = true;
569
570 if(label == "")
571 _w = 14;
572 else
573 _w = font.getStringWidth(label) + 20;
574 _h = font.getFontHeight() < 14 ? 14 : font.getFontHeight();
575
576
577 // Depending on font size, either the font or box will need to be
578 // centered vertically
579 if(_h > 14) // center box
580 _boxY = (_h - 14) / 2;
581 else // center text
582 _textY = (14 - _font.getFontHeight()) / 2;
583
584 setFill(CheckboxWidget::FillType::Normal);
585}
586
587// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
588void CheckboxWidget::handleMouseEntered()
589{
590 setFlags(Widget::FLAG_HILITED);
591}
592
593// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
594void CheckboxWidget::handleMouseLeft()
595{
596 clearFlags(Widget::FLAG_HILITED);
597}
598
599// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
600void CheckboxWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount)
601{
602 if(isEnabled() && _editable && x >= 0 && x < _w && y >= 0 && y < _h)
603 {
604 toggleState();
605
606 // We only send a command when the widget has been changed interactively
607 sendCommand(_cmd, _state, _id);
608 }
609}
610
611// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
612void CheckboxWidget::setEditable(bool editable)
613{
614 _editable = editable;
615 if(_editable)
616 {
617 _bgcolor = kWidColor;
618 }
619 else
620 {
621 _bgcolor = kBGColorHi;
622 setFill(CheckboxWidget::FillType::Inactive);
623 }
624 setDirty();
625}
626
627// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
628void CheckboxWidget::setFill(FillType type)
629{
630 switch(type)
631 {
632 case CheckboxWidget::FillType::Normal:
633 _img = checked_img_active;
634 _drawBox = true;
635 break;
636 case CheckboxWidget::FillType::Inactive:
637 _img = checked_img_inactive;
638 _drawBox = true;
639 break;
640 case CheckboxWidget::FillType::Circle:
641 _img = checked_img_circle;
642 _drawBox = false;
643 break;
644 }
645 setDirty();
646}
647
648// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
649void CheckboxWidget::setState(bool state, bool changed)
650{
651 if(_state != state)
652 {
653 _state = state;
654 setDirty();
655 }
656 _changed = changed;
657}
658
659// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
660void CheckboxWidget::drawWidget(bool hilite)
661{
662 FBSurface& s = _boss->dialog().surface();
663 bool onTop = _boss->dialog().isOnTop();
664
665 if(_drawBox)
666 s.frameRect(_x, _y + _boxY, 14, 14, onTop && hilite && isEnabled() && isEditable() ? kWidColorHi : kColor);
667 // Do we draw a square or cross?
668 s.fillRect(_x + 1, _y + _boxY + 1, 12, 12,
669 _changed ? onTop ? kDbgChangedColor : kDlgColor :
670 isEnabled() && onTop ? _bgcolor : kDlgColor);
671 if(_state)
672 s.drawBitmap(_img, _x + 2, _y + _boxY + 2, onTop && isEnabled() ? hilite && isEditable() ? kWidColorHi : kCheckColor
673 : kColor, 10);
674
675 // Finally draw the label
676 s.drawString(_font, _label, _x + 20, _y + _textY, _w,
677 onTop && isEnabled() ? kTextColor : kColor);
678
679 setDirty();
680}
681
682// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
683SliderWidget::SliderWidget(GuiObject* boss, const GUI::Font& font,
684 int x, int y, int w, int h,
685 const string& label, int labelWidth, int cmd,
686 int valueLabelWidth, const string& valueUnit, int valueLabelGap)
687 : ButtonWidget(boss, font, x, y, w, h, label, cmd),
688 _value(-1),
689 _stepValue(1),
690 _valueMin(0),
691 _valueMax(100),
692 _isDragging(false),
693 _labelWidth(labelWidth),
694 _valueLabel(""),
695 _valueUnit(valueUnit),
696 _valueLabelGap(valueLabelGap),
697 _valueLabelWidth(valueLabelWidth),
698 _numIntervals(0)
699{
700 _flags = Widget::FLAG_ENABLED | Widget::FLAG_TRACK_MOUSE;
701 _bgcolor = kDlgColor;
702 _bgcolorhi = kDlgColor;
703
704 if(!_label.empty() && _labelWidth == 0)
705 _labelWidth = _font.getStringWidth(_label);
706
707 if(_valueLabelWidth == 0)
708 _valueLabelGap = 0;
709
710 _w = w + _labelWidth + _valueLabelGap + _valueLabelWidth;
711}
712
713// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
714SliderWidget::SliderWidget(GuiObject* boss, const GUI::Font& font,
715 int x, int y,
716 const string& label, int labelWidth, int cmd,
717 int valueLabelWidth, const string& valueUnit, int valueLabelGap)
718 : SliderWidget(boss, font, x, y, font.getMaxCharWidth() * 10, font.getLineHeight(),
719 label, labelWidth, cmd, valueLabelWidth, valueUnit, valueLabelGap)
720{
721}
722
723// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
724void SliderWidget::setValue(int value)
725{
726 if(value < _valueMin) value = _valueMin;
727 else if(value > _valueMax) value = _valueMax;
728
729 if(value != _value)
730 {
731 _value = value;
732 setDirty();
733 if (_valueLabelWidth)
734 setValueLabel(_value); // update label
735 sendCommand(_cmd, _value, _id);
736 }
737}
738
739// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
740void SliderWidget::setMinValue(int value)
741{
742 _valueMin = value;
743 setDirty();
744}
745
746// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
747void SliderWidget::setMaxValue(int value)
748{
749 _valueMax = value;
750 setDirty();
751}
752
753// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
754void SliderWidget::setStepValue(int value)
755{
756 _stepValue = value;
757 setDirty();
758}
759
760// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
761void SliderWidget::setValueLabel(const string& valueLabel)
762{
763 _valueLabel = valueLabel;
764 setDirty();
765}
766
767// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
768void SliderWidget::setValueLabel(int value)
769{
770 char buf[256];
771 std::snprintf(buf, 255, "%d", value);
772 _valueLabel = buf;
773
774 setDirty();
775}
776
777// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
778void SliderWidget::setValueUnit(const string& valueUnit)
779{
780 _valueUnit = valueUnit;
781 setDirty();
782}
783
784// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
785void SliderWidget::setTickmarkIntervals(int numIntervals)
786{
787 _numIntervals = numIntervals;
788 setDirty();
789}
790
791// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
792void SliderWidget::handleMouseMoved(int x, int y)
793{
794 // TODO: when the mouse is dragged outside the widget, the slider should
795 // snap back to the old value.
796 if(isEnabled() && _isDragging &&
797 x >= int(_labelWidth - 4) && x <= int(_w - _valueLabelGap - _valueLabelWidth + 4))
798 setValue(posToValue(x - _labelWidth));
799}
800
801// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
802void SliderWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount)
803{
804 if(isEnabled() && b == MouseButton::LEFT)
805 {
806 _isDragging = true;
807 handleMouseMoved(x, y);
808 }
809}
810
811// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
812void SliderWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount)
813{
814 if(isEnabled() && _isDragging)
815 sendCommand(_cmd, _value, _id);
816
817 _isDragging = false;
818}
819
820// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
821void SliderWidget::handleMouseWheel(int x, int y, int direction)
822{
823 if(isEnabled())
824 {
825 if(direction < 0)
826 handleEvent(Event::UIUp);
827 else if(direction > 0)
828 handleEvent(Event::UIDown);
829 }
830}
831
832// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
833bool SliderWidget::handleEvent(Event::Type e)
834{
835 if(!isEnabled())
836 return false;
837
838 switch(e)
839 {
840 case Event::UIDown:
841 case Event::UILeft:
842 case Event::UIPgDown:
843 setValue(_value - _stepValue);
844 break;
845
846 case Event::UIUp:
847 case Event::UIRight:
848 case Event::UIPgUp:
849 setValue(_value + _stepValue);
850 break;
851
852 case Event::UIHome:
853 setValue(_valueMin);
854 break;
855
856 case Event::UIEnd:
857 setValue(_valueMax);
858 break;
859
860 default:
861 return false;
862 }
863 return true;
864}
865
866// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
867void SliderWidget::drawWidget(bool hilite)
868{
869 FBSurface& s = _boss->dialog().surface();
870
871 // Draw the label, if any
872 if(_labelWidth > 0)
873 s.drawString(_font, _label, _x, _y + 2, _labelWidth, isEnabled() ? kTextColor : kColor);
874
875 int p = valueToPos(_value),
876 h = _h - 10,
877 x = _x + _labelWidth,
878 y = _y + (_h - h) / 2 + 1;
879
880 // Fill the box
881 s.fillRect(x, y, _w - _labelWidth - _valueLabelGap - _valueLabelWidth, h,
882 !isEnabled() ? kSliderBGColorLo : hilite ? kSliderBGColorHi : kSliderBGColor);
883 // Draw the 'bar'
884 s.fillRect(x, y, p, h,
885 !isEnabled() ? kColor : hilite ? kSliderColorHi : kSliderColor);
886
887 // Draw the 'tickmarks'
888 for(int i = 1; i < _numIntervals; ++i)
889 {
890 int xt = x + (_w - _labelWidth - _valueLabelGap - _valueLabelWidth) * i / _numIntervals - 1;
891 ColorId color = kNone;
892
893 if(isEnabled())
894 {
895 if(xt > x + p)
896 color = hilite ? kSliderColorHi : kSliderColor;
897 else
898 color = hilite ? kSliderBGColorHi : kSliderBGColor;
899 }
900 else
901 {
902 if(xt > x + p)
903 color = kColor;
904 else
905 color = kSliderBGColorLo;
906 }
907 s.vLine(xt, y + h / 2, y + h - 1, color);
908 }
909
910 // Draw the 'handle'
911 s.fillRect(x + p, y - 2, 2, h + 4,
912 !isEnabled() ? kColor : hilite ? kSliderColorHi : kSliderColor);
913
914 if(_valueLabelWidth > 0)
915 s.drawString(_font, _valueLabel + _valueUnit, _x + _w - _valueLabelWidth, _y + 2,
916 _valueLabelWidth, isEnabled() ? kTextColor : kColor);
917
918 setDirty();
919}
920
921// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
922int SliderWidget::valueToPos(int value) const
923{
924 if(value < _valueMin) value = _valueMin;
925 else if(value > _valueMax) value = _valueMax;
926 int range = std::max(_valueMax - _valueMin, 1); // don't divide by zero
927
928 return ((_w - _labelWidth - _valueLabelGap - _valueLabelWidth - 2) * (value - _valueMin) / range);
929}
930
931// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
932int SliderWidget::posToValue(int pos) const
933{
934 int value = (pos) * (_valueMax - _valueMin) / (_w - _labelWidth - _valueLabelGap - _valueLabelWidth - 4) + _valueMin;
935
936 // Scale the position to the correct interval (according to step value)
937 return value - (value % _stepValue);
938}
939