From 2c812a6b7a0b24b9012379118867fb4f64f32c14 Mon Sep 17 00:00:00 2001 From: Bastien Bouclet Date: Wed, 13 Nov 2019 21:09:21 +0100 Subject: GUI: Add DropdownButtonWidget and use it in the launcher for mass add DropdownButtonWidget is a button split in two parts vertically. Clicking the left part triggers a default action. Clicking the right part shows a list of other actions the user can choose from. Using this widget on the launcher lets 'Mass add' be a secondary action of the 'Add' button, removing the necessity of pressing the shift key to access the feature. --- gui/ThemeEngine.cpp | 40 ++- gui/ThemeEngine.h | 10 + gui/ThemeParser.cpp | 11 + gui/ThemeParser.h | 1 + gui/launcher.cpp | 122 +++---- gui/launcher.h | 7 +- gui/options.cpp | 30 +- gui/themes/default.inc | 307 ++++++++++++++++- gui/themes/scummclassic.zip | Bin 154528 -> 157690 bytes gui/themes/scummclassic/classic_gfx.stx | 307 ++++++++++++++++- gui/themes/scummclassic/classic_layout.stx | 4 + gui/themes/scummclassic/classic_layout_lowres.stx | 4 + gui/themes/scummmodern.zip | Bin 283299 -> 287846 bytes gui/themes/scummmodern/scummmodern_gfx.stx | 362 ++++++++++++++++++++- gui/themes/scummmodern/scummmodern_layout.stx | 35 +- .../scummmodern/scummmodern_layout_lowres.stx | 4 + gui/themes/scummremastered.zip | Bin 283227 -> 285890 bytes gui/themes/scummremastered/remastered_gfx.stx | 316 +++++++++++++++++- gui/themes/scummremastered/remastered_layout.stx | 24 +- .../scummremastered/remastered_layout_lowres.stx | 4 + gui/widget.cpp | 103 +++++- gui/widget.h | 35 +- gui/widgets/popup.cpp | 187 ++++++----- gui/widgets/popup.h | 59 +++- 24 files changed, 1738 insertions(+), 234 deletions(-) (limited to 'gui') diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp index e27ee7ac9d..2c089708a4 100644 --- a/gui/ThemeEngine.cpp +++ b/gui/ThemeEngine.cpp @@ -126,9 +126,16 @@ static const DrawDataInfo kDrawDataDefaults[] = { {kDDWidgetBackgroundSlider, "widget_slider", kDrawLayerBackground, kDDNone}, {kDDButtonIdle, "button_idle", kDrawLayerBackground, kDDNone}, - {kDDButtonHover, "button_hover", kDrawLayerForeground, kDDButtonIdle}, + {kDDButtonHover, "button_hover", kDrawLayerForeground, kDDButtonIdle}, {kDDButtonDisabled, "button_disabled", kDrawLayerBackground, kDDNone}, - {kDDButtonPressed, "button_pressed", kDrawLayerForeground, kDDButtonIdle}, + {kDDButtonPressed, "button_pressed", kDrawLayerForeground, kDDButtonIdle}, + + {kDDDropDownButtonIdle, "dropdown_button_idle", kDrawLayerBackground, kDDNone}, + {kDDDropDownButtonHoverLeft, "dropdown_button_hover_left", kDrawLayerForeground, kDDDropDownButtonIdle}, + {kDDDropDownButtonHoverRight, "dropdown_button_hover_right", kDrawLayerForeground, kDDDropDownButtonIdle}, + {kDDDropDownButtonDisabled, "dropdown_button_disabled", kDrawLayerForeground, kDDNone}, + {kDDDropDownButtonPressedLeft, "dropdown_button_pressed_left", kDrawLayerForeground, kDDDropDownButtonIdle}, + {kDDDropDownButtonPressedRight, "dropdown_button_pressed_right", kDrawLayerForeground, kDDDropDownButtonIdle}, {kDDSliderFull, "slider_full", kDrawLayerForeground, kDDNone}, {kDDSliderHover, "slider_hover", kDrawLayerForeground, kDDNone}, @@ -935,6 +942,35 @@ void ThemeEngine::drawButton(const Common::Rect &r, const Common::String &str, W _widgets[dd]->_textAlignV); } +void ThemeEngine::drawDropDownButton(const Common::Rect &r, uint32 dropdownWidth, const Common::String &str, + ThemeEngine::WidgetStateInfo buttonState, bool inButton, bool inDropdown) { + if (!ready()) + return; + + DrawData dd; + if (buttonState == kStateHighlight && inButton) + dd = kDDDropDownButtonHoverLeft; + else if (buttonState == kStateHighlight && inDropdown) + dd = kDDDropDownButtonHoverRight; + else if (buttonState == kStateDisabled) + dd = kDDDropDownButtonDisabled; + else if (buttonState == kStatePressed && inButton) + dd = kDDDropDownButtonPressedLeft; + else if (buttonState == kStatePressed && inDropdown) + dd = kDDDropDownButtonPressedRight; + else + dd = kDDDropDownButtonIdle; + + drawDD(dd, r); + + // Center the text in the button without using the area of the drop down button + Common::Rect textRect = r; + textRect.left = r.left + dropdownWidth; + textRect.right = r.right - dropdownWidth; + drawDDText(getTextData(dd), getTextColor(dd), textRect, str, false, true, _widgets[dd]->_textAlignH, + _widgets[dd]->_textAlignV); +} + void ThemeEngine::drawLineSeparator(const Common::Rect &r) { if (!ready()) return; diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h index 1c35b1ea03..827ec197f9 100644 --- a/gui/ThemeEngine.h +++ b/gui/ThemeEngine.h @@ -80,6 +80,13 @@ enum DrawData { kDDButtonDisabled, kDDButtonPressed, + kDDDropDownButtonIdle, + kDDDropDownButtonHoverLeft, + kDDDropDownButtonHoverRight, + kDDDropDownButtonDisabled, + kDDDropDownButtonPressedLeft, + kDDDropDownButtonPressedRight, + kDDSliderFull, kDDSliderHover, kDDSliderDisabled, @@ -400,6 +407,9 @@ public: void drawButton(const Common::Rect &r, const Common::String &str, WidgetStateInfo state = kStateEnabled, uint16 hints = 0); + void drawDropDownButton(const Common::Rect &r, uint32 dropdownWidth, const Common::String &str, + WidgetStateInfo buttonState, bool inButton, bool inDropdown); + void drawSurface(const Common::Point &p, const Graphics::Surface &surface, bool themeTrans = false); void drawSlider(const Common::Rect &r, int width, WidgetStateInfo state = kStateEnabled); diff --git a/gui/ThemeParser.cpp b/gui/ThemeParser.cpp index a52e3592af..f92676a383 100644 --- a/gui/ThemeParser.cpp +++ b/gui/ThemeParser.cpp @@ -629,6 +629,17 @@ bool ThemeParser::parseDrawStep(ParserNode *stepNode, Graphics::DrawStep *drawst } } + if (stepNode->values.contains("clip")) { + val = stepNode->values["clip"]; + int cl, ct, cr, cb; + if (parseIntegerKey(val, 4, &cl, &ct, &cr, &cb)) { + drawstep->clip.left = cl; + drawstep->clip.top = ct; + drawstep->clip.right = cr; + drawstep->clip.bottom = cb; + } + } + #undef PARSER_ASSIGN_INT #undef PARSER_ASSIGN_RGB diff --git a/gui/ThemeParser.h b/gui/ThemeParser.h index 155731467f..f55a24aa80 100644 --- a/gui/ThemeParser.h +++ b/gui/ThemeParser.h @@ -147,6 +147,7 @@ protected: XML_PROP(orientation, false) XML_PROP(file, false) XML_PROP(autoscale, false) + XML_PROP(clip, false) KEY_END() XML_KEY(text) diff --git a/gui/launcher.cpp b/gui/launcher.cpp index 84512ea018..15866c0bc8 100644 --- a/gui/launcher.cpp +++ b/gui/launcher.cpp @@ -66,9 +66,11 @@ enum { kAboutCmd = 'ABOU', kOptionsCmd = 'OPTN', kAddGameCmd = 'ADDG', + kMassAddGameCmd = 'MADD', kEditGameCmd = 'EDTG', kRemoveGameCmd = 'REMG', kLoadGameCmd = 'LOAD', + kRecordGameCmd = 'RECG', kQuitCmd = 'QUIT', kSearchCmd = 'SRCH', kListSearchCmd = 'LSSR', @@ -145,20 +147,30 @@ void LauncherDialog::build() { _startButton = new ButtonWidget(this, "Launcher.StartButton", _("~S~tart"), _("Start selected game"), kStartCmd); - _loadButton = - new ButtonWidget(this, "Launcher.LoadGameButton", _("~L~oad..."), _("Load saved game for selected game"), kLoadGameCmd); + DropdownButtonWidget *loadButton = + new DropdownButtonWidget(this, "Launcher.LoadGameButton", _("~L~oad..."), _("Load saved game for selected game"), kLoadGameCmd); +#ifdef ENABLE_EVENTRECORDER + loadButton->appendEntry(_s("Record..."), kRecordGameCmd); +#endif + _loadButton = loadButton; // Above the lowest button rows: two more buttons (directly below the list box) if (g_system->getOverlayWidth() > 320) { - _addButton = - new ButtonWidget(this, "Launcher.AddGameButton", _("~A~dd Game..."), _("Hold Shift for Mass Add"), kAddGameCmd); + DropdownButtonWidget *addButton = + new DropdownButtonWidget(this, "Launcher.AddGameButton", _("~A~dd Game..."), _("Add games to the list"), kAddGameCmd); + addButton->appendEntry(_s("Mass Add..."), kMassAddGameCmd); + _addButton = addButton; + _editButton = new ButtonWidget(this, "Launcher.EditGameButton", _("~E~dit Game..."), _("Change game options"), kEditGameCmd); _removeButton = new ButtonWidget(this, "Launcher.RemoveGameButton", _("~R~emove Game"), _("Remove game from the list. The game data files stay intact"), kRemoveGameCmd); } else { - _addButton = - new ButtonWidget(this, "Launcher.AddGameButton", _c("~A~dd Game...", "lowres"), _("Hold Shift for Mass Add"), kAddGameCmd); + DropdownButtonWidget *addButton = + new DropdownButtonWidget(this, "Launcher.AddGameButton", _c("~A~dd Game...", "lowres"), _("Add games to the list"), kAddGameCmd); + addButton->appendEntry(_c("Mass Add...", "lowres"), kMassAddGameCmd); + _addButton = addButton; + _editButton = new ButtonWidget(this, "Launcher.EditGameButton", _c("~E~dit Game...", "lowres"), _("Change game options"), kEditGameCmd); _removeButton = @@ -318,38 +330,6 @@ void LauncherDialog::updateListing() { } void LauncherDialog::addGame() { - -#ifndef DISABLE_MASS_ADD - const bool massAdd = checkModifier(Common::KBD_SHIFT); - - if (massAdd) { - MessageDialog alert(_("Do you really want to run the mass game detector? " - "This could potentially add a huge number of games."), _("Yes"), _("No")); - if (alert.runModal() == GUI::kMessageOK && _browser->runModal() > 0) { - MassAddDialog massAddDlg(_browser->getResult()); - - massAddDlg.runModal(); - - // Update the ListWidget and force a redraw - - // If new target(s) were added, update the ListWidget and move - // the selection to to first newly detected game. - Common::String newTarget = massAddDlg.getFirstAddedTarget(); - if (!newTarget.empty()) { - updateListing(); - selectTarget(newTarget); - } - - g_gui.scheduleTopDialogRedraw(); - } - - // We need to update the buttons here, so "Mass add" will revert to "Add game" - // without any additional event. - updateButtons(); - return; - } -#endif - // Allow user to add a new game to the list. // 1) show a dir selection dialog which lets the user pick the directory // the game data resides in. @@ -392,6 +372,28 @@ void LauncherDialog::addGame() { } while (looping); } +void LauncherDialog::massAddGame() { + MessageDialog alert(_("Do you really want to run the mass game detector? " + "This could potentially add a huge number of games."), _("Yes"), _("No")); + if (alert.runModal() == GUI::kMessageOK && _browser->runModal() > 0) { + MassAddDialog massAddDlg(_browser->getResult()); + + massAddDlg.runModal(); + + // Update the ListWidget and force a redraw + + // If new target(s) were added, update the ListWidget and move + // the selection to to first newly detected game. + Common::String newTarget = massAddDlg.getFirstAddedTarget(); + if (!newTarget.empty()) { + updateListing(); + selectTarget(newTarget); + } + + g_gui.scheduleTopDialogRedraw(); + } +} + void LauncherDialog::removeGame(int item) { MessageDialog alert(_("Do you really want to remove this game configuration?"), _("Yes"), _("No")); @@ -432,20 +434,6 @@ void LauncherDialog::editGame(int item) { } } -void LauncherDialog::loadGameButtonPressed(int item) { -#ifdef ENABLE_EVENTRECORDER - const bool shiftPressed = checkModifier(Common::KBD_SHIFT); - if (shiftPressed) { - recordGame(item); - } else { - loadGame(item); - } - updateButtons(); -#else - loadGame(item); -#endif -} - #ifdef ENABLE_EVENTRECORDER void LauncherDialog::recordGame(int item) { RecorderDialog recorderDialog; @@ -640,6 +628,9 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat case kAddGameCmd: addGame(); break; + case kMassAddGameCmd: + massAddGame(); + break; case kRemoveGameCmd: removeGame(item); break; @@ -647,8 +638,13 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat editGame(item); break; case kLoadGameCmd: - loadGameButtonPressed(item); + loadGame(item); break; +#ifdef ENABLE_EVENTRECORDER + case kRecordGameCmd: + recordGame(item); + break; +#endif case kOptionsCmd: { GlobalOptionsDialog options(this); options.runModal(); @@ -717,28 +713,8 @@ void LauncherDialog::updateButtons() { _loadButton->setEnabled(en); _loadButton->markAsDirty(); } - switchButtonsText(_addButton, "~A~dd Game...", _s("Mass Add...")); -#ifdef ENABLE_EVENTRECORDER - switchButtonsText(_loadButton, "~L~oad...", _s("Record...")); -#endif } -// Update the label of the button depending on whether shift is pressed or not -void LauncherDialog::switchButtonsText(ButtonWidget *button, const char *normalText, const char *shiftedText) { - const bool shiftPressed = checkModifier(Common::KBD_SHIFT); - const bool lowRes = g_system->getOverlayWidth() <= 320; - - const char *newAddButtonLabel = shiftPressed - ? (lowRes ? _c(shiftedText, "lowres") : _(shiftedText)) - : (lowRes ? _c(normalText, "lowres") : _(normalText)); - - if (button->getLabel() != newAddButtonLabel) - button->setLabel(newAddButtonLabel); -} - - - - void LauncherDialog::reflowLayout() { #ifndef DISABLE_FANCY_THEMES if (g_gui.xmlEval()->getVar("Globals.ShowLauncherLogo") == 1 && g_gui.theme()->supportsImages()) { diff --git a/gui/launcher.h b/gui/launcher.h index 5bb386f9a0..7009b993f2 100644 --- a/gui/launcher.h +++ b/gui/launcher.h @@ -82,7 +82,6 @@ protected: void updateListing(); void updateButtons(); - void switchButtonsText(ButtonWidget *button, const char *normalText, const char *shiftedText); void build(); void clean(); @@ -94,6 +93,7 @@ protected: * Handle "Add game..." button. */ virtual void addGame(); + void massAddGame(); /** * Handle "Remove game..." button. @@ -105,11 +105,6 @@ protected: */ void editGame(int item); - /** - * Facade for "Load..."/"Record..." buttons. - */ - void loadGameButtonPressed(int item); - /** * Handle "Record..." button. */ diff --git a/gui/options.cpp b/gui/options.cpp index a324ef6338..161742d9bc 100644 --- a/gui/options.cpp +++ b/gui/options.cpp @@ -2154,22 +2154,24 @@ void GlobalOptionsDialog::apply() { } #ifdef USE_TTS Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager(); - if (newLang != oldLang) { - if (newLang == "C") - ttsMan->setLanguage("en"); - else { - ttsMan->setLanguage(newLang); + if (ttsMan) { + if (newLang != oldLang) { + if (newLang == "C") + ttsMan->setLanguage("en"); + else { + ttsMan->setLanguage(newLang); + } + _ttsVoiceSelectionPopUp->setSelectedTag(0); } - _ttsVoiceSelectionPopUp->setSelectedTag(0); + int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256; + if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm")) + volume = 0; + ttsMan->setVolume(volume); + ConfMan.setBool("tts_enabled", _ttsCheckbox->getState(), _domain); + int selectedVoice = _ttsVoiceSelectionPopUp->getSelectedTag(); + ConfMan.setInt("tts_voice", selectedVoice, _domain); + ttsMan->setVoice(selectedVoice); } - int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256; - if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm")) - volume = 0; - ttsMan->setVolume(volume); - ConfMan.setBool("tts_enabled", _ttsCheckbox->getState(), _domain); - int selectedVoice = _ttsVoiceSelectionPopUp->getSelectedTag(); - ConfMan.setInt("tts_voice", selectedVoice, _domain); - ttsMan->setVoice(selectedVoice); #endif if (isRebuildNeeded) { diff --git a/gui/themes/default.inc b/gui/themes/default.inc index 3e09b49851..9b6b102c23 100644 --- a/gui/themes/default.inc +++ b/gui/themes/default.inc @@ -17,6 +17,9 @@ const char *defaultXML1 = "" "" +"" "" "" "" "" "" "" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" "" "" "" "" +"" "" "" "" +"" +"" "" ; const char *defaultXML4 = "" @@ -2364,6 +2666,7 @@ const char *defaultXML1 = "" "" "" "" +"" "" @@ -4034,6 +4337,8 @@ const char *defaultXML1 = "" "" "" "" +"" +"" "" ; const char *defaultXML[] = { defaultXML1, defaultXML2, defaultXML3, defaultXML4 }; diff --git a/gui/themes/scummclassic.zip b/gui/themes/scummclassic.zip index b59e9b0cc7..eb43825106 100644 Binary files a/gui/themes/scummclassic.zip and b/gui/themes/scummclassic.zip differ diff --git a/gui/themes/scummclassic/classic_gfx.stx b/gui/themes/scummclassic/classic_gfx.stx index 8b72802ef7..a5fa994e89 100644 --- a/gui/themes/scummclassic/classic_gfx.stx +++ b/gui/themes/scummclassic/classic_gfx.stx @@ -37,6 +37,9 @@ + @@ -89,7 +92,7 @@ /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/themes/scummclassic/classic_layout_lowres.stx b/gui/themes/scummclassic/classic_layout_lowres.stx index e1d90fff50..e65e3d2ca7 100644 --- a/gui/themes/scummclassic/classic_layout_lowres.stx +++ b/gui/themes/scummclassic/classic_layout_lowres.stx @@ -53,6 +53,8 @@ + + @@ -1775,4 +1777,6 @@ + + diff --git a/gui/themes/scummmodern.zip b/gui/themes/scummmodern.zip index 75064465e4..f7a69004d3 100644 Binary files a/gui/themes/scummmodern.zip and b/gui/themes/scummmodern.zip differ diff --git a/gui/themes/scummmodern/scummmodern_gfx.stx b/gui/themes/scummmodern/scummmodern_gfx.stx index 0e62f36627..c449c1a30e 100644 --- a/gui/themes/scummmodern/scummmodern_gfx.stx +++ b/gui/themes/scummmodern/scummmodern_gfx.stx @@ -86,6 +86,9 @@ + @@ -208,7 +211,7 @@ /> @@ -856,6 +859,361 @@ /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -160,31 +165,31 @@ @@ -260,7 +265,7 @@ - + + /> - - - + /> + + - + + diff --git a/gui/themes/scummmodern/scummmodern_layout_lowres.stx b/gui/themes/scummmodern/scummmodern_layout_lowres.stx index 20363bbba0..7324663fee 100644 --- a/gui/themes/scummmodern/scummmodern_layout_lowres.stx +++ b/gui/themes/scummmodern/scummmodern_layout_lowres.stx @@ -43,6 +43,8 @@ + + @@ -1793,4 +1795,6 @@ + + diff --git a/gui/themes/scummremastered.zip b/gui/themes/scummremastered.zip index b4f7c0eebb..b9b247c84a 100644 Binary files a/gui/themes/scummremastered.zip and b/gui/themes/scummremastered.zip differ diff --git a/gui/themes/scummremastered/remastered_gfx.stx b/gui/themes/scummremastered/remastered_gfx.stx index 76b722c5d5..c18a8bf358 100644 --- a/gui/themes/scummremastered/remastered_gfx.stx +++ b/gui/themes/scummremastered/remastered_gfx.stx @@ -36,13 +36,13 @@ rgb = '180, 39, 9' /> - - + - @@ -73,6 +73,9 @@ + @@ -81,8 +84,8 @@ /> - - + @@ -128,7 +131,7 @@ scalable_file = 'FreeSansBold.ttf' /> @@ -287,7 +290,7 @@ stroke = '1' radius = '10' fill = 'gradient' - fg_color = 'blandyellow' + fg_color = 'blandyellow' gradient_start = 'button_hover' gradient_end = 'button_hover' /> @@ -298,7 +301,7 @@ stroke = '1' radius = '10' fill = 'gradient' - fg_color = 'blandyellow' + fg_color = 'blandyellow' gradient_start = 'button_idle' gradient_end = 'button_idle' /> @@ -807,7 +810,7 @@ fill = 'background' shadow = '2' fg_color = 'darkredborder' - bg_color = 'button_idle' + bg_color = 'button_idle' bevel = '0' bevel_color = 'brightredborder' /> @@ -826,7 +829,7 @@ fill = 'background' shadow = '2' fg_color = 'darkredborder' - bg_color = 'button_hover' + bg_color = 'button_hover' bevel = '0' bevel_color = 'brightredborder' /> @@ -852,6 +855,287 @@ /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -160,31 +165,31 @@ @@ -1820,5 +1825,6 @@ - + + diff --git a/gui/themes/scummremastered/remastered_layout_lowres.stx b/gui/themes/scummremastered/remastered_layout_lowres.stx index 3f8ec01a28..2a20442468 100644 --- a/gui/themes/scummremastered/remastered_layout_lowres.stx +++ b/gui/themes/scummremastered/remastered_layout_lowres.stx @@ -43,6 +43,8 @@ + + @@ -1793,4 +1795,6 @@ + + diff --git a/gui/widget.cpp b/gui/widget.cpp index 750fa4e80f..bbb8b06ee6 100644 --- a/gui/widget.cpp +++ b/gui/widget.cpp @@ -32,6 +32,7 @@ #include "gui/ThemeEval.h" #include "gui/dialog.h" +#include "gui/widgets/popup.h" namespace GUI { @@ -322,7 +323,7 @@ void StaticTextWidget::drawWidget() { ButtonWidget::ButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Common::String &label, const char *tooltip, uint32 cmd, uint8 hotkey) : StaticTextWidget(boss, x, y, w, h, cleanupHotkey(label), Graphics::kTextAlignCenter, tooltip), CommandSender(boss), - _cmd(cmd), _hotkey(hotkey), _lastTime(0), _duringPress(false) { + _cmd(cmd), _hotkey(hotkey), _duringPress(false) { if (hotkey == 0) _hotkey = parseHotkey(label); @@ -333,7 +334,7 @@ ButtonWidget::ButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Co ButtonWidget::ButtonWidget(GuiObject *boss, const Common::String &name, const Common::String &label, const char *tooltip, uint32 cmd, uint8 hotkey) : StaticTextWidget(boss, name, cleanupHotkey(label), tooltip), CommandSender(boss), - _cmd(cmd), _hotkey(hotkey), _lastTime(0), _duringPress(false) { + _cmd(cmd), _hotkey(hotkey), _duringPress(false) { if (hotkey == 0) _hotkey = parseHotkey(label); setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG); @@ -400,6 +401,104 @@ void ButtonWidget::setUnpressedState() { #pragma mark - +DropdownButtonWidget::DropdownButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Common::String &label, const char *tooltip, uint32 cmd, uint8 hotkey) : + ButtonWidget(boss, x, y, w, h, label, tooltip, cmd, hotkey) { + setFlags(getFlags() | WIDGET_TRACK_MOUSE); + + reset(); +} + +DropdownButtonWidget::DropdownButtonWidget(GuiObject *boss, const Common::String &name, const Common::String &label, const char *tooltip, uint32 cmd, uint8 hotkey) : + ButtonWidget(boss, name, label, tooltip, cmd, hotkey) { + setFlags(getFlags() | WIDGET_TRACK_MOUSE); + + reset(); +} + +void DropdownButtonWidget::reset() { + _inDropdown = false; + _inButton = false; + _dropdownWidth = g_gui.xmlEval()->getVar("Globals.DropdownButton.Width", 13); +} + +bool DropdownButtonWidget::isInDropDown(int x, int y) const { + Common::Rect dropdownRect(_w - _dropdownWidth, 0, _w, _h); + return dropdownRect.contains(x, y); +} + +void DropdownButtonWidget::handleMouseMoved(int x, int y, int button) { + if (_entries.empty()) { + return; + } + + // Detect which part of the button the cursor is over + bool inDropdown = isInDropDown(x, y); + bool inButton = Common::Rect(_w, _h).contains(x, y) && !inDropdown; + + if (inDropdown != _inDropdown) { + _inDropdown = inDropdown; + markAsDirty(); + } + + if (inButton != _inButton) { + _inButton = inButton; + markAsDirty(); + } +} + +void DropdownButtonWidget::handleMouseUp(int x, int y, int button, int clickCount) { + if (isEnabled() && !_entries.empty() && _duringPress && isInDropDown(x, y)) { + + PopUpDialog popupDialog(this, "DropdownDialog", x + getAbsX(), y + getAbsY()); + popupDialog.setPosition(getAbsX(), getAbsY() + _h); + popupDialog.setLineHeight(_h); + popupDialog.setPadding(_dropdownWidth, _dropdownWidth); + + for (uint i = 0; i < _entries.size(); i++) { + popupDialog.appendEntry(_entries[i].label); + } + + int newSel = popupDialog.runModal(); + if (newSel != -1) { + sendCommand(_entries[newSel].cmd, 0); + } + + setUnpressedState(); + _duringPress = false; + } else { + ButtonWidget::handleMouseUp(x, y, button, clickCount); + } +} + +void DropdownButtonWidget::reflowLayout() { + ButtonWidget::reflowLayout(); + + reset(); +} + +void DropdownButtonWidget::appendEntry(const Common::String &label, uint32 cmd) { + Entry e; + e.label = label; + e.cmd = cmd; + _entries.push_back(e); +} + +void DropdownButtonWidget::clearEntries() { + _entries.clear(); +} + +void DropdownButtonWidget::drawWidget() { + if (_entries.empty()) { + // Degrade to a regular button + g_gui.theme()->drawButton(Common::Rect(_x, _y, _x + _w, _y + _h), _label, _state); + } else { + g_gui.theme()->drawDropDownButton(Common::Rect(_x, _y, _x + _w, _y + _h), _dropdownWidth, _label, + _state, _inButton, _inDropdown); + } +} + +#pragma mark - + PicButtonWidget::PicButtonWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip, uint32 cmd, uint8 hotkey) : ButtonWidget(boss, x, y, w, h, "", tooltip, cmd, hotkey), _alpha(255), _transparency(false), _showButton(true) { diff --git a/gui/widget.h b/gui/widget.h index 06df49629d..2339012074 100644 --- a/gui/widget.h +++ b/gui/widget.h @@ -234,8 +234,39 @@ public: protected: void drawWidget(); bool _duringPress; -private: - uint32 _lastTime; +}; + +/* DropdownButtonWidget */ +class DropdownButtonWidget : public ButtonWidget { +public: + DropdownButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Common::String &label, const char *tooltip = nullptr, uint32 cmd = 0, uint8 hotkey = 0); + DropdownButtonWidget(GuiObject *boss, const Common::String &name, const Common::String &label, const char *tooltip = nullptr, uint32 cmd = 0, uint8 hotkey = 0); + + void handleMouseMoved(int x, int y, int button) override; + void handleMouseUp(int x, int y, int button, int clickCount) override; + void reflowLayout() override; + + void appendEntry(const Common::String &label, uint32 cmd); + void clearEntries(); + +protected: + struct Entry { + Common::String label; + uint32 cmd; + }; + typedef Common::Array EntryList; + + // Widget API + void drawWidget() override; + + void reset(); + bool isInDropDown(int x, int y) const; + + EntryList _entries; + + uint32 _dropdownWidth; + bool _inDropdown; + bool _inButton; }; /* PicButtonWidget */ diff --git a/gui/widgets/popup.cpp b/gui/widgets/popup.cpp index 970e35ab23..351d0fe559 100644 --- a/gui/widgets/popup.cpp +++ b/gui/widgets/popup.cpp @@ -21,7 +21,6 @@ */ #include "common/system.h" -#include "gui/dialog.h" #include "gui/gui-manager.h" #include "gui/widgets/popup.h" @@ -33,62 +32,35 @@ namespace GUI { // PopUpDialog // -class PopUpDialog : public Dialog { -protected: - PopUpWidget *_popUpBoss; - int _clickX, _clickY; - int _selection; - uint32 _openTime; - bool _twoColumns; - int _entriesPerColumn; - - int _leftPadding; - int _rightPadding; - - int _lastRead; - -public: - PopUpDialog(PopUpWidget *boss, int clickX, int clickY); - - void drawDialog(DrawLayer layerToDraw) override; - - void handleMouseUp(int x, int y, int button, int clickCount) override; - void handleMouseWheel(int x, int y, int direction) override; // Scroll through entries with scroll wheel - void handleMouseMoved(int x, int y, int button) override; // Redraw selections depending on mouse position - void handleMouseLeft(int button) override; - void handleKeyDown(Common::KeyState state) override; // Scroll through entries with arrow keys etc. - -protected: - void drawMenuEntry(int entry, bool hilite); - - int findItem(int x, int y) const; - void setSelection(int item); - bool isMouseDown(); - - void moveUp(); - void moveDown(); - void read(Common::String); -}; - -PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY) - : Dialog(0, 0, 16, 16), - _popUpBoss(boss) { +PopUpDialog::PopUpDialog(Widget *boss, const Common::String &name, int clickX, int clickY): + Dialog(name), + _boss(boss), + // Remember original mouse position + _clickX(clickX), + _clickY(clickY), + _selection(-1), + _initialSelection(-1), + _openTime(0), + _twoColumns(false), + _entriesPerColumn(1), + _leftPadding(0), + _rightPadding(0), + _lineHeight(kLineHeight), + _lastRead(-1) { _backgroundType = ThemeEngine::kDialogBackgroundNone; + _w = _boss->getWidth(); +} - _openTime = 0; - _entriesPerColumn = 1; +void PopUpDialog::open() { + // Time the popup was opened + _openTime = g_system->getMillis(); - // Copy the selection index - _selection = _popUpBoss->_selectedItem; + _initialSelection = _selection; // Calculate real popup dimensions - _x = _popUpBoss->getAbsX(); - _y = _popUpBoss->getAbsY() - _popUpBoss->_selectedItem * kLineHeight; - _h = _popUpBoss->_entries.size() * kLineHeight + 2; - _w = _popUpBoss->_w - kLineHeight + 2; + _h = _entries.size() * _lineHeight + 2; - _leftPadding = _popUpBoss->_leftPadding; - _rightPadding = _popUpBoss->_rightPadding; + _entriesPerColumn = 1; // Perform clipping / switch to scrolling mode if we don't fit on the screen // FIXME - OSystem should send out notification messages when the screen @@ -103,16 +75,16 @@ PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY) const int screenW = g_system->getOverlayWidth(); _twoColumns = true; - _entriesPerColumn = _popUpBoss->_entries.size() / 2; + _entriesPerColumn = _entries.size() / 2; - if (_popUpBoss->_entries.size() & 1) + if (_entries.size() & 1) _entriesPerColumn++; - _h = _entriesPerColumn * kLineHeight + 2; + _h = _entriesPerColumn * _lineHeight + 2; _w = 0; - for (uint i = 0; i < _popUpBoss->_entries.size(); i++) { - int width = g_gui.getStringWidth(_popUpBoss->_entries[i].name); + for (uint i = 0; i < _entries.size(); i++) { + int width = g_gui.getStringWidth(_entries[i]); if (width > _w) _w = width; @@ -123,9 +95,9 @@ PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY) if (!(_w & 1)) _w++; - if (_popUpBoss->_selectedItem >= _entriesPerColumn) { + if (_selection >= _entriesPerColumn) { _x -= _w / 2; - _y = _popUpBoss->getAbsY() - (_popUpBoss->_selectedItem - _entriesPerColumn) * kLineHeight; + _y = _boss->getAbsY() - (_selection - _entriesPerColumn) * _lineHeight; } if (_w >= screenW) @@ -146,11 +118,12 @@ PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY) // TODO - implement scrolling if we had to move the menu, or if there are too many entries - // Remember original mouse position - _clickX = clickX - _x; - _clickY = clickY - _y; - _lastRead = -1; + + Dialog::open(); +} + +void PopUpDialog::reflowLayout() { } void PopUpDialog::drawDialog(DrawLayer layerToDraw) { @@ -163,7 +136,7 @@ void PopUpDialog::drawDialog(DrawLayer layerToDraw) { g_gui.vLine(_x + _w / 2, _y, _y + _h - 2, g_gui._color);*/ // Draw the entries - int count = _popUpBoss->_entries.size(); + int count = _entries.size(); for (int i = 0; i < count; i++) { drawMenuEntry(i, i == _selection); } @@ -172,17 +145,15 @@ void PopUpDialog::drawDialog(DrawLayer layerToDraw) { /*if (_twoColumns && (count & 1)) { g_gui.fillRect(_x + 1 + _w / 2, _y + 1 + kLineHeight * (_entriesPerColumn - 1), _w / 2 - 1, kLineHeight, g_gui._bgcolor); }*/ - - if (_openTime == 0) { - // Time the popup was opened - _openTime = g_system->getMillis(); - } } void PopUpDialog::handleMouseUp(int x, int y, int button, int clickCount) { + int absX = x + getAbsX(); + int absY = y + getAbsY(); + // Mouse was released. If it wasn't moved much since the original mouse down, // let the popup stay open. If it did move, assume the user made his selection. - int dist = (_clickX - x) * (_clickX - x) + (_clickY - y) * (_clickY - y); + int dist = (_clickX - absX) * (_clickX - absX) + (_clickY - absY) * (_clickY - absY); if (dist > 3 * 3 || g_system->getMillis() - _openTime > 300) { setResult(_selection); close(); @@ -203,18 +174,18 @@ void PopUpDialog::handleMouseMoved(int x, int y, int button) { // Compute over which item the mouse is... int item = findItem(x, y); - if (item >= 0 && _popUpBoss->_entries[item].name.size() == 0) + if (item >= 0 && _entries[item].size() == 0) item = -1; if (item == -1 && !isMouseDown()) { - setSelection(_popUpBoss->_selectedItem); + setSelection(_initialSelection); return; } // ...and update the selection accordingly setSelection(item); - if (_lastRead != item && _popUpBoss->_entries.size() > 0 && item != -1) { - read(_popUpBoss->_entries[item].name); + if (_lastRead != item && _entries.size() > 0 && item != -1) { + read(_entries[item]); _lastRead = item; } } @@ -261,7 +232,7 @@ void PopUpDialog::handleKeyDown(Common::KeyState state) { break; // fall through case Common::KEYCODE_END: - setSelection(_popUpBoss->_entries.size()-1); + setSelection(_entries.size()-1); break; case Common::KEYCODE_KP2: @@ -293,19 +264,45 @@ void PopUpDialog::handleKeyDown(Common::KeyState state) { } } +void PopUpDialog::setPosition(int x, int y) { + _x = x; + _y = y; +} + +void PopUpDialog::setPadding(int left, int right) { + _leftPadding = left; + _rightPadding = right; +} + +void PopUpDialog::setLineHeight(int lineHeight) { + _lineHeight = lineHeight; +} + +void PopUpDialog::setWidth(uint16 width) { + _w = width; +} + +void PopUpDialog::appendEntry(const Common::String &entry) { + _entries.push_back(entry); +} + +void PopUpDialog::clearEntries() { + _entries.clear(); +} + int PopUpDialog::findItem(int x, int y) const { if (x >= 0 && x < _w && y >= 0 && y < _h) { if (_twoColumns) { - uint entry = (y - 2) / kLineHeight; + uint entry = (y - 2) / _lineHeight; if (x > _w / 2) { entry += _entriesPerColumn; - if (entry >= _popUpBoss->_entries.size()) + if (entry >= _entries.size()) return -1; } return entry; } - return (y - 2) / kLineHeight; + return (y - 2) / _lineHeight; } return -1; } @@ -335,19 +332,19 @@ bool PopUpDialog::isMouseDown() { void PopUpDialog::moveUp() { if (_selection < 0) { - setSelection(_popUpBoss->_entries.size() - 1); + setSelection(_entries.size() - 1); } else if (_selection > 0) { int item = _selection; do { item--; - } while (item >= 0 && _popUpBoss->_entries[item].name.size() == 0); + } while (item >= 0 && _entries[item].size() == 0); if (item >= 0) setSelection(item); } } void PopUpDialog::moveDown() { - int lastItem = _popUpBoss->_entries.size() - 1; + int lastItem = _entries.size() - 1; if (_selection < 0) { setSelection(0); @@ -355,7 +352,7 @@ void PopUpDialog::moveDown() { int item = _selection; do { item++; - } while (item <= lastItem && _popUpBoss->_entries[item].name.size() == 0); + } while (item <= lastItem && _entries[item].size() == 0); if (item <= lastItem) setSelection(item); } @@ -367,34 +364,34 @@ void PopUpDialog::drawMenuEntry(int entry, bool hilite) { int x, y, w; if (_twoColumns) { - int n = _popUpBoss->_entries.size() / 2; + int n = _entries.size() / 2; - if (_popUpBoss->_entries.size() & 1) + if (_entries.size() & 1) n++; if (entry >= n) { x = _x + 1 + _w / 2; - y = _y + 1 + kLineHeight * (entry - n); + y = _y + 1 + _lineHeight * (entry - n); } else { x = _x + 1; - y = _y + 1 + kLineHeight * entry; + y = _y + 1 + _lineHeight * entry; } w = _w / 2 - 1; } else { x = _x + 1; - y = _y + 1 + kLineHeight * entry; + y = _y + 1 + _lineHeight * entry; w = _w - 2; } - Common::String &name(_popUpBoss->_entries[entry].name); + Common::String &name(_entries[entry]); if (name.size() == 0) { // Draw a separator - g_gui.theme()->drawLineSeparator(Common::Rect(x, y, x + w, y + kLineHeight)); + g_gui.theme()->drawLineSeparator(Common::Rect(x, y, x + w, y + _lineHeight)); } else { g_gui.theme()->drawText( - Common::Rect(x + 1, y + 2, x + w, y + 2 + kLineHeight), + Common::Rect(x + 1, y + 2, x + w, y + 2 + _lineHeight), name, hilite ? ThemeEngine::kStateHighlight : ThemeEngine::kStateEnabled, Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, _leftPadding ); @@ -429,7 +426,17 @@ PopUpWidget::PopUpWidget(GuiObject *boss, int x, int y, int w, int h, const char void PopUpWidget::handleMouseDown(int x, int y, int button, int clickCount) { if (isEnabled()) { - PopUpDialog popupDialog(this, x + getAbsX(), y + getAbsY()); + PopUpDialog popupDialog(this, "", x + getAbsX(), y + getAbsY()); + popupDialog.setPosition(getAbsX(), getAbsY() - _selectedItem * kLineHeight); + popupDialog.setPadding(_leftPadding, _rightPadding); + popupDialog.setWidth(getWidth() - kLineHeight + 2); + + + for (uint i = 0; i < _entries.size(); i++) { + popupDialog.appendEntry(_entries[i].name); + } + popupDialog.setSelection(_selectedItem); + int newSel = popupDialog.runModal(); if (newSel != -1 && _selectedItem != newSel) { _selectedItem = newSel; diff --git a/gui/widgets/popup.h b/gui/widgets/popup.h index 3ccf8787d5..d7c218f2d3 100644 --- a/gui/widgets/popup.h +++ b/gui/widgets/popup.h @@ -23,6 +23,7 @@ #ifndef GUI_WIDGETS_POPUP_H #define GUI_WIDGETS_POPUP_H +#include "gui/dialog.h" #include "gui/widget.h" #include "common/str.h" #include "common/array.h" @@ -41,7 +42,6 @@ enum { * is broadcast, with data being equal to the tag value of the selected entry. */ class PopUpWidget : public Widget, public CommandSender { - friend class PopUpDialog; typedef Common::String String; struct Entry { @@ -85,6 +85,63 @@ protected: void drawWidget(); }; +/** + * A small dialog showing a list of items and allowing the user to chose one of them + * + * Used by PopUpWidget and DropdownButtonWidget. + */ +class PopUpDialog : public Dialog { +protected: + Widget *_boss; + int _clickX, _clickY; + int _selection; + int _initialSelection; + uint32 _openTime; + bool _twoColumns; + int _entriesPerColumn; + + int _leftPadding; + int _rightPadding; + int _lineHeight; + + int _lastRead; + + typedef Common::Array EntryList; + EntryList _entries; + +public: + PopUpDialog(Widget *boss, const Common::String &name, int clickX, int clickY); + + void open() override; + void reflowLayout() override; + void drawDialog(DrawLayer layerToDraw) override; + + void handleMouseUp(int x, int y, int button, int clickCount) override; + void handleMouseWheel(int x, int y, int direction) override; // Scroll through entries with scroll wheel + void handleMouseMoved(int x, int y, int button) override; // Redraw selections depending on mouse position + void handleMouseLeft(int button) override; + void handleKeyDown(Common::KeyState state) override; // Scroll through entries with arrow keys etc. + + void setPosition(int x, int y); + void setPadding(int left, int right); + void setLineHeight(int lineHeight); + void setWidth(uint16 width); + + void appendEntry(const Common::String &entry); + void clearEntries(); + void setSelection(int item); + +protected: + void drawMenuEntry(int entry, bool hilite); + + int findItem(int x, int y) const; + bool isMouseDown(); + + void moveUp(); + void moveDown(); + void read(Common::String); +}; + } // End of namespace GUI #endif -- cgit v1.2.3