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 "OSystem.hxx" |
22 | #include "EventHandler.hxx" |
23 | #include "FrameBuffer.hxx" |
24 | #include "FBSurface.hxx" |
25 | #include "Font.hxx" |
26 | #include "Menu.hxx" |
27 | #include "Dialog.hxx" |
28 | #include "Widget.hxx" |
29 | #include "TabWidget.hxx" |
30 | |
31 | #include "ContextMenu.hxx" |
32 | #include "PopUpWidget.hxx" |
33 | #include "Settings.hxx" |
34 | #include "Console.hxx" |
35 | |
36 | #include "Vec.hxx" |
37 | #include "TIA.hxx" |
38 | |
39 | /* |
40 | * TODO list |
41 | * - add some sense of the window being "active" (i.e. in front) or not. If it |
42 | * was inactive and just became active, reset certain vars (like who is focused). |
43 | * Maybe we should just add lostFocus and receivedFocus methods to Dialog, just |
44 | * like we have for class Widget? |
45 | * ... |
46 | */ |
47 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
48 | Dialog::Dialog(OSystem& instance, DialogContainer& parent, const GUI::Font& font, |
49 | const string& title, int x, int y, int w, int h) |
50 | : GuiObject(instance, parent, *this, x, y, w, h), |
51 | _font(font), |
52 | _mouseWidget(nullptr), |
53 | _focusedWidget(nullptr), |
54 | _dragWidget(nullptr), |
55 | _defaultWidget(nullptr), |
56 | _okWidget(nullptr), |
57 | _cancelWidget(nullptr), |
58 | _visible(false), |
59 | _onTop(true), |
60 | _processCancel(false), |
61 | _title(title), |
62 | _th(0), |
63 | _layer(0), |
64 | _surface(nullptr), |
65 | _tabID(0), |
66 | _flags(Widget::FLAG_ENABLED | Widget::FLAG_BORDER | Widget::FLAG_CLEARBG), |
67 | _max_w(0), |
68 | _max_h(0) |
69 | { |
70 | setTitle(title); |
71 | setDirty(); |
72 | } |
73 | |
74 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
75 | Dialog::Dialog(OSystem& instance, DialogContainer& parent, |
76 | int x, int y, int w, int h) |
77 | : Dialog(instance, parent, instance.frameBuffer().font(), "" , x, y, w, h) |
78 | { |
79 | } |
80 | |
81 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
82 | Dialog::~Dialog() |
83 | { |
84 | _myFocus.list.clear(); |
85 | _myTabList.clear(); |
86 | |
87 | delete _firstWidget; |
88 | _firstWidget = nullptr; |
89 | |
90 | _buttonGroup.clear(); |
91 | } |
92 | |
93 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
94 | void Dialog::open() |
95 | { |
96 | // Make sure we have a valid surface to draw into |
97 | // Technically, this shouldn't be needed until drawDialog(), but some |
98 | // dialogs cause drawing to occur within loadConfig() |
99 | if (_surface == nullptr) |
100 | _surface = instance().frameBuffer().allocateSurface(_w, _h); |
101 | else if (uInt32(_w) > _surface->width() || uInt32(_h) > _surface->height()) |
102 | _surface->resize(_w, _h); |
103 | _surface->setSrcSize(_w, _h); |
104 | _layer = parent().addDialog(this); |
105 | |
106 | // Take hidpi scaling into account |
107 | const uInt32 scale = instance().frameBuffer().hidpiScaleFactor(); |
108 | _surface->setDstSize(_w * scale, _h * scale); |
109 | |
110 | center(); |
111 | |
112 | if(_myTabList.size()) |
113 | // (Re)-build the focus list to use for all widgets of all tabs |
114 | for(auto& tabfocus : _myTabList) |
115 | buildCurrentFocusList(tabfocus.widget->getID()); |
116 | else |
117 | buildCurrentFocusList(); |
118 | |
119 | /*if (!_surface->attributes().blending) |
120 | { |
121 | _surface->attributes().blending = true; |
122 | _surface->attributes().blendalpha = 90; |
123 | _surface->applyAttributes(); |
124 | }*/ |
125 | |
126 | loadConfig(); // has to be done AFTER (re)building the focus list |
127 | |
128 | _visible = true; |
129 | |
130 | setDirty(); |
131 | } |
132 | |
133 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
134 | void Dialog::close() |
135 | { |
136 | if(_mouseWidget) |
137 | { |
138 | _mouseWidget->handleMouseLeft(); |
139 | _mouseWidget = nullptr; |
140 | } |
141 | |
142 | releaseFocus(); |
143 | |
144 | _visible = false; |
145 | |
146 | parent().removeDialog(); |
147 | setDirty(); |
148 | } |
149 | |
150 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
151 | void Dialog::setTitle(const string& title) |
152 | { |
153 | _title = title; |
154 | _h -= _th; |
155 | if(title.empty()) |
156 | _th = 0; |
157 | else |
158 | _th = _font.getLineHeight() + 4; |
159 | _h += _th; |
160 | |
161 | setDirty(); |
162 | } |
163 | |
164 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
165 | void Dialog::center() |
166 | { |
167 | positionAt(instance().settings().getInt("dialogpos" )); |
168 | } |
169 | |
170 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
171 | void Dialog::positionAt(uInt32 pos) |
172 | { |
173 | const bool fullscreen = instance().settings().getBool("fullscreen" ); |
174 | const double overscan = fullscreen ? instance().settings().getInt("tia.fs_overscan" ) / 200.0 : 0.0; |
175 | const Common::Size& screen = instance().frameBuffer().screenSize(); |
176 | const Common::Rect& dst = _surface->dstRect(); |
177 | // shift stacked dialogs |
178 | Int32 hgap = (screen.w >> 6) * _layer + screen.w * overscan; |
179 | Int32 vgap = (screen.w >> 6) * _layer + screen.h * overscan; |
180 | int top = std::min(std::max(0, Int32(screen.h - dst.h())), vgap); |
181 | int btm = std::max(0, Int32(screen.h - dst.h() - vgap)); |
182 | int left = std::min(std::max(0, Int32(screen.w - dst.w())), hgap); |
183 | int right = std::max(0, Int32(screen.w - dst.w() - hgap)); |
184 | |
185 | switch (pos) |
186 | { |
187 | case 1: |
188 | _surface->setDstPos(left, top); |
189 | break; |
190 | |
191 | case 2: |
192 | _surface->setDstPos(right, top); |
193 | break; |
194 | |
195 | case 3: |
196 | _surface->setDstPos(right, btm); |
197 | break; |
198 | |
199 | case 4: |
200 | _surface->setDstPos(left, btm); |
201 | break; |
202 | |
203 | default: |
204 | // center |
205 | _surface->setDstPos((screen.w - dst.w()) >> 1, (screen.h - dst.h()) >> 1); |
206 | break; |
207 | } |
208 | } |
209 | |
210 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
211 | bool Dialog::render() |
212 | { |
213 | if(!_dirty || !isVisible()) |
214 | return false; |
215 | |
216 | // Draw this dialog |
217 | center(); |
218 | drawDialog(); |
219 | |
220 | // Update dialog surface; also render any extra surfaces |
221 | // Extra surfaces must be rendered afterwards, so they are drawn on top |
222 | if(_surface->render()) |
223 | { |
224 | mySurfaceStack.applyAll([](shared_ptr<FBSurface>& surface){ |
225 | surface->render(); |
226 | }); |
227 | } |
228 | _dirty = false; |
229 | |
230 | return true; |
231 | } |
232 | |
233 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
234 | void Dialog::releaseFocus() |
235 | { |
236 | if(_focusedWidget) |
237 | { |
238 | // remember focus of all tabs for when dialog is reopened again |
239 | for(auto& tabfocus : _myTabList) |
240 | tabfocus.saveCurrentFocus(_focusedWidget); |
241 | |
242 | //_focusedWidget->lostFocus(); |
243 | //_focusedWidget = nullptr; |
244 | } |
245 | } |
246 | |
247 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
248 | void Dialog::addFocusWidget(Widget* w) |
249 | { |
250 | if(!w) |
251 | return; |
252 | |
253 | // All focusable widgets should retain focus |
254 | w->setFlags(Widget::FLAG_RETAIN_FOCUS); |
255 | |
256 | _myFocus.widget = w; |
257 | _myFocus.list.push_back(w); |
258 | } |
259 | |
260 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
261 | void Dialog::addToFocusList(WidgetArray& list) |
262 | { |
263 | // All focusable widgets should retain focus |
264 | for(const auto& w: list) |
265 | w->setFlags(Widget::FLAG_RETAIN_FOCUS); |
266 | |
267 | Vec::append(_myFocus.list, list); |
268 | _focusList = _myFocus.list; |
269 | |
270 | if(list.size() > 0) |
271 | _myFocus.widget = list[0]; |
272 | } |
273 | |
274 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
275 | void Dialog::addToFocusList(WidgetArray& list, TabWidget* w, int tabId) |
276 | { |
277 | // Only add the list if the tab actually exists |
278 | if(!w || w->getID() >= _myTabList.size()) |
279 | return; |
280 | |
281 | assert(w == _myTabList[w->getID()].widget); |
282 | |
283 | // All focusable widgets should retain focus |
284 | for(const auto& fw: list) |
285 | fw->setFlags(Widget::FLAG_RETAIN_FOCUS); |
286 | |
287 | // First get the appropriate focus list |
288 | FocusList& focus = _myTabList[w->getID()].focus; |
289 | |
290 | // Now insert in the correct place in that focus list |
291 | uInt32 id = tabId; |
292 | if(id < focus.size()) |
293 | Vec::append(focus[id].list, list); |
294 | else |
295 | { |
296 | // Make sure the array is large enough |
297 | while(focus.size() <= id) |
298 | focus.push_back(Focus()); |
299 | |
300 | Vec::append(focus[id].list, list); |
301 | } |
302 | |
303 | if(list.size() > 0) |
304 | focus[id].widget = list[0]; |
305 | } |
306 | |
307 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
308 | void Dialog::addTabWidget(TabWidget* w) |
309 | { |
310 | if(!w) |
311 | return; |
312 | |
313 | // Make sure the array is large enough |
314 | uInt32 id = w->getID(); |
315 | while(_myTabList.size() < id) |
316 | _myTabList.push_back(TabFocus()); |
317 | |
318 | _myTabList.push_back(TabFocus(w)); |
319 | } |
320 | |
321 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
322 | void Dialog::setFocus(Widget* w) |
323 | { |
324 | // If the click occured inside a widget which is not the currently |
325 | // focused one, change the focus to that widget. |
326 | if(w && w != _focusedWidget && w->wantsFocus()) |
327 | { |
328 | // Redraw widgets for new focus |
329 | _focusedWidget = Widget::setFocusForChain(this, getFocusList(), w, 0); |
330 | |
331 | // Update current tab based on new focused widget |
332 | getTabIdForWidget(_focusedWidget); |
333 | } |
334 | } |
335 | |
336 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
337 | void Dialog::buildCurrentFocusList(int tabID) |
338 | { |
339 | // Yes, this is hideously complex. That's the price we pay for |
340 | // tab navigation ... |
341 | _focusList.clear(); |
342 | |
343 | // Remember which tab item previously had focus, if applicable |
344 | // This only applies if this method was called for a tab change |
345 | Widget* tabFocusWidget = nullptr; |
346 | if(tabID >= 0 && tabID < int(_myTabList.size())) |
347 | { |
348 | // Save focus in previously selected tab column, |
349 | // and get focus for new tab column |
350 | TabFocus& tabfocus = _myTabList[tabID]; |
351 | tabfocus.saveCurrentFocus(_focusedWidget); |
352 | tabFocusWidget = tabfocus.getNewFocus(); |
353 | |
354 | _tabID = tabID; |
355 | } |
356 | |
357 | // Add appropriate items from tablist (if present) |
358 | for(auto& tabfocus: _myTabList) |
359 | tabfocus.appendFocusList(_focusList); |
360 | |
361 | // Add remaining items from main focus list |
362 | Vec::append(_focusList, _myFocus.list); |
363 | |
364 | // Add button group at end of current focus list |
365 | // We do it this way for TabWidget, so that buttons are scanned |
366 | // *after* the widgets in the current tab |
367 | if(_buttonGroup.size() > 0) |
368 | Vec::append(_focusList, _buttonGroup); |
369 | |
370 | // Finally, the moment we've all been waiting for :) |
371 | // Set the actual focus widget |
372 | if(tabFocusWidget) |
373 | _focusedWidget = tabFocusWidget; |
374 | else if(!_focusedWidget && _focusList.size() > 0) |
375 | _focusedWidget = _focusList[0]; |
376 | } |
377 | |
378 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
379 | void Dialog::addSurface(shared_ptr<FBSurface> surface) |
380 | { |
381 | mySurfaceStack.push(surface); |
382 | } |
383 | |
384 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
385 | void Dialog::drawDialog() |
386 | { |
387 | if(!isVisible()) |
388 | return; |
389 | |
390 | FBSurface& s = surface(); |
391 | |
392 | // Dialog is still on top if e.g a ContextMenu is opened |
393 | _onTop = parent().myDialogStack.top() == this |
394 | || (parent().myDialogStack.get(parent().myDialogStack.size() - 2) == this |
395 | && !parent().myDialogStack.top()->hasTitle()); |
396 | |
397 | if(_flags & Widget::FLAG_CLEARBG) |
398 | { |
399 | // cerr << "Dialog::drawDialog(): w = " << _w << ", h = " << _h << " @ " << &s << endl << endl; |
400 | s.fillRect(_x, _y + _th, _w, _h - _th, _onTop ? kDlgColor : kBGColorLo); |
401 | if(_th) |
402 | { |
403 | s.fillRect(_x, _y, _w, _th, _onTop ? kColorTitleBar : kColorTitleBarLo); |
404 | s.drawString(_font, _title, _x + 10, _y + 2 + 1, _font.getStringWidth(_title), |
405 | _onTop ? kColorTitleText : kColorTitleTextLo); |
406 | } |
407 | } |
408 | else |
409 | s.invalidate(); |
410 | if(_flags & Widget::FLAG_BORDER) // currently only used by Dialog itself |
411 | s.frameRect(_x, _y, _w, _h, _onTop ? kColor : kShadowColor); |
412 | |
413 | // Make all child widget dirty |
414 | Widget* w = _firstWidget; |
415 | Widget::setDirtyInChain(w); |
416 | |
417 | // Draw all children |
418 | w = _firstWidget; |
419 | while(w) |
420 | { |
421 | w->draw(); |
422 | w = w->_next; |
423 | } |
424 | |
425 | // Draw outlines for focused widgets |
426 | // Don't change focus, since this will trigger lost and received |
427 | // focus events |
428 | if(_focusedWidget) |
429 | { |
430 | _focusedWidget = Widget::setFocusForChain(this, getFocusList(), |
431 | _focusedWidget, 0, false); |
432 | if(_focusedWidget) |
433 | _focusedWidget->draw(); // make sure the highlight color is drawn initially |
434 | } |
435 | } |
436 | |
437 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
438 | void Dialog::handleText(char text) |
439 | { |
440 | // Focused widget receives text events |
441 | if(_focusedWidget) |
442 | _focusedWidget->handleText(text); |
443 | } |
444 | |
445 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
446 | void Dialog::handleKeyDown(StellaKey key, StellaMod mod, bool repeated) |
447 | { |
448 | Event::Type e = Event::NoType; |
449 | |
450 | // FIXME - I don't think this will compile! |
451 | #if defined(RETRON77) |
452 | // special keys used for R77 |
453 | if (key == KBDK_F13) |
454 | e = Event::UITabPrev; |
455 | else if (key == KBDK_BACKSPACE) |
456 | e = Event::UITabNext; |
457 | #endif |
458 | |
459 | // Check the keytable now, since we might get one of the above events, |
460 | // which must always be processed before any widget sees it. |
461 | if(e == Event::NoType) |
462 | e = instance().eventHandler().eventForKey(EventMode::kMenuMode, key, mod); |
463 | |
464 | // Unless a widget has claimed all responsibility for data, we assume |
465 | // that if an event exists for the given data, it should have priority. |
466 | if(!handleNavEvent(e, repeated) && _focusedWidget) |
467 | { |
468 | if(_focusedWidget->wantsRaw() || e == Event::NoType) |
469 | _focusedWidget->handleKeyDown(key, mod); |
470 | else |
471 | _focusedWidget->handleEvent(e); |
472 | } |
473 | } |
474 | |
475 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
476 | void Dialog::handleKeyUp(StellaKey key, StellaMod mod) |
477 | { |
478 | // Focused widget receives keyup events |
479 | if(_focusedWidget) |
480 | _focusedWidget->handleKeyUp(key, mod); |
481 | } |
482 | |
483 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
484 | void Dialog::handleMouseDown(int x, int y, MouseButton b, int clickCount) |
485 | { |
486 | Widget* w = findWidget(x, y); |
487 | |
488 | _dragWidget = w; |
489 | setFocus(w); |
490 | |
491 | if(w) |
492 | w->handleMouseDown(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), |
493 | b, clickCount); |
494 | } |
495 | |
496 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
497 | void Dialog::handleMouseUp(int x, int y, MouseButton b, int clickCount) |
498 | { |
499 | if(_focusedWidget) |
500 | { |
501 | // Lose focus on mouseup unless the widget requested to retain the focus |
502 | if(! (_focusedWidget->getFlags() & Widget::FLAG_RETAIN_FOCUS )) |
503 | releaseFocus(); |
504 | } |
505 | |
506 | Widget* w = _dragWidget; |
507 | if(w) |
508 | w->handleMouseUp(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), |
509 | b, clickCount); |
510 | |
511 | _dragWidget = nullptr; |
512 | } |
513 | |
514 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
515 | void Dialog::handleMouseWheel(int x, int y, int direction) |
516 | { |
517 | // This may look a bit backwards, but I think it makes more sense for |
518 | // the mouse wheel to primarily affect the widget the mouse is at than |
519 | // the widget that happens to be focused. |
520 | |
521 | Widget* w = findWidget(x, y); |
522 | if(!w) |
523 | w = _focusedWidget; |
524 | if(w) |
525 | w->handleMouseWheel(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), direction); |
526 | } |
527 | |
528 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
529 | void Dialog::handleMouseMoved(int x, int y) |
530 | { |
531 | Widget* w; |
532 | |
533 | if(_focusedWidget && !_dragWidget) |
534 | { |
535 | w = _focusedWidget; |
536 | int wx = w->getAbsX() - _x; |
537 | int wy = w->getAbsY() - _y; |
538 | |
539 | // We still send mouseEntered/Left messages to the focused item |
540 | // (but to no other items). |
541 | bool mouseInFocusedWidget = (x >= wx && x < wx + w->_w && y >= wy && y < wy + w->_h); |
542 | if(mouseInFocusedWidget && _mouseWidget != w) |
543 | { |
544 | if(_mouseWidget) |
545 | _mouseWidget->handleMouseLeft(); |
546 | _mouseWidget = w; |
547 | w->handleMouseEntered(); |
548 | } |
549 | else if (!mouseInFocusedWidget && _mouseWidget == w) |
550 | { |
551 | _mouseWidget = nullptr; |
552 | w->handleMouseLeft(); |
553 | } |
554 | |
555 | w->handleMouseMoved(x - wx, y - wy); |
556 | } |
557 | |
558 | // While a "drag" is in process (i.e. mouse is moved while a button is pressed), |
559 | // only deal with the widget in which the click originated. |
560 | if (_dragWidget) |
561 | w = _dragWidget; |
562 | else |
563 | w = findWidget(x, y); |
564 | |
565 | if (_mouseWidget != w) |
566 | { |
567 | if (_mouseWidget) |
568 | _mouseWidget->handleMouseLeft(); |
569 | if (w) |
570 | w->handleMouseEntered(); |
571 | _mouseWidget = w; |
572 | } |
573 | |
574 | if (w && (w->getFlags() & Widget::FLAG_TRACK_MOUSE)) |
575 | w->handleMouseMoved(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y)); |
576 | } |
577 | |
578 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
579 | bool Dialog::handleMouseClicks(int x, int y, MouseButton b) |
580 | { |
581 | Widget* w = findWidget(x, y); |
582 | |
583 | if(w) |
584 | return w->handleMouseClicks(x - (w->getAbsX() - _x), |
585 | y - (w->getAbsY() - _y), b); |
586 | else |
587 | return false; |
588 | } |
589 | |
590 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
591 | void Dialog::handleJoyDown(int stick, int button, bool longPress) |
592 | { |
593 | Event::Type e = |
594 | instance().eventHandler().eventForJoyButton(EventMode::kMenuMode, stick, button); |
595 | |
596 | // Unless a widget has claimed all responsibility for data, we assume |
597 | // that if an event exists for the given data, it should have priority. |
598 | if(!handleNavEvent(e) && _focusedWidget) |
599 | { |
600 | if(_focusedWidget->wantsRaw() || e == Event::NoType) |
601 | _focusedWidget->handleJoyDown(stick, button, longPress); |
602 | else |
603 | _focusedWidget->handleEvent(e); |
604 | } |
605 | } |
606 | |
607 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
608 | void Dialog::handleJoyUp(int stick, int button) |
609 | { |
610 | // Focused widget receives joystick events |
611 | if(_focusedWidget) |
612 | _focusedWidget->handleJoyUp(stick, button); |
613 | } |
614 | |
615 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
616 | Event::Type Dialog::getJoyAxisEvent(int stick, JoyAxis axis, JoyDir adir, int button) |
617 | { |
618 | return instance().eventHandler().eventForJoyAxis(EventMode::kMenuMode, stick, axis, adir, button); |
619 | } |
620 | |
621 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
622 | void Dialog::handleJoyAxis(int stick, JoyAxis axis, JoyDir adir, int button) |
623 | { |
624 | Event::Type e = getJoyAxisEvent(stick, axis, adir, button); |
625 | |
626 | // Unless a widget has claimed all responsibility for data, we assume |
627 | // that if an event exists for the given data, it should have priority. |
628 | if(!handleNavEvent(e) && _focusedWidget) |
629 | { |
630 | if(_focusedWidget->wantsRaw() || e == Event::NoType) |
631 | _focusedWidget->handleJoyAxis(stick, axis, adir, button); |
632 | else if(adir != JoyDir::NONE) |
633 | _focusedWidget->handleEvent(e); |
634 | } |
635 | } |
636 | |
637 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
638 | bool Dialog::handleJoyHat(int stick, int hat, JoyHatDir hdir, int button) |
639 | { |
640 | Event::Type e = |
641 | instance().eventHandler().eventForJoyHat(EventMode::kMenuMode, stick, hat, hdir, button); |
642 | |
643 | // Unless a widget has claimed all responsibility for data, we assume |
644 | // that if an event exists for the given data, it should have priority. |
645 | if(!handleNavEvent(e) && _focusedWidget) |
646 | { |
647 | if(_focusedWidget->wantsRaw() || e == Event::NoType) |
648 | return _focusedWidget->handleJoyHat(stick, hat, hdir, button); |
649 | else |
650 | return _focusedWidget->handleEvent(e); |
651 | } |
652 | return true; |
653 | } |
654 | |
655 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
656 | bool Dialog::handleNavEvent(Event::Type e, bool repeated) |
657 | { |
658 | switch(e) |
659 | { |
660 | case Event::UITabPrev: |
661 | if (cycleTab(-1)) |
662 | return true; |
663 | break; |
664 | |
665 | case Event::UITabNext: |
666 | if (cycleTab(1)) |
667 | return true; |
668 | break; |
669 | |
670 | case Event::UINavPrev: |
671 | if(_focusedWidget && !_focusedWidget->wantsTab()) |
672 | { |
673 | _focusedWidget = Widget::setFocusForChain(this, getFocusList(), |
674 | _focusedWidget, -1); |
675 | // Update current tab based on new focused widget |
676 | getTabIdForWidget(_focusedWidget); |
677 | |
678 | return true; |
679 | } |
680 | break; |
681 | |
682 | case Event::UINavNext: |
683 | if(_focusedWidget && !_focusedWidget->wantsTab()) |
684 | { |
685 | _focusedWidget = Widget::setFocusForChain(this, getFocusList(), |
686 | _focusedWidget, +1); |
687 | // Update current tab based on new focused widget |
688 | getTabIdForWidget(_focusedWidget); |
689 | |
690 | return true; |
691 | } |
692 | break; |
693 | |
694 | case Event::UIOK: |
695 | if(_okWidget && _okWidget->isEnabled() && !repeated) |
696 | { |
697 | // Receiving 'OK' is the same as getting the 'Select' event |
698 | _okWidget->handleEvent(Event::UISelect); |
699 | return true; |
700 | } |
701 | break; |
702 | |
703 | case Event::UICancel: |
704 | if(_cancelWidget && _cancelWidget->isEnabled() && !repeated) |
705 | { |
706 | // Receiving 'Cancel' is the same as getting the 'Select' event |
707 | _cancelWidget->handleEvent(Event::UISelect); |
708 | return true; |
709 | } |
710 | else if(_processCancel) |
711 | { |
712 | // Some dialogs want the ability to cancel without actually having |
713 | // a corresponding cancel button |
714 | processCancel(); |
715 | return true; |
716 | } |
717 | break; |
718 | |
719 | default: |
720 | return false; |
721 | } |
722 | return false; |
723 | } |
724 | |
725 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
726 | void Dialog::getTabIdForWidget(Widget* w) |
727 | { |
728 | if(_myTabList.size() == 0 || !w) |
729 | return; |
730 | |
731 | for(uInt32 id = 0; id < _myTabList.size(); ++id) |
732 | { |
733 | if(w->_boss == _myTabList[id].widget) |
734 | { |
735 | _tabID = id; |
736 | return; |
737 | } |
738 | } |
739 | } |
740 | |
741 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
742 | bool Dialog::cycleTab(int direction) |
743 | { |
744 | if(_tabID >= 0 && _tabID < int(_myTabList.size())) |
745 | { |
746 | _myTabList[_tabID].widget->cycleTab(direction); |
747 | return true; |
748 | } |
749 | return false; |
750 | } |
751 | |
752 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
753 | void Dialog::handleCommand(CommandSender* sender, int cmd, int data, int id) |
754 | { |
755 | switch(cmd) |
756 | { |
757 | case TabWidget::kTabChangedCmd: |
758 | if(_visible) |
759 | buildCurrentFocusList(id); |
760 | break; |
761 | |
762 | case GuiObject::kCloseCmd: |
763 | close(); |
764 | break; |
765 | } |
766 | } |
767 | |
768 | /* |
769 | * Determine the widget at location (x,y) if any. Assumes the coordinates are |
770 | * in the local coordinate system, i.e. relative to the top left of the dialog. |
771 | */ |
772 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
773 | Widget* Dialog::findWidget(int x, int y) const |
774 | { |
775 | return Widget::findWidgetInChain(_firstWidget, x, y); |
776 | } |
777 | |
778 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
779 | void Dialog::addOKCancelBGroup(WidgetArray& wid, const GUI::Font& font, |
780 | const string& okText, const string& cancelText, |
781 | bool focusOKButton, int buttonWidth) |
782 | { |
783 | const int HBORDER = 10; |
784 | const int VBORDER = 10; |
785 | const int BTN_BORDER = 20; |
786 | const int BUTTON_GAP = 8; |
787 | buttonWidth = std::max(buttonWidth, |
788 | std::max(font.getStringWidth("Defaults" ), |
789 | std::max(font.getStringWidth(okText), |
790 | font.getStringWidth(cancelText))) + BTN_BORDER); |
791 | int buttonHeight = font.getLineHeight() + 4; |
792 | |
793 | _w = std::max(HBORDER * 2 + buttonWidth * 2 + BUTTON_GAP, _w); |
794 | |
795 | #ifndef BSPF_MACOS |
796 | addOKWidget(new ButtonWidget(this, font, _w - 2 * buttonWidth - HBORDER - BUTTON_GAP, |
797 | _h - buttonHeight - VBORDER, buttonWidth, buttonHeight, okText, GuiObject::kOKCmd)); |
798 | addCancelWidget(new ButtonWidget(this, font, _w - (buttonWidth + HBORDER), |
799 | _h - buttonHeight - VBORDER, buttonWidth, buttonHeight, cancelText, GuiObject::kCloseCmd)); |
800 | #else |
801 | addCancelWidget(new ButtonWidget(this, font, _w - 2 * buttonWidth - HBORDER - BUTTON_GAP, |
802 | _h - buttonHeight - VBORDER, buttonWidth, buttonHeight, cancelText, GuiObject::kCloseCmd)); |
803 | addOKWidget(new ButtonWidget(this, font, _w - (buttonWidth + HBORDER), |
804 | _h - buttonHeight - VBORDER, buttonWidth, buttonHeight, okText, GuiObject::kOKCmd)); |
805 | #endif |
806 | |
807 | // Note that 'focusOKButton' only takes effect when there are no other UI |
808 | // elements in the dialog; otherwise, the first widget of the dialog is always |
809 | // automatically focused first |
810 | // Changing this behaviour would require a fairly major refactoring of the UI code |
811 | if(focusOKButton) |
812 | { |
813 | wid.push_back(_okWidget); |
814 | wid.push_back(_cancelWidget); |
815 | } |
816 | else |
817 | { |
818 | wid.push_back(_cancelWidget); |
819 | wid.push_back(_okWidget); |
820 | } |
821 | } |
822 | |
823 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
824 | void Dialog::addDefaultsOKCancelBGroup(WidgetArray& wid, const GUI::Font& font, |
825 | const string& okText, const string& cancelText, |
826 | const string& defaultsText, |
827 | bool focusOKButton) |
828 | { |
829 | const int HBORDER = 10; |
830 | const int VBORDER = 10; |
831 | const int BTN_BORDER = 20; |
832 | int buttonWidth = font.getStringWidth(defaultsText) + BTN_BORDER; |
833 | int buttonHeight = font.getLineHeight() + 4; |
834 | |
835 | addDefaultWidget(new ButtonWidget(this, font, HBORDER, _h - buttonHeight - VBORDER, |
836 | buttonWidth, buttonHeight, defaultsText, GuiObject::kDefaultsCmd)); |
837 | wid.push_back(_defaultWidget); |
838 | |
839 | addOKCancelBGroup(wid, font, okText, cancelText, focusOKButton, buttonWidth); |
840 | } |
841 | |
842 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
843 | void Dialog::TabFocus::appendFocusList(WidgetArray& list) |
844 | { |
845 | int active = widget->getActiveTab(); |
846 | |
847 | if(active >= 0 && active < int(focus.size())) |
848 | Vec::append(list, focus[active].list); |
849 | } |
850 | |
851 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
852 | void Dialog::TabFocus::saveCurrentFocus(Widget* w) |
853 | { |
854 | if(currentTab < focus.size() && |
855 | Widget::isWidgetInChain(focus[currentTab].list, w)) |
856 | focus[currentTab].widget = w; |
857 | } |
858 | |
859 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
860 | Widget* Dialog::TabFocus::getNewFocus() |
861 | { |
862 | currentTab = widget->getActiveTab(); |
863 | |
864 | return (currentTab < focus.size()) ? focus[currentTab].widget : nullptr; |
865 | } |
866 | |
867 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
868 | bool Dialog::getDynamicBounds(uInt32& w, uInt32& h) const |
869 | { |
870 | const Common::Rect& r = instance().frameBuffer().imageRect(); |
871 | const uInt32 scale = instance().frameBuffer().hidpiScaleFactor(); |
872 | |
873 | if(r.w() <= FBMinimum::Width || r.h() <= FBMinimum::Height) |
874 | { |
875 | w = r.w() / scale; |
876 | h = r.h() / scale; |
877 | return false; |
878 | } |
879 | else |
880 | { |
881 | w = uInt32(0.95 * r.w() / scale); |
882 | h = uInt32(0.95 * r.h() / scale); |
883 | return true; |
884 | } |
885 | } |
886 | |
887 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
888 | void Dialog::setSize(uInt32 w, uInt32 h, uInt32 max_w, uInt32 max_h) |
889 | { |
890 | _w = std::min(w, max_w); |
891 | _max_w = w; |
892 | _h = std::min(h, max_h); |
893 | _max_h = h; |
894 | } |
895 | |
896 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
897 | bool Dialog::shouldResize(uInt32& w, uInt32& h) const |
898 | { |
899 | getDynamicBounds(w, h); |
900 | |
901 | // returns true if the current size is larger than the allowed size or |
902 | // if the current size is smaller than the allowed and wanted size |
903 | return (uInt32(_w) > w || uInt32(_h) > h || |
904 | (uInt32(_w) < w && uInt32(_w) < _max_w) || |
905 | (uInt32(_h) < h && uInt32(_h) < _max_h)); |
906 | } |
907 | |