1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00
This commit is contained in:
SmallJoker 2025-06-27 17:28:16 +08:00 committed by GitHub
commit 23aba644cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 286 additions and 48 deletions

View file

@ -3653,6 +3653,8 @@ Some types may inherit styles from parent types.
* `+<number>`/`-<number>`: Offsets default font size by `number` points.
* `*<number>`: 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

View file

@ -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. 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.
### `description.txt`
**Deprecated**, you should use texture_pack.conf instead.

View file

@ -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;

View file

@ -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];

View file

@ -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();

View file

@ -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

View file

@ -13,6 +13,9 @@
#include <array>
#include <vector>
class StyleSpec;
using StyleSpecMap = std::unordered_map<std::string, std::vector<StyleSpec>>;
class StyleSpec
{
@ -59,6 +62,8 @@ public:
STATE_INVALID = 1 << 4,
};
using StateMap = std::array<StyleSpec, NUM_STATES>;
private:
std::array<bool, NUM_PROPERTIES> property_set{};
std::array<std::string, NUM_PROPERTIES> properties;
@ -380,12 +385,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;
}

View file

@ -303,13 +303,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) {
// Regular image button
driver->draw2DImage(texture,
ScaleImage? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
sourceRect, &AbsoluteClippingRect,
image_colors, UseAlphaChannel);
} else {
// This is generally used to replace the default border style
draw2DImage9Slice(driver, texture,
ScaleImage ? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
sourceRect, BgMiddle, &AbsoluteClippingRect, image_colors);

View file

@ -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"
@ -43,14 +44,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;
@ -70,8 +75,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;
@ -83,6 +94,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 */
/******************************************************************************/

View file

@ -76,7 +76,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
@ -91,6 +91,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 */

View file

@ -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) { \
@ -114,10 +115,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 +2624,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<std::string> *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<std::string> parts = split(element, ';');
if (parts.size() < 2) {
@ -2653,11 +2653,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 +2705,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 +2724,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 +2742,31 @@ 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");
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(
*map,
element,
do_warn ? &property_warned : nullptr
);
}
void GUIFormSpecMenu::parseSetFocus(parserData*, const std::string &element)
@ -2898,8 +2906,8 @@ void GUIFormSpecMenu::removeAll()
scroll_container_it.second->drop();
}
const std::unordered_map<std::string, std::function<void(GUIFormSpecMenu*, GUIFormSpecMenu::parserData *data,
const std::string &description)>> GUIFormSpecMenu::element_parsers = {
const std::unordered_map<std::string, GUIFormSpecMenu::parser_function_t>
GUIFormSpecMenu::element_parsers = {
{"container", &GUIFormSpecMenu::parseContainer},
{"container_end", &GUIFormSpecMenu::parseContainerEnd},
{"list", &GUIFormSpecMenu::parseList},
@ -2948,14 +2956,20 @@ const std::unordered_map<std::string, std::function<void(GUIFormSpecMenu*, GUIFo
{"allow_close", &GUIFormSpecMenu::parseAllowClose},
};
// Formspec elements allowed for themes
const std::unordered_map<std::string, GUIFormSpecMenu::parser_function_t>
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('[');
@ -2968,8 +2982,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;
}
@ -5054,6 +5069,40 @@ std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
return L"";
}
void GUIFormSpecMenu::setThemeFromSettings()
{
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;
if (!settings.readConfigFile(settingspath.c_str()))
return;
if (!settings.exists("formspec_theme"))
return;
settings.getU16NoEx("formspec_version_theme", m_theme_formspec_version);
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)
{
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];

View file

@ -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<std::string> &parts);
std::unordered_map<std::string, std::vector<StyleSpec>> theme_by_type;
std::unordered_map<std::string, std::vector<StyleSpec>> theme_by_name;
StyleSpecMap theme_by_type, theme_by_name;
std::unordered_set<std::string> property_warned;
// Texturepack-definied formspec theming support
u16 m_theme_formspec_version;
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<StyleSpec, StyleSpec::NUM_STATES> getStyleForElement(const std::string &type,
@ -387,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;
@ -419,7 +424,9 @@ private:
std::string type;
};
static const std::unordered_map<std::string, std::function<void(GUIFormSpecMenu*, GUIFormSpecMenu::parserData *data, const std::string &description)>> element_parsers;
using parser_function_t = std::function<void(GUIFormSpecMenu*, parserData *, const std::string &)>;
static const std::unordered_map<std::string, parser_function_t>
element_parsers, element_parsers_theme;
struct fs_key_pending {
bool key_up;
@ -433,7 +440,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);
@ -483,6 +490,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<std::string> *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);

76
src/gui/guiSkin.cpp Normal file
View file

@ -0,0 +1,76 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2025 Krock/SmallJoker <mk939@ymail.com>
#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<s32> &rect,
const core::rect<s32> *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<s32> &rect,
const core::rect<s32> *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<s32> &rect,
const core::rect<s32> *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<s32>(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;
}

37
src/gui/guiSkin.h Normal file
View file

@ -0,0 +1,37 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2025 Krock/SmallJoker <mk939@ymail.com>
#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<s32> &rect,
const core::rect<s32> *clip = 0,
const video::SColor *colors = 0) override;
virtual void drawColored3DButtonPanePressed(gui::IGUIElement *element,
const core::rect<s32> &rect,
const core::rect<s32> *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<s32> &rect,
const core::rect<s32> *clip = 0);
ISimpleTextureSource *m_texture_source = nullptr;
StyleSpecMap m_theme;
};