From ebef65e90568cabd2ef73cc19e87cbe8617d6c5e Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 20 May 2025 20:37:16 +0200 Subject: [PATCH 1/6] Formspec: do not draw button backgrounds if border=false --- src/gui/guiButtonImage.cpp | 12 ++++++++++++ src/gui/guiButtonImage.h | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/gui/guiButtonImage.cpp b/src/gui/guiButtonImage.cpp index fd3481d85..f085d4383 100644 --- a/src/gui/guiButtonImage.cpp +++ b/src/gui/guiButtonImage.cpp @@ -24,6 +24,18 @@ GUIButtonImage::GUIButtonImage(gui::IGUIEnvironment *environment, sendToBack(m_image.get()); } +void GUIButtonImage::draw() +{ + if (isDrawingBorder()) { + // `GUIButton` also allows drawing different textures depending on + // `EGUI_BUTTON_STATE` --> Skip everything if the border is disabled (else case). + GUIButton::draw(); + } else { + IGUIElement::draw(); + } +} + + void GUIButtonImage::setForegroundImage(irr_ptr image, const core::rect &middle) { diff --git a/src/gui/guiButtonImage.h b/src/gui/guiButtonImage.h index c07be178b..dd84b2569 100644 --- a/src/gui/guiButtonImage.h +++ b/src/gui/guiButtonImage.h @@ -19,6 +19,9 @@ public: s32 id, core::rect rectangle, ISimpleTextureSource *tsrc, bool noclip = false); + //! draws the element and its children + virtual void draw() override; + void setForegroundImage(irr_ptr image = nullptr, const core::rect &middle = core::rect()); From 810bbc54727ec07b29656956b70e2f4c1a519dda Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 20 May 2025 21:49:41 +0200 Subject: [PATCH 2/6] Formspec: Introduce default element styling/theming This introduces a new setting to customize the default appearance of formspecs. Server-sent 'formspec prepends' will internally overwrite this setting. --- doc/lua_api.md | 4 +- doc/texture_packs.md | 4 + games/devtest/mods/testformspec/formspec.lua | 2 +- src/gui/StyleSpec.h | 12 +++ src/gui/guiButton.cpp | 11 ++- src/gui/guiButtonImage.cpp | 9 +- src/gui/guiEngine.cpp | 39 +++++++-- src/gui/guiEngine.h | 8 +- src/gui/guiFormSpecMenu.cpp | 86 ++++++++++++++------ src/gui/guiFormSpecMenu.h | 10 ++- 10 files changed, 134 insertions(+), 51 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 3a6da1bdb..8b839514b 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -3632,9 +3632,9 @@ Some types may inherit styles from parent types. * bgimg - standard background image. Defaults to none. * bgimg_hovered - background image when hovered. Defaults to bgimg when not provided. * This is deprecated, use states instead. - * bgimg_middle - Makes the bgimg textures render in 9-sliced mode and defines the middle rect. + * bgimg_middle - Replaces the default border. Defines the middle rect for 9-sliced mode. See background9[] documentation for more details. This property also pads the - button's content when set. + button's content when set. When `border` is set to false * bgimg_pressed - background image when pressed. Defaults to bgimg when not provided. * This is deprecated, use states instead. * font - Sets font type. This is a comma separated list of options. Valid options: diff --git a/doc/texture_packs.md b/doc/texture_packs.md index bde5eecfd..c5abea79b 100644 --- a/doc/texture_packs.md +++ b/doc/texture_packs.md @@ -38,6 +38,10 @@ A key-value config file with the following keys: * `textdomain`: Textdomain used to translate title and description. Defaults to the texture pack name. See [Translating content meta](lua_api.md#translating-content-meta). +* `formspec_theme`: optional. Default formspec styling. + This is a newline separated list of `style_type[...]` values. +* `formspec_version_theme`: optional. Indicates the minimal [formspec version](lua_api.md) + needed to properly display `formspec_theme`. On older clients, this suppresses warnings. ### `description.txt` **Deprecated**, you should use texture_pack.conf instead. diff --git a/games/devtest/mods/testformspec/formspec.lua b/games/devtest/mods/testformspec/formspec.lua index f2f632fa0..94c2ceba6 100644 --- a/games/devtest/mods/testformspec/formspec.lua +++ b/games/devtest/mods/testformspec/formspec.lua @@ -235,7 +235,7 @@ local style_fs = [[ style[one_btn15;border=false;bgcolor=#1cc;bgimg=testformspec_bg.png;bgimg_hovered=testformspec_bg_hovered.png;bgimg_pressed=testformspec_bg_pressed.png] item_image_button[1.25,9.6;1,1;testformspec:item;one_btn15;Bg] - style[one_btn16;border=false;bgimg=testformspec_bg_9slice.png;bgimg_middle=4,6;padding=5,7;fgimg=testformspec_bg.png;fgimg_middle=1] + style[one_btn16;bgimg=testformspec_bg_9slice.png;bgimg_middle=4,6;padding=5,7;fgimg=testformspec_bg.png;fgimg_middle=1] style[one_btn16:hovered;bgimg=testformspec_bg_9slice_hovered.png;fgimg=testformspec_bg_hovered.png] style[one_btn16:pressed;bgimg=testformspec_bg_9slice_pressed.png;fgimg=testformspec_bg_pressed.png] style[one_btn16:focused;bgimg=testformspec_bg_9slice_focused.png;fgimg=testformspec_bg_focused.png] diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index f78a038c2..a952e855a 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -380,12 +380,24 @@ public: StyleSpec &operator|=(const StyleSpec &other) { + u32 props_set = 0; + static_assert(sizeof(props_set) * 8 > NUM_PROPERTIES); + for (size_t i = 0; i < NUM_PROPERTIES; i++) { auto prop = (Property)i; if (other.hasProperty(prop)) { + props_set |= (1 << i); set(prop, other.get(prop, "")); } } + if ((props_set & (1 << FGIMG | 1 << FGIMG_MIDDLE)) == (1 << FGIMG)) { + // Image was specified without 9-slice. Reset to non-9-slice. + set(FGIMG_MIDDLE, ""); + } + if ((props_set & (1 << BGIMG | 1 << BGIMG_MIDDLE)) == (1 << BGIMG)) { + // Image was specified without 9-slice. Reset to non-9-slice. + set(BGIMG_MIDDLE, ""); + } return *this; } diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index 9975549fe..a8cd2cafa 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -255,9 +255,10 @@ void GUIButton::draw() video::IVideoDriver* driver = Environment->getVideoDriver(); IGUISkin *skin = Environment->getSkin(); + const bool is_9_slice_border = BgMiddle.getArea() > 0; // END PATCH - if (DrawBorder) + if (DrawBorder && !is_9_slice_border) { if (!Pressed) { @@ -303,13 +304,17 @@ void GUIButton::draw() // PATCH video::ITexture* texture = ButtonImages[(u32)imageState].Texture; + // FIXME: Vertices can only be darkened because [0, 255] is normalized to [0, 1] + // For reference: irr/src/OpenGL/Driver.cpp -> `vt2DImage` video::SColor image_colors[] = { BgColor, BgColor, BgColor, BgColor }; - if (BgMiddle.getArea() == 0) { + if (!is_9_slice_border) { + // Regular image button driver->draw2DImage(texture, ScaleImage? AbsoluteRect : core::rect(pos, sourceRect.getSize()), sourceRect, &AbsoluteClippingRect, image_colors, UseAlphaChannel); - } else { + } else if (DrawBorder) { + // The background image is 9-slice --> use as new border style draw2DImage9Slice(driver, texture, ScaleImage ? AbsoluteRect : core::rect(pos, sourceRect.getSize()), sourceRect, BgMiddle, &AbsoluteClippingRect, image_colors); diff --git a/src/gui/guiButtonImage.cpp b/src/gui/guiButtonImage.cpp index f085d4383..61849be22 100644 --- a/src/gui/guiButtonImage.cpp +++ b/src/gui/guiButtonImage.cpp @@ -26,16 +26,9 @@ GUIButtonImage::GUIButtonImage(gui::IGUIEnvironment *environment, void GUIButtonImage::draw() { - if (isDrawingBorder()) { - // `GUIButton` also allows drawing different textures depending on - // `EGUI_BUTTON_STATE` --> Skip everything if the border is disabled (else case). - GUIButton::draw(); - } else { - IGUIElement::draw(); - } + GUIButton::draw(); } - void GUIButtonImage::setForegroundImage(irr_ptr image, const core::rect &middle) { diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 0ec0d7a76..465274c67 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -8,6 +8,7 @@ #include "client/guiscalingfilter.h" #include "client/renderingengine.h" #include "client/shader.h" +#include "client/texturepaths.h" #include "client/tile.h" #include "clientdynamicinfo.h" #include "config.h" @@ -41,14 +42,18 @@ void TextDestGuiEngine::gotText(const StringMap &fields) } /******************************************************************************/ +MenuTextureSource::MenuTextureSource(video::IVideoDriver* driver) : + m_driver(driver) +{ + g_settings->registerChangedCallback("texture_path", onTxpSettingChanged, this); +} + MenuTextureSource::~MenuTextureSource() { - u32 before = m_driver->getTextureCount(); + g_settings->deregisterAllChangedCallbacks(this); - for (const auto &it: m_to_delete) { - m_driver->removeTexture(it); - } - m_to_delete.clear(); + u32 before = m_driver->getTextureCount(); + cleanupTextures(); infostream << "~MenuTextureSource() before cleanup: "<< before << " after: " << m_driver->getTextureCount() << std::endl; @@ -68,8 +73,14 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id) if (retval) return retval; - verbosestream << "MenuTextureSource: loading " << name << std::endl; - video::IImage *image = m_driver->createImageFromFile(name.c_str()); + // Try to find the texture in the active texture pack + std::string path; + if (!fs::IsPathAbsolute(name)) + path = getTexturePath(name, nullptr); + + const char *filepath = path.empty() ? name.c_str() : path.c_str(); + verbosestream << "MenuTextureSource: loading " << filepath << std::endl; + video::IImage *image = m_driver->createImageFromFile(filepath); if (!image) return NULL; @@ -81,6 +92,20 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id) return retval; } +void MenuTextureSource::cleanupTextures() +{ + for (const auto &it: m_to_delete) { + m_driver->removeTexture(it); + } + m_to_delete.clear(); +} + +void MenuTextureSource::onTxpSettingChanged(const std::string &name, void *data) +{ + ((MenuTextureSource *)data)->cleanupTextures(); + clearTextureNameCache(); +} + /******************************************************************************/ /** MenuMusicFetcher */ /******************************************************************************/ diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index f4b22f3c2..9fd5e869c 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -74,7 +74,7 @@ public: * default constructor * @param driver the video driver to load textures from */ - MenuTextureSource(video::IVideoDriver *driver) : m_driver(driver) {}; + MenuTextureSource(video::IVideoDriver *driver); /** * destructor, removes all loaded textures @@ -89,6 +89,12 @@ public: video::ITexture *getTexture(const std::string &name, u32 *id = NULL); private: + /** Unloads all textures in `m_to_delete` */ + void cleanupTextures(); + + /** Update the texture cache */ + static void onTxpSettingChanged(const std::string &name, void *data); + /** driver to get textures from */ video::IVideoDriver *m_driver = nullptr; /** set of textures to delete */ diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 97d86b05f..50187a2a7 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -114,10 +114,15 @@ GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick, m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay"); m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname"); + + g_settings->registerChangedCallback("texture_path", onTxpSettingChanged, this); + setThemeFromSettings(); } GUIFormSpecMenu::~GUIFormSpecMenu() { + g_settings->deregisterAllChangedCallbacks(this); + removeAll(); delete m_selected_item; @@ -2618,15 +2623,9 @@ void GUIFormSpecMenu::parsePadding(parserData *data, const std::string &element) << "'" << std::endl; } -void GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element) +void GUIFormSpecMenu::parse_style_to_map(StyleSpecMap &out, const std::string &element, + std::unordered_set *prop_warned) { - if (data->type != "style" && data->type != "style_type") { - errorstream << "Invalid style element type: '" << data->type << "'" << std::endl; - return; - } - - bool style_type = (data->type == "style_type"); - std::vector parts = split(element, ';'); if (parts.size() < 2) { @@ -2653,11 +2652,11 @@ void GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element) StyleSpec::Property prop = StyleSpec::GetPropertyByName(propname); if (prop == StyleSpec::NONE) { - if (property_warned.find(propname) != property_warned.end()) { + if (prop_warned && prop_warned->find(propname) != prop_warned->end()) { warningstream << "Invalid style element (Unknown property " << propname << "): '" << element << "'" << std::endl; - property_warned.insert(propname); + prop_warned->insert(propname); } continue; } @@ -2705,11 +2704,7 @@ void GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element) continue; } - if (style_type) { - theme_by_type[selector].push_back(selector_spec); - } else { - theme_by_name[selector].push_back(selector_spec); - } + out[selector].push_back(selector_spec); // Backwards-compatibility for existing _hovered/_pressed properties if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED) @@ -2728,11 +2723,7 @@ void GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element) hover_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_HOVERED, "")); } - if (style_type) { - theme_by_type[selector].push_back(hover_spec); - } else { - theme_by_name[selector].push_back(hover_spec); - } + out[selector].push_back(hover_spec); } if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED) || selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED) @@ -2750,15 +2741,22 @@ void GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element) press_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_PRESSED, "")); } - if (style_type) { - theme_by_type[selector].push_back(press_spec); - } else { - theme_by_name[selector].push_back(press_spec); - } + out[selector].push_back(press_spec); } } +} - return; +void GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element) +{ + if (data->type != "style" && data->type != "style_type") { + errorstream << "Invalid style element type: '" << data->type << "'" << std::endl; + return; + } + + bool style_type = (data->type == "style_type"); + + parse_style_to_map(style_type ? theme_by_type : theme_by_name, + element, &property_warned); } void GUIFormSpecMenu::parseSetFocus(parserData*, const std::string &element) @@ -3041,7 +3039,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_dropdowns.clear(); m_scroll_containers.clear(); theme_by_name.clear(); - theme_by_type.clear(); + theme_by_type = theme_by_type_default; m_clickthrough_elements.clear(); field_enter_after_edit.clear(); field_close_on_enter.clear(); @@ -5028,6 +5026,40 @@ std::wstring GUIFormSpecMenu::getLabelByID(s32 id) return L""; } + +void GUIFormSpecMenu::setThemeFromSettings() +{ + theme_by_type_default.clear(); + + const std::string settingspath = g_settings->get("texture_path") + DIR_DELIM + "texture_pack.conf"; + Settings settings; + if (!settings.readConfigFile(settingspath.c_str())) + return; + if (!settings.exists("formspec_theme")) + return; + + std::unordered_set *prop_warned = nullptr; + { + u16 fs_ver = FORMSPEC_API_VERSION; + settings.getU16NoEx("formspec_version_theme", fs_ver); + if (fs_ver <= FORMSPEC_API_VERSION) + prop_warned = &property_warned; + // else: silence + } + + auto splits = split(settings.get("formspec_theme"), '\n'); + for (const std::string &s : splits) { + parse_style_to_map(theme_by_type_default, s, prop_warned); + } +} + +void GUIFormSpecMenu::onTxpSettingChanged(const std::string &name, void *data) +{ + GUIFormSpecMenu *me = (GUIFormSpecMenu *)data; + me->setThemeFromSettings(); + me->regenerateGui(me->m_screensize_old); +} + StyleSpec GUIFormSpecMenu::getDefaultStyleForElement(const std::string &type, const std::string &name, const std::string &parent_type) { return getStyleForElement(type, name, parent_type)[StyleSpec::STATE_DEFAULT]; diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index f279bbb29..72b7a2ad5 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -302,10 +302,14 @@ protected: bool precheckElement(const std::string &name, const std::string &element, size_t args_min, size_t args_max, std::vector &parts); - std::unordered_map> theme_by_type; - std::unordered_map> theme_by_name; + using StyleSpecMap = std::unordered_map>; + StyleSpecMap theme_by_type, theme_by_name, + theme_by_type_default; std::unordered_set property_warned; + void setThemeFromSettings(); + static void onTxpSettingChanged(const std::string &name, void *data); + StyleSpec getDefaultStyleForElement(const std::string &type, const std::string &name="", const std::string &parent_type=""); std::array getStyleForElement(const std::string &type, @@ -483,6 +487,8 @@ private: void parseAnchor(parserData *data, const std::string &element); bool parsePaddingDirect(parserData *data, const std::string &element); void parsePadding(parserData *data, const std::string &element); + static void parse_style_to_map(StyleSpecMap &out, const std::string &element, + std::unordered_set *prop_warned); void parseStyle(parserData *data, const std::string &element); void parseSetFocus(parserData *, const std::string &element); void parseModel(parserData *data, const std::string &element); From cd62a39dcca194dc31aa859f156c9dc5152ddbd9 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 25 May 2025 14:29:03 +0200 Subject: [PATCH 3/6] style_type and bgcolor --- doc/texture_packs.md | 4 +-- src/gui/guiFormSpecMenu.cpp | 56 ++++++++++++++++++++++--------------- src/gui/guiFormSpecMenu.h | 12 +++++--- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/doc/texture_packs.md b/doc/texture_packs.md index c5abea79b..7036f3508 100644 --- a/doc/texture_packs.md +++ b/doc/texture_packs.md @@ -38,8 +38,8 @@ A key-value config file with the following keys: * `textdomain`: Textdomain used to translate title and description. Defaults to the texture pack name. See [Translating content meta](lua_api.md#translating-content-meta). -* `formspec_theme`: optional. Default formspec styling. - This is a newline separated list of `style_type[...]` values. +* `formspec_theme`: optional. Formspec elements that define the defaut style. + * Allowed elements: `bgcolor`, `style_type` * `formspec_version_theme`: optional. Indicates the minimal [formspec version](lua_api.md) needed to properly display `formspec_theme`. On older clients, this suppresses warnings. diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 50187a2a7..34ddcea0a 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -2754,9 +2754,13 @@ void GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element) } bool style_type = (data->type == "style_type"); + bool do_warn = m_formspec_version <= FORMSPEC_API_VERSION; - parse_style_to_map(style_type ? theme_by_type : theme_by_name, - element, &property_warned); + parse_style_to_map( + style_type ? theme_by_type : theme_by_name, + element, + do_warn ? &property_warned : nullptr + ); } void GUIFormSpecMenu::parseSetFocus(parserData*, const std::string &element) @@ -2895,8 +2899,8 @@ void GUIFormSpecMenu::removeAll() scroll_container_it.second->drop(); } -const std::unordered_map> GUIFormSpecMenu::element_parsers = { +const std::unordered_map + GUIFormSpecMenu::element_parsers = { {"container", &GUIFormSpecMenu::parseContainer}, {"container_end", &GUIFormSpecMenu::parseContainerEnd}, {"list", &GUIFormSpecMenu::parseList}, @@ -2945,14 +2949,20 @@ const std::unordered_map + GUIFormSpecMenu::element_parsers_theme = { + {"bgcolor", &GUIFormSpecMenu::parseBackgroundColor}, + {"style_type", &GUIFormSpecMenu::parseStyle}, +}; -void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseElement(parserData *data, const std::string &element, bool is_theme) { //some prechecks if (element.empty()) return; - if (parseVersionDirect(element)) + if (!is_theme && parseVersionDirect(element)) return; size_t pos = element.find('['); @@ -2965,8 +2975,9 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) // They remain here due to bool flags, for now data->type = type; - auto it = element_parsers.find(type); - if (it != element_parsers.end()) { + auto &parser_lut = is_theme ? element_parsers_theme : element_parsers; + auto it = parser_lut.find(type); + if (it != parser_lut.end()) { it->second(this, data, description); return; } @@ -3039,7 +3050,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_dropdowns.clear(); m_scroll_containers.clear(); theme_by_name.clear(); - theme_by_type = theme_by_type_default; + theme_by_type.clear(); m_clickthrough_elements.clear(); field_enter_after_edit.clear(); field_close_on_enter.clear(); @@ -3237,6 +3248,16 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) pos_offset = v2f32(); + if (!m_theme_elements.empty()) { + // Formspec theming + + const u16 version_backup = m_formspec_version; + m_formspec_version = m_theme_formspec_version; + for (const std::string &element : m_theme_elements) + parseElement(&mydata, element, true); + m_formspec_version = version_backup; + } + // used for formspec versions < 3 auto legacy_sort_start = std::prev(Children.end()); // last element @@ -5029,7 +5050,7 @@ std::wstring GUIFormSpecMenu::getLabelByID(s32 id) void GUIFormSpecMenu::setThemeFromSettings() { - theme_by_type_default.clear(); + m_theme_elements.clear(); const std::string settingspath = g_settings->get("texture_path") + DIR_DELIM + "texture_pack.conf"; Settings settings; @@ -5038,19 +5059,8 @@ void GUIFormSpecMenu::setThemeFromSettings() if (!settings.exists("formspec_theme")) return; - std::unordered_set *prop_warned = nullptr; - { - u16 fs_ver = FORMSPEC_API_VERSION; - settings.getU16NoEx("formspec_version_theme", fs_ver); - if (fs_ver <= FORMSPEC_API_VERSION) - prop_warned = &property_warned; - // else: silence - } - - auto splits = split(settings.get("formspec_theme"), '\n'); - for (const std::string &s : splits) { - parse_style_to_map(theme_by_type_default, s, prop_warned); - } + settings.getU16NoEx("formspec_version_theme", m_theme_formspec_version); + m_theme_elements = split(settings.get("formspec_theme"), ']'); } void GUIFormSpecMenu::onTxpSettingChanged(const std::string &name, void *data) diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 72b7a2ad5..8380bb5a4 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -303,10 +303,12 @@ protected: size_t args_min, size_t args_max, std::vector &parts); using StyleSpecMap = std::unordered_map>; - StyleSpecMap theme_by_type, theme_by_name, - theme_by_type_default; + StyleSpecMap theme_by_type, theme_by_name; std::unordered_set property_warned; + // Texturepack-definied formspec theming support + std::vector m_theme_elements; + u16 m_theme_formspec_version; void setThemeFromSettings(); static void onTxpSettingChanged(const std::string &name, void *data); @@ -423,7 +425,9 @@ private: std::string type; }; - static const std::unordered_map> element_parsers; + using parser_function_t = std::function; + static const std::unordered_map + element_parsers, element_parsers_theme; struct fs_key_pending { bool key_up; @@ -437,7 +441,7 @@ private: void removeAll(); - void parseElement(parserData* data, const std::string &element); + void parseElement(parserData* data, const std::string &element, bool is_theme = false); void parseSize(parserData* data, const std::string &element); void parseContainer(parserData* data, const std::string &element); From 484ece088fd520f962a96e1d067773e9d7d6e6be Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 31 May 2025 14:51:17 +0200 Subject: [PATCH 4/6] wip --- doc/lua_api.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 8b839514b..d937788a9 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -3632,9 +3632,9 @@ Some types may inherit styles from parent types. * bgimg - standard background image. Defaults to none. * bgimg_hovered - background image when hovered. Defaults to bgimg when not provided. * This is deprecated, use states instead. - * bgimg_middle - Replaces the default border. Defines the middle rect for 9-sliced mode. + * bgimg_middle - Makes the bgimg textures render in 9-sliced mode and defines the middle rect. See background9[] documentation for more details. This property also pads the - button's content when set. When `border` is set to false + button's content when set. * bgimg_pressed - background image when pressed. Defaults to bgimg when not provided. * This is deprecated, use states instead. * font - Sets font type. This is a comma separated list of options. Valid options: @@ -3650,6 +3650,8 @@ Some types may inherit styles from parent types. * `+`/`-`: Offsets default font size by `number` points. * `*`: Multiplies default font size by `number`, similar to CSS `em`. * border - boolean, draw border. Set to false to hide the bevelled button pane. Default true. + * border_img - string, a texture that overrides the border style. + * border_img_middle - rect, to render `border_img` in 9-sliced mode. Refer to `background9[...]`. * content_offset - 2d vector, shifts the position of the button's content without resizing it. * noclip - boolean, set to true to allow the element to exceed formspec bounds. * padding - rect, adds space between the edges of the button and the content. This value is From bc981a2e29bf0fcdd8ea77bcb50dd85d6282eb94 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Thu, 5 Jun 2025 18:53:21 +0200 Subject: [PATCH 5/6] revertt --- games/devtest/mods/testformspec/formspec.lua | 2 +- src/gui/StyleSpec.h | 3 +++ src/gui/guiButton.cpp | 9 ++++----- src/gui/guiButtonImage.cpp | 5 ----- src/gui/guiButtonImage.h | 3 --- src/gui/guiFormSpecMenu.h | 1 - 6 files changed, 8 insertions(+), 15 deletions(-) diff --git a/games/devtest/mods/testformspec/formspec.lua b/games/devtest/mods/testformspec/formspec.lua index 94c2ceba6..f2f632fa0 100644 --- a/games/devtest/mods/testformspec/formspec.lua +++ b/games/devtest/mods/testformspec/formspec.lua @@ -235,7 +235,7 @@ local style_fs = [[ style[one_btn15;border=false;bgcolor=#1cc;bgimg=testformspec_bg.png;bgimg_hovered=testformspec_bg_hovered.png;bgimg_pressed=testformspec_bg_pressed.png] item_image_button[1.25,9.6;1,1;testformspec:item;one_btn15;Bg] - style[one_btn16;bgimg=testformspec_bg_9slice.png;bgimg_middle=4,6;padding=5,7;fgimg=testformspec_bg.png;fgimg_middle=1] + style[one_btn16;border=false;bgimg=testformspec_bg_9slice.png;bgimg_middle=4,6;padding=5,7;fgimg=testformspec_bg.png;fgimg_middle=1] style[one_btn16:hovered;bgimg=testformspec_bg_9slice_hovered.png;fgimg=testformspec_bg_hovered.png] style[one_btn16:pressed;bgimg=testformspec_bg_9slice_pressed.png;fgimg=testformspec_bg_pressed.png] style[one_btn16:focused;bgimg=testformspec_bg_9slice_focused.png;fgimg=testformspec_bg_focused.png] diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index a952e855a..1003c5865 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -13,6 +13,9 @@ #include #include +class StyleSpec; + +using StyleSpecMap = std::unordered_map>; class StyleSpec { diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index a8cd2cafa..f4ded8b18 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -255,10 +255,9 @@ void GUIButton::draw() video::IVideoDriver* driver = Environment->getVideoDriver(); IGUISkin *skin = Environment->getSkin(); - const bool is_9_slice_border = BgMiddle.getArea() > 0; // END PATCH - if (DrawBorder && !is_9_slice_border) + if (DrawBorder) { if (!Pressed) { @@ -307,14 +306,14 @@ void GUIButton::draw() // FIXME: Vertices can only be darkened because [0, 255] is normalized to [0, 1] // For reference: irr/src/OpenGL/Driver.cpp -> `vt2DImage` video::SColor image_colors[] = { BgColor, BgColor, BgColor, BgColor }; - if (!is_9_slice_border) { + if (BgMiddle.getArea() == 0) { // Regular image button driver->draw2DImage(texture, ScaleImage? AbsoluteRect : core::rect(pos, sourceRect.getSize()), sourceRect, &AbsoluteClippingRect, image_colors, UseAlphaChannel); - } else if (DrawBorder) { - // The background image is 9-slice --> use as new border style + } else { + // This is generally used to replace the default border style draw2DImage9Slice(driver, texture, ScaleImage ? AbsoluteRect : core::rect(pos, sourceRect.getSize()), sourceRect, BgMiddle, &AbsoluteClippingRect, image_colors); diff --git a/src/gui/guiButtonImage.cpp b/src/gui/guiButtonImage.cpp index 61849be22..fd3481d85 100644 --- a/src/gui/guiButtonImage.cpp +++ b/src/gui/guiButtonImage.cpp @@ -24,11 +24,6 @@ GUIButtonImage::GUIButtonImage(gui::IGUIEnvironment *environment, sendToBack(m_image.get()); } -void GUIButtonImage::draw() -{ - GUIButton::draw(); -} - void GUIButtonImage::setForegroundImage(irr_ptr image, const core::rect &middle) { diff --git a/src/gui/guiButtonImage.h b/src/gui/guiButtonImage.h index dd84b2569..c07be178b 100644 --- a/src/gui/guiButtonImage.h +++ b/src/gui/guiButtonImage.h @@ -19,9 +19,6 @@ public: s32 id, core::rect rectangle, ISimpleTextureSource *tsrc, bool noclip = false); - //! draws the element and its children - virtual void draw() override; - void setForegroundImage(irr_ptr image = nullptr, const core::rect &middle = core::rect()); diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 8380bb5a4..b732fe0f4 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -302,7 +302,6 @@ protected: bool precheckElement(const std::string &name, const std::string &element, size_t args_min, size_t args_max, std::vector &parts); - using StyleSpecMap = std::unordered_map>; StyleSpecMap theme_by_type, theme_by_name; std::unordered_set property_warned; From bd0d9a7f9532b1d3cfe6de2d507bc813d6c54625 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Thu, 5 Jun 2025 20:35:27 +0200 Subject: [PATCH 6/6] Introduce skin styles (currently discards colors) --- irr/src/CGUIEnvironment.cpp | 4 +- irr/src/CGUISkin.h | 2 +- src/client/clientlauncher.cpp | 6 +++ src/gui/CMakeLists.txt | 1 + src/gui/StyleSpec.h | 2 + src/gui/guiFormSpecMenu.cpp | 33 +++++++++------ src/gui/guiFormSpecMenu.h | 6 +-- src/gui/guiSkin.cpp | 76 +++++++++++++++++++++++++++++++++++ src/gui/guiSkin.h | 37 +++++++++++++++++ 9 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 src/gui/guiSkin.cpp create mode 100644 src/gui/guiSkin.h diff --git a/irr/src/CGUIEnvironment.cpp b/irr/src/CGUIEnvironment.cpp index abfa0aab9..1a4560eab 100644 --- a/irr/src/CGUIEnvironment.cpp +++ b/irr/src/CGUIEnvironment.cpp @@ -28,6 +28,8 @@ #endif #include "os.h" +irr::gui::IGUISkin *impl_create_irr_guiskin(irr::video::IVideoDriver *driver); + namespace irr { namespace gui @@ -590,7 +592,7 @@ If you no longer need the skin, you should call IGUISkin::drop(). See IReferenceCounted::drop() for more information. */ IGUISkin *CGUIEnvironment::createSkin() { - IGUISkin *skin = new CGUISkin(Driver); + IGUISkin *skin = impl_create_irr_guiskin(Driver); IGUIFont *builtinfont = getBuiltInFont(); IGUIFontBitmap *bitfont = 0; diff --git a/irr/src/CGUISkin.h b/irr/src/CGUISkin.h index 704a09745..e3bb6f380 100644 --- a/irr/src/CGUISkin.h +++ b/irr/src/CGUISkin.h @@ -291,7 +291,7 @@ namespace gui //! gets the colors virtual void getColors(video::SColor* colors); // ::PATCH: - private: + protected: float Scale = 1.0f; video::SColor Colors[EGDC_COUNT]; diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 64f2e8f51..8a0484824 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -14,6 +14,7 @@ #include "inputhandler.h" #include "profiler.h" #include "gui/guiEngine.h" +#include "gui/guiSkin.h" #include "fontengine.h" #include "clientlauncher.h" #include "version.h" @@ -338,6 +339,11 @@ static video::ITexture *loadTexture(video::IVideoDriver *driver, const char *pat return texture; } +gui::IGUISkin *impl_create_irr_guiskin(video::IVideoDriver *driver) +{ + return new GUISkin(driver); +} + void ClientLauncher::config_guienv() { gui::IGUISkin *skin = guienv->getSkin(); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 0c1db2ae0..9b9216be1 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -19,6 +19,7 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiScene.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollContainer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiHyperText.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index 1003c5865..a7d389cea 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -62,6 +62,8 @@ public: STATE_INVALID = 1 << 4, }; + using StateMap = std::array; + private: std::array property_set{}; std::array properties; diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 34ddcea0a..b86df13e2 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -56,6 +56,7 @@ #include "guiScrollContainer.h" #include "guiHyperText.h" #include "guiScene.h" +#include "guiSkin.h" #define MY_CHECKPOS(a,b) \ if (v_pos.size() != 2) { \ @@ -2756,8 +2757,13 @@ void GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element) bool style_type = (data->type == "style_type"); bool do_warn = m_formspec_version <= FORMSPEC_API_VERSION; + StyleSpecMap *map = style_type ? &theme_by_type : &theme_by_name; + if (data->reading_theme) { + GUISkin *skin = (GUISkin *)Environment->getSkin(); + map = &skin->getThemeRef(); + } parse_style_to_map( - style_type ? theme_by_type : theme_by_name, + *map, element, do_warn ? &property_warned : nullptr ); @@ -3248,16 +3254,6 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) pos_offset = v2f32(); - if (!m_theme_elements.empty()) { - // Formspec theming - - const u16 version_backup = m_formspec_version; - m_formspec_version = m_theme_formspec_version; - for (const std::string &element : m_theme_elements) - parseElement(&mydata, element, true); - m_formspec_version = version_backup; - } - // used for formspec versions < 3 auto legacy_sort_start = std::prev(Children.end()); // last element @@ -5050,7 +5046,9 @@ std::wstring GUIFormSpecMenu::getLabelByID(s32 id) void GUIFormSpecMenu::setThemeFromSettings() { - m_theme_elements.clear(); + GUISkin *skin = (GUISkin *)Environment->getSkin(); + skin->getThemeRef().clear(); + skin->setTextureSource(m_tsrc); const std::string settingspath = g_settings->get("texture_path") + DIR_DELIM + "texture_pack.conf"; Settings settings; @@ -5060,7 +5058,16 @@ void GUIFormSpecMenu::setThemeFromSettings() return; settings.getU16NoEx("formspec_version_theme", m_theme_formspec_version); - m_theme_elements = split(settings.get("formspec_theme"), ']'); + auto theme_elements = split(settings.get("formspec_theme"), ']'); + + parserData mydata; + mydata.reading_theme = true; + + const u16 version_backup = m_formspec_version; + m_formspec_version = m_theme_formspec_version; + for (const std::string &element : theme_elements) + parseElement(&mydata, element, true); + m_formspec_version = version_backup; } void GUIFormSpecMenu::onTxpSettingChanged(const std::string &name, void *data) diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index b732fe0f4..d7a18cf4b 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -306,7 +306,6 @@ protected: std::unordered_set property_warned; // Texturepack-definied formspec theming support - std::vector m_theme_elements; u16 m_theme_formspec_version; void setThemeFromSettings(); static void onTxpSettingChanged(const std::string &name, void *data); @@ -392,8 +391,9 @@ private: bool m_show_debug = false; struct parserData { - bool explicit_size; - bool real_coordinates; + bool explicit_size = false; + bool real_coordinates = false; + bool reading_theme = false; u8 simple_field_count; v2f invsize; v2s32 size; diff --git a/src/gui/guiSkin.cpp b/src/gui/guiSkin.cpp new file mode 100644 index 000000000..b4aef156b --- /dev/null +++ b/src/gui/guiSkin.cpp @@ -0,0 +1,76 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 Krock/SmallJoker + +#include "guiSkin.h" +#include "client/guiscalingfilter.h" + + +GUISkin::GUISkin(video::IVideoDriver *driver) : gui::CGUISkin(driver) +{ +} + + +GUISkin::~GUISkin() +{ +} + +void GUISkin::drawColored3DButtonPanePressed(gui::IGUIElement *element, + const core::rect &rect, + const core::rect *clip, + const video::SColor *colors) +{ + if (!Driver) + return; + + if (tryDrawPane("_skin_button", StyleSpec::STATE_PRESSED, rect, clip)) + return; + + gui::CGUISkin::drawColored3DButtonPanePressed(element, rect, clip, colors); +} + +void GUISkin::drawColored3DButtonPaneStandard(gui::IGUIElement *element, + const core::rect &rect, + const core::rect *clip, + const video::SColor *colors) +{ + if (!Driver) + return; + + if (tryDrawPane("_skin_button", StyleSpec::STATE_DEFAULT, rect, clip)) + return; + + gui::CGUISkin::drawColored3DButtonPaneStandard(element, rect, clip, colors); +} + +bool GUISkin::tryDrawPane(const char *type, StyleSpec::State state, + const core::rect &rect, + const core::rect *clip) +{ + auto it = m_theme.find(type); + if (it == m_theme.end()) + return false; + + video::SColor c = 0xFFFFFFFF; + video::SColor image_colors[] = { c, c, c, c }; + + // Similar to GUIFormSpecMenu::getStyleForElement + StyleSpec::StateMap states; + for (const StyleSpec &spec : it->second) + states[(u32)spec.getState()] |= spec; + + StyleSpec style = StyleSpec::getStyleFromStatePropagation(states, state); + video::ITexture *texture = style.getTexture(StyleSpec::BGIMG, m_texture_source); + core::recti source_rect = core::rect(core::position2di(0,0), texture->getOriginalSize()); + + core::recti bg_middle = style.getRect(StyleSpec::BGIMG_MIDDLE, core::recti()); + if (bg_middle.getArea() == 0) { + Driver->draw2DImage(texture, rect, source_rect, clip, image_colors, true); + } else { + draw2DImage9Slice(Driver, texture, rect, source_rect, bg_middle, clip, image_colors); + } + + return true; +} + + diff --git a/src/gui/guiSkin.h b/src/gui/guiSkin.h new file mode 100644 index 000000000..4c46a79d5 --- /dev/null +++ b/src/gui/guiSkin.h @@ -0,0 +1,37 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 Krock/SmallJoker + +#pragma once + +#include "StyleSpec.h" // StyleSpecMap +#include "../../irr/src/CGUISkin.h" + +class GUISkin : public gui::CGUISkin { +public: + GUISkin(video::IVideoDriver *driver); + virtual ~GUISkin(); + + void setTextureSource(ISimpleTextureSource *src) { m_texture_source = src; } + + virtual void drawColored3DButtonPaneStandard(gui::IGUIElement *element, + const core::rect &rect, + const core::rect *clip = 0, + const video::SColor *colors = 0) override; + + virtual void drawColored3DButtonPanePressed(gui::IGUIElement *element, + const core::rect &rect, + const core::rect *clip = 0, + const video::SColor *colors = 0) override; + + StyleSpecMap &getThemeRef() { return m_theme; } + +private: + bool tryDrawPane(const char *type, StyleSpec::State state, + const core::rect &rect, + const core::rect *clip = 0); + + ISimpleTextureSource *m_texture_source = nullptr; + StyleSpecMap m_theme; +}; +