| 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 31 | Widget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 58 | Widget::~Widget() |
| 59 | { |
| 60 | delete _next; |
| 61 | _next = nullptr; |
| 62 | |
| 63 | _focusList.clear(); |
| 64 | } |
| 65 | |
| 66 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 67 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 75 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 137 | void Widget::receivedFocus() |
| 138 | { |
| 139 | if(_hasFocus) |
| 140 | return; |
| 141 | |
| 142 | _hasFocus = true; |
| 143 | setFlags(Widget::FLAG_HILITED); |
| 144 | receivedFocusWidget(); |
| 145 | } |
| 146 | |
| 147 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 148 | void Widget::lostFocus() |
| 149 | { |
| 150 | if(!_hasFocus) |
| 151 | return; |
| 152 | |
| 153 | _hasFocus = false; |
| 154 | clearFlags(Widget::FLAG_HILITED); |
| 155 | lostFocusWidget(); |
| 156 | } |
| 157 | |
| 158 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 159 | void Widget::setEnabled(bool e) |
| 160 | { |
| 161 | if(e) setFlags(Widget::FLAG_ENABLED); |
| 162 | else clearFlags(Widget::FLAG_ENABLED); |
| 163 | } |
| 164 | |
| 165 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 166 | Widget* 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 183 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 195 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 205 | Widget* 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 301 | void Widget::setDirtyInChain(Widget* start) |
| 302 | { |
| 303 | while(start) |
| 304 | { |
| 305 | start->setDirty(); |
| 306 | start = start->_next; |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 311 | StaticTextWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 330 | StaticTextWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 340 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 350 | void StaticTextWidget::setLabel(const string& label) |
| 351 | { |
| 352 | _label = label; |
| 353 | |
| 354 | setDirty(); |
| 355 | } |
| 356 | |
| 357 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 358 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 369 | ButtonWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 393 | ButtonWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 401 | ButtonWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 409 | ButtonWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 422 | void ButtonWidget::handleMouseEntered() |
| 423 | { |
| 424 | setFlags(Widget::FLAG_HILITED); |
| 425 | } |
| 426 | |
| 427 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 428 | void ButtonWidget::handleMouseLeft() |
| 429 | { |
| 430 | clearFlags(Widget::FLAG_HILITED); |
| 431 | } |
| 432 | |
| 433 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 434 | bool 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 | |
| 450 | bool ButtonWidget::handleMouseClicks(int x, int y, MouseButton b) |
| 451 | { |
| 452 | return _repeat; |
| 453 | } |
| 454 | |
| 455 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 456 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 466 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 476 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 487 | void 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 */ |
| 509 | static 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 | |
| 523 | static 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 | |
| 537 | static 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 552 | CheckboxWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 588 | void CheckboxWidget::handleMouseEntered() |
| 589 | { |
| 590 | setFlags(Widget::FLAG_HILITED); |
| 591 | } |
| 592 | |
| 593 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 594 | void CheckboxWidget::handleMouseLeft() |
| 595 | { |
| 596 | clearFlags(Widget::FLAG_HILITED); |
| 597 | } |
| 598 | |
| 599 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 600 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 612 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 628 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 649 | void CheckboxWidget::setState(bool state, bool changed) |
| 650 | { |
| 651 | if(_state != state) |
| 652 | { |
| 653 | _state = state; |
| 654 | setDirty(); |
| 655 | } |
| 656 | _changed = changed; |
| 657 | } |
| 658 | |
| 659 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 660 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 683 | SliderWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 714 | SliderWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 724 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 740 | void SliderWidget::setMinValue(int value) |
| 741 | { |
| 742 | _valueMin = value; |
| 743 | setDirty(); |
| 744 | } |
| 745 | |
| 746 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 747 | void SliderWidget::setMaxValue(int value) |
| 748 | { |
| 749 | _valueMax = value; |
| 750 | setDirty(); |
| 751 | } |
| 752 | |
| 753 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 754 | void SliderWidget::setStepValue(int value) |
| 755 | { |
| 756 | _stepValue = value; |
| 757 | setDirty(); |
| 758 | } |
| 759 | |
| 760 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 761 | void SliderWidget::setValueLabel(const string& valueLabel) |
| 762 | { |
| 763 | _valueLabel = valueLabel; |
| 764 | setDirty(); |
| 765 | } |
| 766 | |
| 767 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 768 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 778 | void SliderWidget::setValueUnit(const string& valueUnit) |
| 779 | { |
| 780 | _valueUnit = valueUnit; |
| 781 | setDirty(); |
| 782 | } |
| 783 | |
| 784 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 785 | void SliderWidget::setTickmarkIntervals(int numIntervals) |
| 786 | { |
| 787 | _numIntervals = numIntervals; |
| 788 | setDirty(); |
| 789 | } |
| 790 | |
| 791 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 792 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 802 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 812 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 821 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 833 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 867 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 922 | int 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 932 | int 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 | |