From 69a2099c04527404f2d0942f2088b3d22dd75b5a Mon Sep 17 00:00:00 2001 From: Hugues Ross Date: Sat, 12 Oct 2019 12:44:23 -0400 Subject: [PATCH] Add more visual feedback for button states (#8916) - Add style properties for overriding the the hovered/pressed state - By default, hovered buttons are a lighter version of the base color - By default, pressed buttons are a darker version of the base color - Add hovered bg image support for image buttons (style property) --- doc/lua_api.txt | 3 + games/minimal/mods/test/formspec.lua | 12 ++-- src/gui/StyleSpec.h | 9 +++ src/gui/guiButton.cpp | 84 ++++++++++++++++++++++++++-- src/gui/guiButton.h | 38 +++++++------ src/gui/guiConfirmRegistration.cpp | 5 +- src/gui/guiFormSpecMenu.cpp | 21 ++++++- src/gui/guiKeyChangeMenu.cpp | 9 ++- src/gui/guiPasswordChange.cpp | 5 +- src/gui/guiVolumeChange.cpp | 4 +- 10 files changed, 151 insertions(+), 39 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 907f47e73..5640be73c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2501,7 +2501,10 @@ Some types may inherit styles from parent types. * button, button_exit * alpha - boolean, whether to draw alpha in bgimg. Default true. * bgcolor - color, sets button tint. + * bgcolor_hovered - color when hovered. Defaults to a lighter bgcolor when not provided. + * bgcolor_pressed - color when pressed. Defaults to a darker bgcolor when not provided. * bgimg - standard image. Defaults to none. + * bgimg_hovered - image when hovered. Defaults to bgimg when not provided. * bgimg_pressed - image when pressed. Defaults to bgimg when not provided. * border - boolean, draw border. Set to false to hide the bevelled button pane. Default true. * noclip - boolean, set to true to allow the element to exceed formspec bounds. diff --git a/games/minimal/mods/test/formspec.lua b/games/minimal/mods/test/formspec.lua index a4d120b41..6371e3d44 100644 --- a/games/minimal/mods/test/formspec.lua +++ b/games/minimal/mods/test/formspec.lua @@ -27,19 +27,23 @@ local clip_fs = [[ local style_fs = [[ - style[one_btn1;bgcolor=red;textcolor=yellow] + style[one_btn1;bgcolor=red;textcolor=yellow;bgcolor_hovered=orange; + bgcolor_pressed=purple] button[0,0;2.5,0.8;one_btn1;Button] style[one_btn2;border=false;textcolor=cyan] button[0,1.05;2.5,0.8;one_btn2;Text Button] - style[one_btn3;bgimg=bubble.png;bgimg_pressed=heart.png] + style[one_btn3;bgimg=bubble.png;bgimg_hovered=default_apple.png; + bgimg_pressed=heart.png] button[0,2.1;1,1;one_btn3;Bor] - style[one_btn4;bgimg=bubble.png;bgimg_pressed=heart.png;border=false] + style[one_btn4;bgimg=bubble.png;bgimg_hovered=default_apple.png; + bgimg_pressed=heart.png;border=false] button[1.25,2.1;1,1;one_btn4;Bub] - style[one_btn5;bgimg=bubble.png;bgimg_pressed=heart.png;border=false;alpha=false] + style[one_btn5;bgimg=bubble.png;bgimg_hovered=default_apple.png; + bgimg_pressed=heart.png;border=false;alpha=false] button[0,3.35;1,1;one_btn5;Alph] style[one_btn6;border=true] diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index 29aae0836..5784a552c 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -29,9 +29,12 @@ public: { TEXTCOLOR, BGCOLOR, + BGCOLOR_HOVERED, + BGCOLOR_PRESSED, NOCLIP, BORDER, BGIMG, + BGIMG_HOVERED, BGIMG_PRESSED, ALPHA, NUM_PROPERTIES, @@ -49,12 +52,18 @@ public: return TEXTCOLOR; } else if (name == "bgcolor") { return BGCOLOR; + } else if (name == "bgcolor_hovered") { + return BGCOLOR_HOVERED; + } else if (name == "bgcolor_pressed") { + return BGCOLOR_PRESSED; } else if (name == "noclip") { return NOCLIP; } else if (name == "border") { return BORDER; } else if (name == "bgimg") { return BGIMG; + } else if (name == "bgimg_hovered") { + return BGIMG_HOVERED; } else if (name == "bgimg_pressed") { return BGIMG_PRESSED; } else if (name == "alpha") { diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index 60d330f4a..6ed5d3772 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -14,6 +14,12 @@ using namespace irr; using namespace gui; +// Multiply with a color to get the default corresponding hovered color +#define COLOR_HOVERED_MOD 1.25f + +// Multiply with a color to get the default corresponding pressed color +#define COLOR_PRESSED_MOD 0.85f + //! constructor GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent, s32 id, core::rect rectangle, bool noclip) @@ -34,6 +40,14 @@ GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent, // PATCH for (size_t i = 0; i < 4; i++) { Colors[i] = Environment->getSkin()->getColor((EGUI_DEFAULT_COLOR)i); + HoveredColors[i] = irr::video::SColor(Colors[i].getAlpha(), + core::clamp(Colors[i].getRed() * COLOR_HOVERED_MOD, 0, 255), + core::clamp(Colors[i].getGreen() * COLOR_HOVERED_MOD, 0, 255), + core::clamp(Colors[i].getBlue() * COLOR_HOVERED_MOD, 0, 255)); + PressedColors[i] = irr::video::SColor(Colors[i].getAlpha(), + core::clamp(Colors[i].getRed() * COLOR_PRESSED_MOD, 0, 255), + core::clamp(Colors[i].getGreen() * COLOR_PRESSED_MOD, 0, 255), + core::clamp(Colors[i].getBlue() * COLOR_PRESSED_MOD, 0, 255)); } // END PATCH } @@ -247,13 +261,15 @@ void GUIButton::draw() if (!Pressed) { // PATCH - skin->drawColored3DButtonPaneStandard(this, AbsoluteRect, &AbsoluteClippingRect, Colors); + skin->drawColored3DButtonPaneStandard(this, AbsoluteRect, &AbsoluteClippingRect, + Environment->getHovered() == this ? HoveredColors : Colors); // END PATCH } else { // PATCH - skin->drawColored3DButtonPanePressed(this, AbsoluteRect, &AbsoluteClippingRect, Colors); + skin->drawColored3DButtonPanePressed(this, + AbsoluteRect, &AbsoluteClippingRect, PressedColors); // END PATCH } } @@ -389,10 +405,11 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const // find a compatible state that has images while ( state != EGBIS_IMAGE_UP && !ButtonImages[(u32)state].Texture ) { + // PATCH switch ( state ) { case EGBIS_IMAGE_UP_FOCUSED: - state = EGBIS_IMAGE_UP_MOUSEOVER; + state = EGBIS_IMAGE_UP; break; case EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER: state = EGBIS_IMAGE_UP_FOCUSED; @@ -401,7 +418,7 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const state = EGBIS_IMAGE_DOWN; break; case EGBIS_IMAGE_DOWN_FOCUSED: - state = EGBIS_IMAGE_DOWN_MOUSEOVER; + state = EGBIS_IMAGE_DOWN; break; case EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER: state = EGBIS_IMAGE_DOWN_FOCUSED; @@ -415,6 +432,7 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const default: state = EGBIS_IMAGE_UP; } + // END PATCH } return state; @@ -490,6 +508,40 @@ void GUIButton::setImage(EGUI_BUTTON_IMAGE_STATE state, video::ITexture* image, ButtonImages[stateIdx].SourceRect = sourceRect; } +// PATCH +void GUIButton::setImage(video::ITexture* image) +{ + setImage(gui::EGBIS_IMAGE_UP, image); +} + +void GUIButton::setImage(video::ITexture* image, const core::rect& pos) +{ + setImage(gui::EGBIS_IMAGE_UP, image, pos); +} + +void GUIButton::setPressedImage(video::ITexture* image) +{ + setImage(gui::EGBIS_IMAGE_DOWN, image); +} + +void GUIButton::setPressedImage(video::ITexture* image, const core::rect& pos) +{ + setImage(gui::EGBIS_IMAGE_DOWN, image, pos); +} + +void GUIButton::setHoveredImage(video::ITexture* image) +{ + setImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image); + setImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image); +} + +void GUIButton::setHoveredImage(video::ITexture* image, const core::rect& pos) +{ + setImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image, pos); + setImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image, pos); +} +// END PATCH + //! Sets if the button should behave like a push button. Which means it //! can be in two states: Normal or Pressed. With a click on the button, //! the user can change the state of the button. @@ -644,6 +696,30 @@ void GUIButton::setColor(video::SColor color) for (size_t i = 0; i < 4; i++) { video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i); Colors[i] = base.getInterpolated(color, d); + HoveredColors[i] = irr::video::SColor(Colors[i].getAlpha(), + core::clamp(Colors[i].getRed() * COLOR_HOVERED_MOD, 0, 255), + core::clamp(Colors[i].getGreen() * COLOR_HOVERED_MOD, 0, 255), + core::clamp(Colors[i].getBlue() * COLOR_HOVERED_MOD, 0, 255)); + PressedColors[i] = irr::video::SColor(Colors[i].getAlpha(), + core::clamp(Colors[i].getRed() * COLOR_PRESSED_MOD, 0, 255), + core::clamp(Colors[i].getGreen() * COLOR_PRESSED_MOD, 0, 255), + core::clamp(Colors[i].getBlue() * COLOR_PRESSED_MOD, 0, 255)); + } +} +void GUIButton::setHoveredColor(video::SColor color) +{ + float d = 0.65f; + for (size_t i = 0; i < 4; i++) { + video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i); + HoveredColors[i] = base.getInterpolated(color, d); + } +} +void GUIButton::setPressedColor(video::SColor color) +{ + float d = 0.65f; + for (size_t i = 0; i < 4; i++) { + video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i); + PressedColors[i] = base.getInterpolated(color, d); } } // END PATCH diff --git a/src/gui/guiButton.h b/src/gui/guiButton.h index 63e29ccfc..3c353d240 100644 --- a/src/gui/guiButton.h +++ b/src/gui/guiButton.h @@ -102,34 +102,30 @@ public: //! Checks if an override color is enabled virtual bool isOverrideColorEnabled(void) const; + // PATCH //! Sets an image which should be displayed on the button when it is in the given state. virtual void setImage(gui::EGUI_BUTTON_IMAGE_STATE state, - video::ITexture* image=0, + video::ITexture* image=nullptr, const core::rect& sourceRect=core::rect(0,0,0,0)); //! Sets an image which should be displayed on the button when it is in normal state. - virtual void setImage(video::ITexture* image=0) override - { - setImage(gui::EGBIS_IMAGE_UP, image); - } + virtual void setImage(video::ITexture* image=nullptr) override; //! Sets an image which should be displayed on the button when it is in normal state. - virtual void setImage(video::ITexture* image, const core::rect& pos) override - { - setImage(gui::EGBIS_IMAGE_UP, image, pos); - } + virtual void setImage(video::ITexture* image, const core::rect& pos) override; //! Sets an image which should be displayed on the button when it is in pressed state. - virtual void setPressedImage(video::ITexture* image=0) override - { - setImage(gui::EGBIS_IMAGE_DOWN, image); - } + virtual void setPressedImage(video::ITexture* image=nullptr) override; //! Sets an image which should be displayed on the button when it is in pressed state. - virtual void setPressedImage(video::ITexture* image, const core::rect& pos) override - { - setImage(gui::EGBIS_IMAGE_DOWN, image, pos); - } + virtual void setPressedImage(video::ITexture* image, const core::rect& pos) override; + + //! Sets an image which should be displayed on the button when it is in hovered state. + virtual void setHoveredImage(video::ITexture* image=nullptr); + // END PATCH + + //! Sets an image which should be displayed on the button when it is in hovered state. + virtual void setHoveredImage(video::ITexture* image, const core::rect& pos); //! Sets the sprite bank used by the button virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override; @@ -215,6 +211,10 @@ public: void setColor(video::SColor color); + // PATCH + void setHoveredColor(video::SColor color); + void setPressedColor(video::SColor color); + // END PATCH //! Do not drop returned handle @@ -307,4 +307,8 @@ private: bool ScaleImage; video::SColor Colors[4]; + // PATCH + video::SColor HoveredColors[4]; + video::SColor PressedColors[4]; + // END PATCH }; diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp index 6fe2a4fc4..49b81b01d 100644 --- a/src/gui/guiConfirmRegistration.cpp +++ b/src/gui/guiConfirmRegistration.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiConfirmRegistration.h" #include "client/client.h" +#include "guiButton.h" #include #include #include @@ -128,14 +129,14 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) core::rect rect2(0, 0, 230 * s, 35 * s); rect2 = rect2 + v2s32(size.X / 2 - 220 * s, ypos); text = wgettext("Register and Join"); - Environment->addButton(rect2, this, ID_confirm, text); + GUIButton::addButton(Environment, rect2, this, ID_confirm, text); delete[] text; } { core::rect rect2(0, 0, 120 * s, 35 * s); rect2 = rect2 + v2s32(size.X / 2 + 70 * s, ypos); text = wgettext("Cancel"); - Environment->addButton(rect2, this, ID_cancel, text); + GUIButton::addButton(Environment, rect2, this, ID_cancel, text); delete[] text; } { diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index aee7da869..ea6072cab 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -712,6 +712,13 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, if (style.isNotDefault(StyleSpec::BGCOLOR)) { e->setColor(style.getColor(StyleSpec::BGCOLOR)); } + if (style.isNotDefault(StyleSpec::BGCOLOR_HOVERED)) { + e->setHoveredColor(style.getColor(StyleSpec::BGCOLOR_HOVERED)); + } + if (style.isNotDefault(StyleSpec::BGCOLOR_PRESSED)) { + e->setPressedColor(style.getColor(StyleSpec::BGCOLOR_PRESSED)); + } + if (style.isNotDefault(StyleSpec::TEXTCOLOR)) { e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR)); } @@ -720,11 +727,17 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, if (style.isNotDefault(StyleSpec::BGIMG)) { std::string image_name = style.get(StyleSpec::BGIMG, ""); + std::string hovered_image_name = style.get(StyleSpec::BGIMG_HOVERED, ""); std::string pressed_image_name = style.get(StyleSpec::BGIMG_PRESSED, ""); video::ITexture *texture = 0; + video::ITexture *hovered_texture = 0; video::ITexture *pressed_texture = 0; texture = m_tsrc->getTexture(image_name); + if (!hovered_image_name.empty()) + hovered_texture = m_tsrc->getTexture(hovered_image_name); + else + hovered_texture = texture; if (!pressed_image_name.empty()) pressed_texture = m_tsrc->getTexture(pressed_image_name); else @@ -733,6 +746,8 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, e->setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true)); e->setImage(guiScalingImageButton( Environment->getVideoDriver(), texture, geom.X, geom.Y)); + e->setHoveredImage(guiScalingImageButton( + Environment->getVideoDriver(), hovered_texture, geom.X, geom.Y)); e->setPressedImage(guiScalingImageButton( Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y)); e->setScaleImage(true); @@ -1620,7 +1635,7 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem else pressed_texture = texture; - gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); + GUIButton *e = GUIButton::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str()); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -1818,7 +1833,7 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & 258 + m_fields.size() ); - gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, L""); + gui::IGUIButton *e = GUIButton::addButton(Environment, rect, this, spec.fid, L""); auto style = getStyleForElement("item_image_button", spec.fname, "image_button"); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); @@ -2697,7 +2712,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) core::rect(size.X/2-70, pos.Y, (size.X/2-70)+140, pos.Y + (m_btn_height*2)); const wchar_t *text = wgettext("Proceed"); - Environment->addButton(mydata.rect, this, 257, text); + GUIButton::addButton(Environment, mydata.rect, this, 257, text); delete[] text; } diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index ca331a7d4..1d4f351cc 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -21,6 +21,7 @@ #include "guiKeyChangeMenu.h" #include "debug.h" +#include "guiButton.h" #include "serialization.h" #include #include @@ -157,7 +158,7 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) core::rect rect(0, 0, 100 * s, 30 * s); rect += topleft + v2s32(offset.X + 150 * s, offset.Y - 5 * s); const wchar_t *text = wgettext(k->key.name()); - k->button = Environment->addButton(rect, this, k->id, text); + k->button = GUIButton::addButton(Environment, rect, this, k->id, text); delete[] text; } if ((i + 1) % KMaxButtonPerColumns == 0) { @@ -217,16 +218,14 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) core::rect rect(0, 0, 100 * s, 30 * s); rect += topleft + v2s32(size.X / 2 - 105 * s, size.Y - 40 * s); const wchar_t *text = wgettext("Save"); - Environment->addButton(rect, this, GUI_ID_BACK_BUTTON, - text); + GUIButton::addButton(Environment, rect, this, GUI_ID_BACK_BUTTON, text); delete[] text; } { core::rect rect(0, 0, 100 * s, 30 * s); rect += topleft + v2s32(size.X / 2 + 5 * s, size.Y - 40 * s); const wchar_t *text = wgettext("Cancel"); - Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON, - text); + GUIButton::addButton(Environment, rect, this, GUI_ID_ABORT_BUTTON, text); delete[] text; } } diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp index 469c38dbe..af91ce84c 100644 --- a/src/gui/guiPasswordChange.cpp +++ b/src/gui/guiPasswordChange.cpp @@ -18,6 +18,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "guiPasswordChange.h" #include "client/client.h" +#include "guiButton.h" #include #include #include @@ -145,14 +146,14 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) core::rect rect(0, 0, 100 * s, 30 * s); rect = rect + v2s32(size.X / 4 + 56 * s, ypos); text = wgettext("Change"); - Environment->addButton(rect, this, ID_change, text); + GUIButton::addButton(Environment, rect, this, ID_change, text); delete[] text; } { core::rect rect(0, 0, 100 * s, 30 * s); rect = rect + v2s32(size.X / 4 + 185 * s, ypos); text = wgettext("Cancel"); - Environment->addButton(rect, this, ID_cancel, text); + GUIButton::addButton(Environment, rect, this, ID_cancel, text); delete[] text; } diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp index 45d2ee139..9428cde83 100644 --- a/src/gui/guiVolumeChange.cpp +++ b/src/gui/guiVolumeChange.cpp @@ -19,6 +19,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "guiVolumeChange.h" #include "debug.h" +#include "guiButton.h" #include "serialization.h" #include #include @@ -103,8 +104,7 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) core::rect rect(0, 0, 80 * s, 30 * s); rect = rect + v2s32(size.X / 2 - 80 * s / 2, size.Y / 2 + 55 * s); const wchar_t *text = wgettext("Exit"); - Environment->addButton(rect, this, ID_soundExitButton, - text); + GUIButton::addButton(Environment, rect, this, ID_soundExitButton, text); delete[] text; } {