stella/src/gui/Dialog.cxx

1000 lines
30 KiB
C++

//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// Based on code from ScummVM - Scumm Interpreter
// Copyright (C) 2002-2004 The ScummVM project
//============================================================================
#include "OSystem.hxx"
#include "EventHandler.hxx"
#include "FrameBuffer.hxx"
#include "FBSurface.hxx"
#include "Font.hxx"
#include "Menu.hxx"
#include "Dialog.hxx"
#include "Widget.hxx"
#include "TabWidget.hxx"
#include "ContextMenu.hxx"
#include "PopUpWidget.hxx"
#include "Settings.hxx"
#include "Console.hxx"
#include "Vec.hxx"
#include "TIA.hxx"
/*
* TODO list
* - add some sense of the window being "active" (i.e. in front) or not. If it
* was inactive and just became active, reset certain vars (like who is focused).
* Maybe we should just add lostFocus and receivedFocus methods to Dialog, just
* like we have for class Widget?
* ...
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Dialog::Dialog(OSystem& instance, DialogContainer& parent, const GUI::Font& font,
const string& title, int x, int y, int w, int h)
: GuiObject(instance, parent, *this, x, y, w, h),
_font(font),
_title(title)
{
_flags = Widget::FLAG_ENABLED | Widget::FLAG_BORDER | Widget::FLAG_CLEARBG;
setTitle(title);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Dialog::Dialog(OSystem& instance, DialogContainer& parent,
int x, int y, int w, int h)
: Dialog(instance, parent, instance.frameBuffer().font(), "", x, y, w, h)
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Dialog::~Dialog()
{
_myFocus.list.clear();
_myTabList.clear();
delete _firstWidget;
_firstWidget = nullptr;
_buttonGroup.clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::open()
{
// Make sure we have a valid surface to draw into
// Technically, this shouldn't be needed until drawDialog(), but some
// dialogs cause drawing to occur within loadConfig()
if (_surface == nullptr)
_surface = instance().frameBuffer().allocateSurface(_w, _h);
else if (uInt32(_w) > _surface->width() || uInt32(_h) > _surface->height())
_surface->resize(_w, _h);
_surface->setSrcSize(_w, _h);
_layer = parent().addDialog(this);
// Take hidpi scaling into account
const uInt32 scale = instance().frameBuffer().hidpiScaleFactor();
_surface->setDstSize(_w * scale, _h * scale);
center();
if(_myTabList.size())
// (Re)-build the focus list to use for all widgets of all tabs
for(auto& tabfocus : _myTabList)
buildCurrentFocusList(tabfocus.widget->getID());
else
buildCurrentFocusList();
/*if (!_surface->attributes().blending)
{
_surface->attributes().blending = true;
_surface->attributes().blendalpha = 90;
_surface->applyAttributes();
}*/
loadConfig(); // has to be done AFTER (re)building the focus list
_visible = true;
setDirty();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::close()
{
if(_mouseWidget)
{
_mouseWidget->handleMouseLeft();
_mouseWidget = nullptr;
}
releaseFocus();
_visible = false;
parent().removeDialog();
setDirty();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::setTitle(const string& title)
{
_title = title;
_h -= _th;
if(title.empty())
_th = 0;
else
_th = _font.getLineHeight() * 1.25;
_h += _th;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::center()
{
positionAt(instance().settings().getInt("dialogpos"));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::setDirty()
{
_dirty = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Dialog::isDirty() const
{
return _dirty;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Dialog::isChainDirty() const
{
bool dirty = false;
// Check if widget or any subwidgets are dirty
Widget* w = _firstWidget;
while(!dirty && w)
{
dirty |= w->needsRedraw();
w = w->_next;
}
return dirty;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::positionAt(uInt32 pos)
{
const bool fullscreen = instance().settings().getBool("fullscreen");
const double overscan = fullscreen ? instance().settings().getInt("tia.fs_overscan") / 200.0 : 0.0;
const Common::Size& screen = instance().frameBuffer().screenSize();
const Common::Rect& dst = _surface->dstRect();
// shift stacked dialogs
Int32 hgap = (screen.w >> 6) * _layer + screen.w * overscan;
Int32 vgap = (screen.w >> 6) * _layer + screen.h * overscan;
int top = std::min(std::max(0, Int32(screen.h - dst.h())), vgap);
int btm = std::max(0, Int32(screen.h - dst.h() - vgap));
int left = std::min(std::max(0, Int32(screen.w - dst.w())), hgap);
int right = std::max(0, Int32(screen.w - dst.w() - hgap));
switch (pos)
{
case 1:
_surface->setDstPos(left, top);
break;
case 2:
_surface->setDstPos(right, top);
break;
case 3:
_surface->setDstPos(right, btm);
break;
case 4:
_surface->setDstPos(left, btm);
break;
default:
// center
_surface->setDstPos((screen.w - dst.w()) >> 1, (screen.h - dst.h()) >> 1);
break;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Dialog::render()
{
//assert(_dirty);
if(!isVisible() || !needsRedraw())
return false;
// Draw this dialog
center();
drawDialog();
// Update dialog surface; also render any extra surfaces
// Extra surfaces must be rendered afterwards, so they are drawn on top
if(_surface->render())
{
mySurfaceStack.applyAll([](shared_ptr<FBSurface>& surface){
surface->render();
});
}
//_dirty = false;
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::releaseFocus()
{
if(_focusedWidget)
{
// remember focus of all tabs for when dialog is reopened again
for(auto& tabfocus : _myTabList)
tabfocus.saveCurrentFocus(_focusedWidget);
//_focusedWidget->lostFocus();
//_focusedWidget = nullptr;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::addFocusWidget(Widget* w)
{
if(!w)
return;
// All focusable widgets should retain focus
w->setFlags(Widget::FLAG_RETAIN_FOCUS);
_myFocus.widget = w;
_myFocus.list.push_back(w);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::addToFocusList(WidgetArray& list)
{
// All focusable widgets should retain focus
for(const auto& w: list)
w->setFlags(Widget::FLAG_RETAIN_FOCUS);
Vec::append(_myFocus.list, list);
_focusList = _myFocus.list;
if(list.size() > 0)
_myFocus.widget = list[0];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::addToFocusList(WidgetArray& list, TabWidget* w, int tabId)
{
// Only add the list if the tab actually exists
if(!w || w->getID() >= _myTabList.size())
return;
assert(w == _myTabList[w->getID()].widget);
// All focusable widgets should retain focus
for(const auto& fw: list)
fw->setFlags(Widget::FLAG_RETAIN_FOCUS);
// First get the appropriate focus list
FocusList& focus = _myTabList[w->getID()].focus;
// Now insert in the correct place in that focus list
uInt32 id = tabId;
if(id < focus.size())
Vec::append(focus[id].list, list);
else
{
// Make sure the array is large enough
while(focus.size() <= id)
focus.push_back(Focus());
Vec::append(focus[id].list, list);
}
if(list.size() > 0)
focus[id].widget = list[0];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::addTabWidget(TabWidget* w)
{
if(!w)
return;
// Make sure the array is large enough
uInt32 id = w->getID();
while(_myTabList.size() < id)
_myTabList.push_back(TabFocus());
_myTabList.push_back(TabFocus(w));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::setFocus(Widget* w)
{
// If the click occured inside a widget which is not the currently
// focused one, change the focus to that widget.
if(w && w != _focusedWidget && w->wantsFocus())
{
// Redraw widgets for new focus
_focusedWidget = Widget::setFocusForChain(this, getFocusList(), w, 0);
// Update current tab based on new focused widget
getTabIdForWidget(_focusedWidget);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::buildCurrentFocusList(int tabID)
{
// Yes, this is hideously complex. That's the price we pay for
// tab navigation ...
_focusList.clear();
// Remember which tab item previously had focus, if applicable
// This only applies if this method was called for a tab change
Widget* tabFocusWidget = nullptr;
if(tabID >= 0 && tabID < int(_myTabList.size()))
{
// Save focus in previously selected tab column,
// and get focus for new tab column
TabFocus& tabfocus = _myTabList[tabID];
tabfocus.saveCurrentFocus(_focusedWidget);
tabFocusWidget = tabfocus.getNewFocus();
_tabID = tabID;
}
// Add appropriate items from tablist (if present)
for(auto& tabfocus: _myTabList)
tabfocus.appendFocusList(_focusList);
// Add remaining items from main focus list
Vec::append(_focusList, _myFocus.list);
// Add button group at end of current focus list
// We do it this way for TabWidget, so that buttons are scanned
// *after* the widgets in the current tab
if(_buttonGroup.size() > 0)
Vec::append(_focusList, _buttonGroup);
// Finally, the moment we've all been waiting for :)
// Set the actual focus widget
if(tabFocusWidget)
_focusedWidget = tabFocusWidget;
else if(!_focusedWidget && _focusList.size() > 0)
_focusedWidget = _focusList[0];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::addSurface(const shared_ptr<FBSurface>& surface)
{
mySurfaceStack.push(surface);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::drawDialog()
{
if(!isVisible())
return;
FBSurface& s = surface();
if(isDirty())
{
cerr << "*** draw dialog " << typeid(*this).name() << " ***" << endl;
// Dialog is still on top if e.g a ContextMenu is opened
_onTop = parent().myDialogStack.top() == this
|| (parent().myDialogStack.get(parent().myDialogStack.size() - 2) == this
&& !parent().myDialogStack.top()->hasTitle());
if(clearsBackground())
{
// cerr << "Dialog::drawDialog(): w = " << _w << ", h = " << _h << " @ " << &s << endl << endl;
if(hasBackground())
s.fillRect(_x, _y + _th, _w, _h - _th, _onTop ? kDlgColor : kBGColorLo);
else
s.invalidateRect(_x, _y + _th, _w, _h - _th);
if(_th)
{
s.fillRect(_x, _y, _w, _th, _onTop ? kColorTitleBar : kColorTitleBarLo);
s.drawString(_font, _title, _x + _font.getMaxCharWidth() * 1.25, _y + _font.getFontHeight() / 6,
_font.getStringWidth(_title),
_onTop ? kColorTitleText : kColorTitleTextLo);
}
}
else {
s.invalidate();
cerr << "invalidate " << typeid(*this).name() << endl;
}
if(hasBorder()) // currently only used by Dialog itself
s.frameRect(_x, _y, _w, _h, _onTop ? kColor : kShadowColor);
// Make all child widgets dirty
Widget::setDirtyInChain(_firstWidget);
clearDirty();
}
Widget* w = _firstWidget;
// Draw all children
w = _firstWidget;
while(w)
{
// only redraw changed widgets
if(w->needsRedraw())
w->draw();
w = w->_next;
}
// Draw outlines for focused widgets
// Don't change focus, since this will trigger lost and received
// focus events
if(_focusedWidget)
{
_focusedWidget = Widget::setFocusForChain(this, getFocusList(),
_focusedWidget, 0, false);
if(_focusedWidget)
_focusedWidget->draw(); // make sure the highlight color is drawn initially
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleText(char text)
{
// Focused widget receives text events
if(_focusedWidget)
_focusedWidget->handleText(text);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleKeyDown(StellaKey key, StellaMod mod, bool repeated)
{
Event::Type e = Event::NoType;
// FIXME - I don't think this will compile!
#if defined(RETRON77)
// special keys used for R77
if (key == KBDK_F13)
e = Event::UITabPrev;
else if (key == KBDK_BACKSPACE)
e = Event::UITabNext;
#endif
// Check the keytable now, since we might get one of the above events,
// which must always be processed before any widget sees it.
if(e == Event::NoType)
e = instance().eventHandler().eventForKey(EventMode::kMenuMode, key, mod);
// Unless a widget has claimed all responsibility for data, we assume
// that if an event exists for the given data, it should have priority.
if(!handleNavEvent(e, repeated) && _focusedWidget)
{
if(_focusedWidget->wantsRaw() || e == Event::NoType)
_focusedWidget->handleKeyDown(key, mod);
else
_focusedWidget->handleEvent(e);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleKeyUp(StellaKey key, StellaMod mod)
{
// Focused widget receives keyup events
if(_focusedWidget)
_focusedWidget->handleKeyUp(key, mod);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleMouseDown(int x, int y, MouseButton b, int clickCount)
{
Widget* w = findWidget(x, y);
_dragWidget = w;
setFocus(w);
if(w)
w->handleMouseDown(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y),
b, clickCount);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleMouseUp(int x, int y, MouseButton b, int clickCount)
{
if(_focusedWidget)
{
// Lose focus on mouseup unless the widget requested to retain the focus
if(! (_focusedWidget->getFlags() & Widget::FLAG_RETAIN_FOCUS ))
releaseFocus();
}
Widget* w = _dragWidget;
if(w)
w->handleMouseUp(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y),
b, clickCount);
_dragWidget = nullptr;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleMouseWheel(int x, int y, int direction)
{
// This may look a bit backwards, but I think it makes more sense for
// the mouse wheel to primarily affect the widget the mouse is at than
// the widget that happens to be focused.
Widget* w = findWidget(x, y);
if(!w)
w = _focusedWidget;
if(w)
w->handleMouseWheel(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), direction);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleMouseMoved(int x, int y)
{
Widget* w;
if(_focusedWidget && !_dragWidget)
{
w = _focusedWidget;
int wx = w->getAbsX() - _x;
int wy = w->getAbsY() - _y;
// We still send mouseEntered/Left messages to the focused item
// (but to no other items).
bool mouseInFocusedWidget = (x >= wx && x < wx + w->_w && y >= wy && y < wy + w->_h);
if(mouseInFocusedWidget && _mouseWidget != w)
{
if(_mouseWidget)
_mouseWidget->handleMouseLeft();
_mouseWidget = w;
w->handleMouseEntered();
}
else if (!mouseInFocusedWidget && _mouseWidget == w)
{
_mouseWidget = nullptr;
w->handleMouseLeft();
}
w->handleMouseMoved(x - wx, y - wy);
}
// While a "drag" is in process (i.e. mouse is moved while a button is pressed),
// only deal with the widget in which the click originated.
if (_dragWidget)
w = _dragWidget;
else
w = findWidget(x, y);
if (_mouseWidget != w)
{
if (_mouseWidget)
_mouseWidget->handleMouseLeft();
if (w)
w->handleMouseEntered();
_mouseWidget = w;
}
if (w && (w->getFlags() & Widget::FLAG_TRACK_MOUSE))
w->handleMouseMoved(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Dialog::handleMouseClicks(int x, int y, MouseButton b)
{
Widget* w = findWidget(x, y);
if(w)
return w->handleMouseClicks(x - (w->getAbsX() - _x),
y - (w->getAbsY() - _y), b);
else
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleJoyDown(int stick, int button, bool longPress)
{
// Focused widget receives joystick events
if(_focusedWidget)
{
Event::Type e =
instance().eventHandler().eventForJoyButton(EventMode::kMenuMode, stick, button);
if(_focusedWidget->wantsRaw() || e == Event::NoType)
_focusedWidget->handleJoyDown(stick, button, longPress);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleJoyUp(int stick, int button)
{
Event::Type e =
instance().eventHandler().eventForJoyButton(EventMode::kMenuMode, stick, button);
// Unless a widget has claimed all responsibility for data, we assume
// that if an event exists for the given data, it should have priority.
if (!handleNavEvent(e) && _focusedWidget)
{
if (_focusedWidget->wantsRaw() || e == Event::NoType)
_focusedWidget->handleJoyUp(stick, button);
else
_focusedWidget->handleEvent(e);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Event::Type Dialog::getJoyAxisEvent(int stick, JoyAxis axis, JoyDir adir, int button)
{
return instance().eventHandler().eventForJoyAxis(EventMode::kMenuMode, stick, axis, adir, button);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleJoyAxis(int stick, JoyAxis axis, JoyDir adir, int button)
{
Event::Type e = getJoyAxisEvent(stick, axis, adir, button);
// Unless a widget has claimed all responsibility for data, we assume
// that if an event exists for the given data, it should have priority.
if(!handleNavEvent(e) && _focusedWidget)
{
if(_focusedWidget->wantsRaw() || e == Event::NoType)
_focusedWidget->handleJoyAxis(stick, axis, adir, button);
else if(adir != JoyDir::NONE)
_focusedWidget->handleEvent(e);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Dialog::handleJoyHat(int stick, int hat, JoyHatDir hdir, int button)
{
Event::Type e =
instance().eventHandler().eventForJoyHat(EventMode::kMenuMode, stick, hat, hdir, button);
// Unless a widget has claimed all responsibility for data, we assume
// that if an event exists for the given data, it should have priority.
if(!handleNavEvent(e) && _focusedWidget)
{
if(_focusedWidget->wantsRaw() || e == Event::NoType)
return _focusedWidget->handleJoyHat(stick, hat, hdir, button);
else
return _focusedWidget->handleEvent(e);
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Dialog::handleNavEvent(Event::Type e, bool repeated)
{
switch(e)
{
case Event::UITabPrev:
if (cycleTab(-1))
return true;
break;
case Event::UITabNext:
if (cycleTab(1))
return true;
break;
case Event::UINavPrev:
if(_focusedWidget && !_focusedWidget->wantsTab())
{
_focusedWidget = Widget::setFocusForChain(this, getFocusList(),
_focusedWidget, -1);
// Update current tab based on new focused widget
getTabIdForWidget(_focusedWidget);
return true;
}
break;
case Event::UINavNext:
if(_focusedWidget && !_focusedWidget->wantsTab())
{
_focusedWidget = Widget::setFocusForChain(this, getFocusList(),
_focusedWidget, +1);
// Update current tab based on new focused widget
getTabIdForWidget(_focusedWidget);
return true;
}
break;
case Event::UIOK:
if(_okWidget && _okWidget->isEnabled() && !repeated)
{
// Receiving 'OK' is the same as getting the 'Select' event
_okWidget->handleEvent(Event::UISelect);
return true;
}
break;
case Event::UICancel:
if(_cancelWidget && _cancelWidget->isEnabled() && !repeated)
{
// Receiving 'Cancel' is the same as getting the 'Select' event
_cancelWidget->handleEvent(Event::UISelect);
return true;
}
else if(_processCancel)
{
// Some dialogs want the ability to cancel without actually having
// a corresponding cancel button
processCancel();
return true;
}
break;
default:
return false;
}
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::getTabIdForWidget(Widget* w)
{
if(_myTabList.size() == 0 || !w)
return;
for(uInt32 id = 0; id < _myTabList.size(); ++id)
{
if(w->_boss == _myTabList[id].widget)
{
_tabID = id;
return;
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Dialog::cycleTab(int direction)
{
if(_tabID >= 0 && _tabID < int(_myTabList.size()))
{
_myTabList[_tabID].widget->cycleTab(direction);
return true;
}
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::handleCommand(CommandSender* sender, int cmd, int data, int id)
{
switch(cmd)
{
case TabWidget::kTabChangedCmd:
if(_visible)
buildCurrentFocusList(id);
break;
case GuiObject::kCloseCmd:
close();
break;
default:
break;
}
}
/*
* Determine the widget at location (x,y) if any. Assumes the coordinates are
* in the local coordinate system, i.e. relative to the top left of the dialog.
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Widget* Dialog::findWidget(int x, int y) const
{
return Widget::findWidgetInChain(_firstWidget, x, y);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::addOKBGroup(WidgetArray& wid, const GUI::Font& font,
const string& okText, int buttonWidth)
{
const int fontWidth = font.getMaxCharWidth(),
fontHeight = font.getFontHeight(),
buttonHeight = font.getLineHeight() * 1.25;
const int VBORDER = fontHeight / 2;
const int HBORDER = fontWidth * 1.25;
const int BTN_BORDER = fontWidth * 2.5;
const int BUTTON_GAP = fontWidth;
buttonWidth = fontWidth * 6 + BTN_BORDER;
buttonWidth = std::max(buttonWidth, font.getStringWidth(okText) + BTN_BORDER);
_w = std::max(HBORDER * 2 + buttonWidth * 2 + BUTTON_GAP, _w);
addOKWidget(new ButtonWidget(this, font, (_w - buttonWidth) / 2,
_h - buttonHeight - VBORDER, buttonWidth, buttonHeight, okText, GuiObject::kCloseCmd));
wid.push_back(_okWidget);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::addOKCancelBGroup(WidgetArray& wid, const GUI::Font& font,
const string& okText, const string& cancelText,
bool focusOKButton, int buttonWidth)
{
const int fontWidth = font.getMaxCharWidth(),
fontHeight = font.getFontHeight(),
buttonHeight = font.getLineHeight() * 1.25;
const int VBORDER = fontHeight / 2;
const int HBORDER = fontWidth * 1.25;
const int BTN_BORDER = fontWidth * 2.5;
const int BUTTON_GAP = fontWidth;
buttonWidth = std::max(buttonWidth,
std::max(font.getStringWidth("Defaults"),
std::max(font.getStringWidth(okText),
font.getStringWidth(cancelText))) + BTN_BORDER);
_w = std::max(HBORDER * 2 + buttonWidth * 2 + BUTTON_GAP, _w);
#ifndef BSPF_MACOS
addOKWidget(new ButtonWidget(this, font, _w - 2 * buttonWidth - HBORDER - BUTTON_GAP,
_h - buttonHeight - VBORDER, buttonWidth, buttonHeight, okText, GuiObject::kOKCmd));
addCancelWidget(new ButtonWidget(this, font, _w - (buttonWidth + HBORDER),
_h - buttonHeight - VBORDER, buttonWidth, buttonHeight, cancelText, GuiObject::kCloseCmd));
#else
addCancelWidget(new ButtonWidget(this, font, _w - 2 * buttonWidth - HBORDER - BUTTON_GAP,
_h - buttonHeight - VBORDER, buttonWidth, buttonHeight, cancelText, GuiObject::kCloseCmd));
addOKWidget(new ButtonWidget(this, font, _w - (buttonWidth + HBORDER),
_h - buttonHeight - VBORDER, buttonWidth, buttonHeight, okText, GuiObject::kOKCmd));
#endif
// Note that 'focusOKButton' only takes effect when there are no other UI
// elements in the dialog; otherwise, the first widget of the dialog is always
// automatically focused first
// Changing this behaviour would require a fairly major refactoring of the UI code
if(focusOKButton)
{
wid.push_back(_okWidget);
wid.push_back(_cancelWidget);
}
else
{
wid.push_back(_cancelWidget);
wid.push_back(_okWidget);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::addDefaultsOKCancelBGroup(WidgetArray& wid, const GUI::Font& font,
const string& okText, const string& cancelText,
const string& defaultsText,
bool focusOKButton)
{
const int fontWidth = font.getMaxCharWidth(),
fontHeight = font.getFontHeight(),
buttonHeight = font.getLineHeight() * 1.25;
const int VBORDER = fontHeight / 2;
const int HBORDER = fontWidth * 1.25;
const int BTN_BORDER = fontWidth * 2.5;
const int buttonWidth = font.getStringWidth(defaultsText) + BTN_BORDER;
addDefaultWidget(new ButtonWidget(this, font, HBORDER, _h - buttonHeight - VBORDER,
buttonWidth, buttonHeight, defaultsText, GuiObject::kDefaultsCmd));
wid.push_back(_defaultWidget);
addOKCancelBGroup(wid, font, okText, cancelText, focusOKButton, buttonWidth);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::addDefaultsExtraOKCancelBGroup(
WidgetArray& wid, const GUI::Font& font,
const string& extraText, int extraCmd,
const string& okText, const string& cancelText, const string& defaultsText,
bool focusOKButton)
{
const int fontWidth = font.getMaxCharWidth(),
fontHeight = font.getFontHeight(),
buttonHeight = font.getLineHeight() * 1.25;
const int VBORDER = fontHeight / 2;
const int HBORDER = fontWidth * 1.25;
const int BTN_BORDER = fontWidth * 2.5;
const int BUTTON_GAP = fontWidth;
const int buttonWidth = font.getStringWidth(defaultsText) + BTN_BORDER;
addDefaultWidget(new ButtonWidget(this, font, HBORDER, _h - buttonHeight - VBORDER,
buttonWidth, buttonHeight, defaultsText, GuiObject::kDefaultsCmd));
wid.push_back(_defaultWidget);
wid.push_back(new ButtonWidget(this, font, HBORDER + buttonWidth + BUTTON_GAP,
_h - buttonHeight - VBORDER, buttonWidth, buttonHeight,
extraText, extraCmd)
);
addOKCancelBGroup(wid, font, okText, cancelText, focusOKButton, buttonWidth);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::TabFocus::appendFocusList(WidgetArray& list)
{
int active = widget->getActiveTab();
if(active >= 0 && active < int(focus.size()))
Vec::append(list, focus[active].list);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::TabFocus::saveCurrentFocus(Widget* w)
{
if(currentTab < focus.size() &&
Widget::isWidgetInChain(focus[currentTab].list, w))
focus[currentTab].widget = w;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Widget* Dialog::TabFocus::getNewFocus()
{
currentTab = widget->getActiveTab();
return (currentTab < focus.size()) ? focus[currentTab].widget : nullptr;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Dialog::getDynamicBounds(uInt32& w, uInt32& h) const
{
const Common::Rect& r = instance().frameBuffer().imageRect();
const uInt32 scale = instance().frameBuffer().hidpiScaleFactor();
if(r.w() <= FBMinimum::Width || r.h() <= FBMinimum::Height)
{
w = r.w() / scale;
h = r.h() / scale;
return false;
}
else
{
w = uInt32(0.95 * r.w() / scale);
h = uInt32(0.95 * r.h() / scale);
return true;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::setSize(uInt32 w, uInt32 h, uInt32 max_w, uInt32 max_h)
{
_w = std::min(w, max_w);
_max_w = w;
_h = std::min(h, max_h);
_max_h = h;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Dialog::shouldResize(uInt32& w, uInt32& h) const
{
getDynamicBounds(w, h);
// returns true if the current size is larger than the allowed size or
// if the current size is smaller than the allowed and wanted size
return (uInt32(_w) > w || uInt32(_h) > h ||
(uInt32(_w) < w && uInt32(_w) < _max_w) ||
(uInt32(_h) < h && uInt32(_h) < _max_h));
}