diff options
author | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-03-17 10:39:38 +0100 |
---|---|---|
committer | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-03-24 13:47:11 +0100 |
commit | 5274cc92c1055a3209dfae7e5346bfe52c35e4a8 (patch) | |
tree | 8bb9ceab97fb752a07294a52dcf2d390e79616fe /src | |
parent | d5f49e4bef99e2ae4b39bdf1b7c644c28a85c5a2 (diff) | |
download | mana-5274cc92c1055a3209dfae7e5346bfe52c35e4a8.tar.gz mana-5274cc92c1055a3209dfae7e5346bfe52c35e4a8.tar.bz2 mana-5274cc92c1055a3209dfae7e5346bfe52c35e4a8.tar.xz mana-5274cc92c1055a3209dfae7e5346bfe52c35e4a8.zip |
Define the GUI theme in XML
Now all images used by the various UI widgets are defined in a
`theme.xml`, removing hardcoded requirements on the size of images,
borders and sub-images and their locations. The `colors.xml` file was
merged into this new file as well.
The `<img>` element defines either a plain image, or a 9-scale that is
automatically rendered at the size of the widget when any of the `left`,
`right`, `top` or `bottom` attributes are given.
The `x`, `y`, `width` and `height` attributes determine the
sub-rectangle within the image referenced by `src`. `x` and `y` default
to 0 and `width` and `height` default to the imge size.
The `<state>` element defines in which state its images are used by
setting its `selected`, `disabled`, `hovered` or `focused` attributes to
either `true` or `false`. Only the first matching state is rendered.
The `Text` and `SpeechBubble` classes now use the same skin to draw the
bubble, as well as using a newly introduced `BUBBLE_TEXT` color from the
theme palette.
Diffstat (limited to 'src')
-rw-r--r-- | src/being.cpp | 4 | ||||
-rw-r--r-- | src/graphics.cpp | 11 | ||||
-rw-r--r-- | src/graphics.h | 2 | ||||
-rw-r--r-- | src/gui/minimap.cpp | 3 | ||||
-rw-r--r-- | src/gui/speechbubble.cpp | 13 | ||||
-rw-r--r-- | src/gui/widgets/button.cpp | 20 | ||||
-rw-r--r-- | src/gui/widgets/checkbox.cpp | 11 | ||||
-rw-r--r-- | src/gui/widgets/dropdown.cpp | 32 | ||||
-rw-r--r-- | src/gui/widgets/popup.cpp | 25 | ||||
-rw-r--r-- | src/gui/widgets/popup.h | 23 | ||||
-rw-r--r-- | src/gui/widgets/radiobutton.cpp | 11 | ||||
-rw-r--r-- | src/gui/widgets/resizegrip.cpp | 13 | ||||
-rw-r--r-- | src/gui/widgets/scrollarea.cpp | 76 | ||||
-rw-r--r-- | src/gui/widgets/scrollarea.h | 8 | ||||
-rw-r--r-- | src/gui/widgets/slider.cpp | 16 | ||||
-rw-r--r-- | src/gui/widgets/tab.cpp | 16 | ||||
-rw-r--r-- | src/gui/widgets/textfield.cpp | 8 | ||||
-rw-r--r-- | src/gui/widgets/window.cpp | 81 | ||||
-rw-r--r-- | src/gui/widgets/window.h | 5 | ||||
-rw-r--r-- | src/resources/theme.cpp | 918 | ||||
-rw-r--r-- | src/resources/theme.h | 176 | ||||
-rw-r--r-- | src/text.cpp | 63 | ||||
-rw-r--r-- | src/text.h | 8 |
23 files changed, 649 insertions, 894 deletions
diff --git a/src/being.cpp b/src/being.cpp index 6cb68994..bbecf712 100644 --- a/src/being.cpp +++ b/src/being.cpp @@ -312,7 +312,7 @@ void Being::setSpeech(const std::string &text, int time) mText = new Text(mSpeech, getPixelX(), getSpeechTextYPosition(), gcn::Graphics::CENTER, - &userPalette->getColor(UserPalette::PARTICLE), + &Theme::getThemeColor(Theme::BUBBLE_TEXT), true); } } @@ -999,7 +999,7 @@ void Being::drawSpeech(int offsetX, int offsetY) mText = new Text(mSpeech, getPixelX(), getPixelY() - getHeight(), gcn::Graphics::CENTER, - &userPalette->getColor(UserPalette::PARTICLE), + &Theme::getThemeColor(Theme::BUBBLE_TEXT), true); } } diff --git a/src/graphics.cpp b/src/graphics.cpp index 2b262625..e9ccfc49 100644 --- a/src/graphics.cpp +++ b/src/graphics.cpp @@ -48,6 +48,17 @@ void ImageRect::setAlpha(float alpha) img->setAlpha(alpha); } +int ImageRect::minWidth() const +{ + return grid[ImageRect::UPPER_LEFT]->getWidth() + grid[ImageRect::UPPER_RIGHT]->getWidth(); +} + +int ImageRect::minHeight() const +{ + return grid[ImageRect::UPPER_LEFT]->getHeight() + grid[ImageRect::LOWER_LEFT]->getHeight(); +} + + void Graphics::updateSize(int width, int height, float /*scale*/) { mWidth = width; diff --git a/src/graphics.h b/src/graphics.h index 4556e499..085ad5d4 100644 --- a/src/graphics.h +++ b/src/graphics.h @@ -72,6 +72,8 @@ public: Image *grid[9]; void setAlpha(float alpha); + int minWidth() const; + int minHeight() const; }; /** diff --git a/src/gui/minimap.cpp b/src/gui/minimap.cpp index 14e2a257..fc7fddd0 100644 --- a/src/gui/minimap.cpp +++ b/src/gui/minimap.cpp @@ -59,8 +59,7 @@ Minimap::Minimap(): setVisible(config.showMinimap, isSticky()); } -Minimap::~Minimap() -{} +Minimap::~Minimap() = default; void Minimap::setMap(Map *map) { diff --git a/src/gui/speechbubble.cpp b/src/gui/speechbubble.cpp index 58747d57..72ec8bd2 100644 --- a/src/gui/speechbubble.cpp +++ b/src/gui/speechbubble.cpp @@ -27,14 +27,11 @@ #include "gui/widgets/label.h" #include "gui/widgets/textbox.h" -#include "resources/theme.h" - #include <guichan/font.hpp> - #include <guichan/widgets/label.hpp> -SpeechBubble::SpeechBubble(): - Popup("Speech", "speechbubble.xml") +SpeechBubble::SpeechBubble() + : Popup("Speech", SkinType::SpeechBubble) { setMinWidth(0); setMinHeight(0); @@ -45,7 +42,7 @@ SpeechBubble::SpeechBubble(): mSpeechBox = new TextBox; mSpeechBox->setEditable(false); mSpeechBox->setOpaque(false); - mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::CHAT)); + mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::BUBBLE_TEXT)); add(mCaption); add(mSpeechBox); @@ -60,11 +57,9 @@ void SpeechBubble::setCaption(const std::string &name, const gcn::Color *color) void SpeechBubble::setText(const std::string &text, bool showName) { - if (text == mText && (mCaption->getWidth() <= mSpeechBox->getMinWidth())) + if (text == mText && mCaption->getWidth() <= mSpeechBox->getMinWidth()) return; - mSpeechBox->setTextColor(&Theme::getThemeColor(Theme::TEXT)); - int width = mCaption->getWidth(); mSpeechBox->setTextWrapped(text, 130 > width ? 130 : width); const int speechWidth = mSpeechBox->getMinWidth(); diff --git a/src/gui/widgets/button.cpp b/src/gui/widgets/button.cpp index eb2722e5..215cb5a0 100644 --- a/src/gui/widgets/button.cpp +++ b/src/gui/widgets/button.cpp @@ -123,23 +123,21 @@ void Button::init() void Button::draw(gcn::Graphics *graphics) { - Theme::WidgetState state; - state.width = getWidth(); - state.height = getHeight(); - state.enabled = isEnabled(); - state.hovered = mHasMouse; - state.selected = isPressed(); - state.focused = isFocused(); + WidgetState state(this); + if (mHasMouse) + state.flags |= STATE_HOVERED; + if (isPressed()) + state.flags |= STATE_SELECTED; - gui->getTheme()->drawButton(static_cast<Graphics*>(graphics), state); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::Button, state); int mode; - if (!state.enabled) + if (state.flags & STATE_DISABLED) mode = BUTTON_DISABLED; - else if (state.selected) + else if (state.flags & STATE_SELECTED) mode = BUTTON_PRESSED; - else if (state.hovered || state.focused) + else if (state.flags & (STATE_HOVERED | STATE_FOCUSED)) mode = BUTTON_HIGHLIGHTED; else mode = BUTTON_STANDARD; diff --git a/src/gui/widgets/checkbox.cpp b/src/gui/widgets/checkbox.cpp index e2d13ab2..bea5f1d9 100644 --- a/src/gui/widgets/checkbox.cpp +++ b/src/gui/widgets/checkbox.cpp @@ -43,12 +43,13 @@ void CheckBox::draw(gcn::Graphics* graphics) void CheckBox::drawBox(gcn::Graphics* graphics) { - Theme::WidgetState state; - state.enabled = isEnabled(); - state.hovered = mHasMouse; - state.selected = isSelected(); + WidgetState state(this); + if (mHasMouse) + state.flags |= STATE_HOVERED; + if (isSelected()) + state.flags |= STATE_SELECTED; - gui->getTheme()->drawCheckBox(graphics, state); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::CheckBox, state); } void CheckBox::mouseEntered(gcn::MouseEvent& event) diff --git a/src/gui/widgets/dropdown.cpp b/src/gui/widgets/dropdown.cpp index 7202827b..1d54efd7 100644 --- a/src/gui/widgets/dropdown.cpp +++ b/src/gui/widgets/dropdown.cpp @@ -87,23 +87,31 @@ void DropDown::drawFrame(gcn::Graphics *graphics) { const int bs = getFrameSize(); - Theme::WidgetState state; - state.width = getWidth() + bs * 2; - state.height = getHeight() + bs * 2; + WidgetState state(this); + state.width += bs * 2; + state.height += bs * 2; - gui->getTheme()->drawDropDownFrame(static_cast<Graphics*>(graphics), state); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::DropDownFrame, state); } void DropDown::drawButton(gcn::Graphics *graphics) { - Theme::WidgetState state; - state.width = getWidth(); - state.height = mDroppedDown ? mFoldedUpHeight : getHeight(); - state.enabled = isEnabled(); - state.selected = mDroppedDown; - state.hovered = mPushed; - - gui->getTheme()->drawDropDownButton(static_cast<Graphics*>(graphics), state); + WidgetState state(this); + if (mDroppedDown) + { + state.height = mFoldedUpHeight; + state.flags |= STATE_SELECTED; + } + if (mPushed) + state.flags |= STATE_HOVERED; + + const auto theme = gui->getTheme(); + const int buttonWidth = theme->getMinWidth(SkinType::DropDownButton); + + // FIXME: Needs support for setting alignment in the theme. + state.x = state.width - buttonWidth; + + theme->drawSkin(static_cast<Graphics *>(graphics), SkinType::DropDownButton, state); } // -- KeyListener notifications diff --git a/src/gui/widgets/popup.cpp b/src/gui/widgets/popup.cpp index b12acf31..8bbab948 100644 --- a/src/gui/widgets/popup.cpp +++ b/src/gui/widgets/popup.cpp @@ -29,14 +29,13 @@ #include "gui/viewport.h" #include "gui/widgets/windowcontainer.h" -#include "resources/theme.h" - #include <guichan/exception.hpp> -Popup::Popup(const std::string &name, const std::string &skin): - mPopupName(name), - mMaxWidth(graphics->getWidth()), - mMaxHeight(graphics->getHeight()) +Popup::Popup(const std::string &name, SkinType skinType) + : mPopupName(name) + , mMaxWidth(graphics->getWidth()) + , mMaxHeight(graphics->getHeight()) + , mSkinType(skinType) { logger->log("Popup::Popup(\"%s\")", name.c_str()); @@ -45,9 +44,6 @@ Popup::Popup(const std::string &name, const std::string &skin): setPadding(6); - // Loads the skin - mSkin = gui->getTheme()->load(skin); - // Add this window to the window container windowContainer->add(this); @@ -58,8 +54,6 @@ Popup::Popup(const std::string &name, const std::string &skin): Popup::~Popup() { logger->log("Popup::~Popup(\"%s\")", mPopupName.c_str()); - - mSkin->instances--; } void Popup::setWindowContainer(WindowContainer *wc) @@ -69,10 +63,7 @@ void Popup::setWindowContainer(WindowContainer *wc) void Popup::draw(gcn::Graphics *graphics) { - auto *g = static_cast<Graphics*>(graphics); - - g->drawImageRect(0, 0, getWidth(), getHeight(), mSkin->getBorder()); - + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), mSkinType, WidgetState(this)); drawChildren(graphics); } @@ -116,12 +107,12 @@ void Popup::setLocationRelativeTo(gcn::Widget *widget) void Popup::setMinWidth(int width) { - mMinWidth = std::max(width, mSkin->getMinWidth()); + mMinWidth = std::max(gui->getTheme()->getMinWidth(mSkinType), width); } void Popup::setMinHeight(int height) { - mMinHeight = std::max(height, mSkin->getMinHeight()); + mMinHeight = std::max(gui->getTheme()->getMinHeight(mSkinType), height); } void Popup::setMaxWidth(int width) diff --git a/src/gui/widgets/popup.h b/src/gui/widgets/popup.h index 41914984..6a206672 100644 --- a/src/gui/widgets/popup.h +++ b/src/gui/widgets/popup.h @@ -25,6 +25,7 @@ #include "guichanfwd.h" #include "gui/widgets/container.h" +#include "resources/theme.h" #include <guichan/mouselistener.hpp> @@ -52,10 +53,10 @@ class Popup : public Container, public gcn::MouseListener * * @param name A human readable name for the popup. Only useful for * debugging purposes. - * @param skin The location where the Popup's skin XML can be found. + * @param skinType The skin type used when drawing the popup. */ - Popup(const std::string &name = std::string(), - const std::string &skin = "window.xml"); + explicit Popup(const std::string &name = std::string(), + SkinType skinType = SkinType::Popup); /** * Destructor. Deletes all the added widgets. @@ -151,12 +152,12 @@ class Popup : public Container, public gcn::MouseListener void position(int x, int y); private: - std::string mPopupName; /**< Name of the popup */ - int mMinWidth = 100; /**< Minimum popup width */ - int mMinHeight = 40; /**< Minimum popup height */ - int mMaxWidth; /**< Maximum popup width */ - int mMaxHeight; /**< Maximum popup height */ - int mPadding; /**< Holds the padding of the popup. */ - - Skin *mSkin; /**< Skin in use by this popup */ + std::string mPopupName; /**< Name of the popup */ + int mMinWidth = 100; /**< Minimum popup width */ + int mMinHeight = 40; /**< Minimum popup height */ + int mMaxWidth; /**< Maximum popup width */ + int mMaxHeight; /**< Maximum popup height */ + int mPadding; /**< Holds the padding of the popup. */ + + SkinType mSkinType; /**< The skin type used when drawing the popup widget. */ }; diff --git a/src/gui/widgets/radiobutton.cpp b/src/gui/widgets/radiobutton.cpp index ee2f4983..93ceee07 100644 --- a/src/gui/widgets/radiobutton.cpp +++ b/src/gui/widgets/radiobutton.cpp @@ -33,12 +33,13 @@ RadioButton::RadioButton(const std::string &caption, void RadioButton::drawBox(gcn::Graphics* graphics) { - Theme::WidgetState state; - state.enabled = isEnabled(); - state.hovered = mHasMouse; - state.selected = isSelected(); + WidgetState state(this); + if (mHasMouse) + state.flags |= STATE_HOVERED; + if (isSelected()) + state.flags |= STATE_SELECTED; - gui->getTheme()->drawRadioButton(graphics, state); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::RadioButton, state); } void RadioButton::draw(gcn::Graphics* graphics) diff --git a/src/gui/widgets/resizegrip.cpp b/src/gui/widgets/resizegrip.cpp index bda0b386..69803d07 100644 --- a/src/gui/widgets/resizegrip.cpp +++ b/src/gui/widgets/resizegrip.cpp @@ -24,20 +24,21 @@ #include "graphics.h" #include "gui/gui.h" -#include "resources/image.h" #include "resources/theme.h" #include <guichan/graphics.hpp> ResizeGrip::ResizeGrip() { - const auto gripImage = gui->getTheme()->getResizeGripImage(); - setSize(gripImage->getWidth() + 2, - gripImage->getHeight() + 2); + const auto theme = gui->getTheme(); + const auto minWidth = theme->getMinWidth(SkinType::ResizeGrip); + const auto minHeight = theme->getMinHeight(SkinType::ResizeGrip); + setSize(minWidth + 2, minHeight + 2); } void ResizeGrip::draw(gcn::Graphics *graphics) { - const auto gripImage = gui->getTheme()->getResizeGripImage(); - static_cast<Graphics*>(graphics)->drawImage(gripImage, 0, 0); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), + SkinType::ResizeGrip, + WidgetState(this)); } diff --git a/src/gui/widgets/scrollarea.cpp b/src/gui/widgets/scrollarea.cpp index 6a2cb7e4..62c37c64 100644 --- a/src/gui/widgets/scrollarea.cpp +++ b/src/gui/widgets/scrollarea.cpp @@ -24,7 +24,6 @@ #include "graphics.h" #include "gui/gui.h" -#include "resources/theme.h" ScrollArea::ScrollArea() { @@ -107,11 +106,11 @@ void ScrollArea::drawFrame(gcn::Graphics *graphics) const int bs = getFrameSize(); - Theme::WidgetState state; - state.width = getWidth() + bs * 2; - state.height = getHeight() + bs * 2; + WidgetState state(this); + state.width += bs * 2; + state.height += + bs * 2; - gui->getTheme()->drawScrollAreaFrame(static_cast<Graphics *>(graphics), state); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::ScrollArea, state); } void ScrollArea::setOpaque(bool opaque) @@ -127,38 +126,22 @@ void ScrollArea::drawBackground(gcn::Graphics *graphics) void ScrollArea::drawUpButton(gcn::Graphics *graphics) { - auto theme = gui->getTheme(); - theme->drawScrollAreaButton(static_cast<Graphics *>(graphics), - Theme::ARROW_UP, - mUpButtonPressed, - getUpButtonDimension()); + drawButton(graphics, SkinType::ButtonUp, mUpButtonPressed, getUpButtonDimension()); } void ScrollArea::drawDownButton(gcn::Graphics *graphics) { - auto theme = gui->getTheme(); - theme->drawScrollAreaButton(static_cast<Graphics *>(graphics), - Theme::ARROW_DOWN, - mDownButtonPressed, - getDownButtonDimension()); + drawButton(graphics, SkinType::ButtonDown, mDownButtonPressed, getDownButtonDimension()); } void ScrollArea::drawLeftButton(gcn::Graphics *graphics) { - auto theme = gui->getTheme(); - theme->drawScrollAreaButton(static_cast<Graphics *>(graphics), - Theme::ARROW_LEFT, - mLeftButtonPressed, - getLeftButtonDimension()); + drawButton(graphics, SkinType::ButtonLeft, mLeftButtonPressed, getLeftButtonDimension()); } void ScrollArea::drawRightButton(gcn::Graphics *graphics) { - auto theme = gui->getTheme(); - theme->drawScrollAreaButton(static_cast<Graphics *>(graphics), - Theme::ARROW_RIGHT, - mRightButtonPressed, - getRightButtonDimension()); + drawButton(graphics, SkinType::ButtonRight, mRightButtonPressed, getRightButtonDimension()); } void ScrollArea::drawVBar(gcn::Graphics *graphics) @@ -177,18 +160,45 @@ void ScrollArea::drawHBar(gcn::Graphics *graphics) void ScrollArea::drawVMarker(gcn::Graphics *graphics) { - auto theme = gui->getTheme(); - theme->drawScrollAreaMarker(static_cast<Graphics *>(graphics), - mHasMouse && (mX > (getWidth() - getScrollbarWidth())), - getVerticalMarkerDimension()); + drawMarker(static_cast<Graphics *>(graphics), + mHasMouse && (mX > (getWidth() - getScrollbarWidth())), + getVerticalMarkerDimension()); } void ScrollArea::drawHMarker(gcn::Graphics *graphics) { - auto theme = gui->getTheme(); - theme->drawScrollAreaMarker(static_cast<Graphics *>(graphics), - mHasMouse && (mY > (getHeight() - getScrollbarWidth())), - getHorizontalMarkerDimension()); + drawMarker(static_cast<Graphics *>(graphics), + mHasMouse && (mY > (getHeight() - getScrollbarWidth())), + getHorizontalMarkerDimension()); +} + +void ScrollArea::drawButton(gcn::Graphics *graphics, + SkinType skinType, + bool pressed, + const gcn::Rectangle &dim) +{ + WidgetState state; + state.x = dim.x; + state.y = dim.y; + state.width = dim.width; + state.height = dim.height; + if (pressed) + state.flags |= STATE_SELECTED; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), skinType, state); +} + +void ScrollArea::drawMarker(gcn::Graphics *graphics, bool hovered, const gcn::Rectangle &dim) +{ + WidgetState state; + state.x = dim.x; + state.y = dim.y; + state.width = dim.width; + state.height = dim.height; + if (hovered) + state.flags |= STATE_HOVERED; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::ScrollBar, state); } void ScrollArea::mouseMoved(gcn::MouseEvent& event) diff --git a/src/gui/widgets/scrollarea.h b/src/gui/widgets/scrollarea.h index ab0eb026..17f69548 100644 --- a/src/gui/widgets/scrollarea.h +++ b/src/gui/widgets/scrollarea.h @@ -21,6 +21,8 @@ #pragma once +#include "resources/theme.h" + #include <guichan/widgetlistener.hpp> #include <guichan/widgets/scrollarea.hpp> @@ -108,6 +110,12 @@ class ScrollArea : public gcn::ScrollArea, public gcn::WidgetListener void drawVMarker(gcn::Graphics *graphics) override; void drawHMarker(gcn::Graphics *graphics) override; + static void drawButton(gcn::Graphics *graphics, + SkinType skinType, + bool pressed, + const gcn::Rectangle &dim); + static void drawMarker(gcn::Graphics *graphics, bool hovered, const gcn::Rectangle &dim); + int mX = 0; int mY = 0; bool mHasMouse = false; diff --git a/src/gui/widgets/slider.cpp b/src/gui/widgets/slider.cpp index 20a843f3..7ad5aa60 100644 --- a/src/gui/widgets/slider.cpp +++ b/src/gui/widgets/slider.cpp @@ -41,19 +41,21 @@ Slider::Slider(double scaleStart, double scaleEnd): void Slider::init() { setFrameSize(0); - setMarkerLength(gui->getTheme()->getSliderMarkerLength()); + setMarkerLength(gui->getTheme()->getMinWidth(SkinType::SliderHandle)); } void Slider::draw(gcn::Graphics *graphics) { - Theme::WidgetState state; - state.width = getWidth(); - state.height = getHeight(); - state.enabled = isEnabled(); - state.hovered = mHasMouse; + WidgetState state(this); + if (mHasMouse) + state.flags |= STATE_HOVERED; auto theme = gui->getTheme(); - theme->drawSlider(static_cast<Graphics*>(graphics), state, getMarkerPosition()); + theme->drawSkin(static_cast<Graphics*>(graphics), SkinType::Slider, state); + + WidgetState handleState(state); + handleState.x += getMarkerPosition(); + theme->drawSkin(static_cast<Graphics*>(graphics), SkinType::SliderHandle, handleState); } void Slider::drawMarker(gcn::Graphics *graphics) diff --git a/src/gui/widgets/tab.cpp b/src/gui/widgets/tab.cpp index 1a857e55..5779b561 100644 --- a/src/gui/widgets/tab.cpp +++ b/src/gui/widgets/tab.cpp @@ -44,18 +44,16 @@ void Tab::init() void Tab::draw(gcn::Graphics *graphics) { - Theme::WidgetState state; - state.width = getWidth(); - state.height = getHeight(); - state.enabled = isEnabled(); - state.hovered = mHasMouse; - state.selected = mTabbedArea && mTabbedArea->isTabSelected(this); - state.focused = isFocused(); + WidgetState state(this); + if (mHasMouse) + state.flags |= STATE_HOVERED; + if (mTabbedArea && mTabbedArea->isTabSelected(this)) + state.flags |= STATE_SELECTED; - gui->getTheme()->drawTab(static_cast<Graphics*>(graphics), state); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::Tab, state); // if tab is selected, it doesnt need to highlight activity - if (state.selected) + if (state.flags & STATE_SELECTED) mFlash = false; if (mFlash) diff --git a/src/gui/widgets/textfield.cpp b/src/gui/widgets/textfield.cpp index a1a20e90..46fa18ae 100644 --- a/src/gui/widgets/textfield.cpp +++ b/src/gui/widgets/textfield.cpp @@ -60,11 +60,11 @@ void TextField::drawFrame(gcn::Graphics *graphics) { const int bs = getFrameSize(); - Theme::WidgetState state; - state.width = getWidth() + bs * 2; - state.height = getHeight() + bs * 2; + WidgetState state(this); + state.width += bs * 2; + state.height += bs * 2; - gui->getTheme()->drawTextFieldFrame(static_cast<Graphics*>(graphics), state); + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::TextField, state); } void TextField::setNumeric(bool numeric) diff --git a/src/gui/widgets/window.cpp b/src/gui/widgets/window.cpp index 52ee917e..d47e1a29 100644 --- a/src/gui/widgets/window.cpp +++ b/src/gui/widgets/window.cpp @@ -31,7 +31,6 @@ #include "gui/widgets/resizegrip.h" #include "gui/widgets/windowcontainer.h" -#include "resources/image.h" #include "resources/theme.h" #include <guichan/exception.hpp> @@ -43,13 +42,12 @@ int Window::instances = 0; int Window::mouseResize = 0; -Window::Window(const std::string &caption, bool modal, Window *parent, - const std::string &skin): - gcn::Window(caption), - mParent(parent), - mModal(modal), - mMaxWinWidth(graphics->getWidth()), - mMaxWinHeight(graphics->getHeight()) +Window::Window(const std::string &caption, bool modal, Window *parent) + : gcn::Window(caption) + , mParent(parent) + , mModal(modal) + , mMaxWinWidth(graphics->getWidth()) + , mMaxWinHeight(graphics->getHeight()) { logger->log("Window::Window(\"%s\")", caption.c_str()); @@ -62,9 +60,6 @@ Window::Window(const std::string &caption, bool modal, Window *parent, setPadding(3); setTitleBarHeight(20); - // Loads the skin - mSkin = gui->getTheme()->load(skin); - // Add this window to the window container windowContainer->add(this); @@ -94,8 +89,6 @@ Window::~Window() removeWidgetListener(this); instances--; - - mSkin->instances--; } void Window::setWindowContainer(WindowContainer *wc) @@ -105,9 +98,11 @@ void Window::setWindowContainer(WindowContainer *wc) void Window::draw(gcn::Graphics *graphics) { - auto *g = static_cast<Graphics*>(graphics); + auto g = static_cast<Graphics*>(graphics); + auto theme = gui->getTheme(); - g->drawImageRect(0, 0, getWidth(), getHeight(), mSkin->getBorder()); + WidgetState state(this); + theme->drawSkin(g, SkinType::Window, state); // Draw title if (mShowTitle) @@ -117,23 +112,26 @@ void Window::draw(gcn::Graphics *graphics) g->drawText(getCaption(), 7, 5, gcn::Graphics::LEFT); } + const int closeButtonWidth = theme->getMinWidth(SkinType::ButtonClose); + const int stickyButtonWidth = theme->getMinWidth(SkinType::ButtonSticky); + // Draw Close Button if (mCloseButton) { - g->drawImage(mSkin->getCloseImage(), - getWidth() - mSkin->getCloseImage()->getWidth() - getPadding(), - getPadding()); + state.x = state.width - closeButtonWidth - getPadding(); + state.y = getPadding(); + theme->drawSkin(g, SkinType::ButtonClose, state); } // Draw Sticky Button if (mStickyButton) { - Image *button = mSkin->getStickyImage(mSticky); - int x = getWidth() - button->getWidth() - getPadding(); + state.flags = mSticky ? STATE_SELECTED : 0; + state.x = state.width - stickyButtonWidth - getPadding(); + state.y = getPadding(); if (mCloseButton) - x -= mSkin->getCloseImage()->getWidth(); - - g->drawImage(button, x, getPadding()); + state.x -= closeButtonWidth; + theme->drawSkin(g, SkinType::ButtonSticky, state); } drawChildren(graphics); @@ -218,13 +216,12 @@ void Window::setLocationRelativeTo(ImageRect::ImagePosition position, void Window::setMinWidth(int width) { - mMinWinWidth = width > mSkin->getMinWidth() ? width : mSkin->getMinWidth(); + mMinWinWidth = std::max(gui->getTheme()->getMinWidth(SkinType::Window), width); } void Window::setMinHeight(int height) { - mMinWinHeight = height > mSkin->getMinHeight() ? - height : mSkin->getMinHeight(); + mMinWinHeight = std::max(gui->getTheme()->getMinHeight(SkinType::Window), height); } void Window::setMaxWidth(int width) @@ -338,38 +335,42 @@ void Window::mousePressed(gcn::MouseEvent &event) if (event.getButton() == gcn::MouseEvent::LEFT) { + auto theme = gui->getTheme(); + const int x = event.getX(); const int y = event.getY(); + const int closeButtonWidth = theme->getMinWidth(SkinType::ButtonClose); + const int closeButtonHeight = theme->getMinHeight(SkinType::ButtonClose); + const int stickyButtonWidth = theme->getMinWidth(SkinType::ButtonSticky); + const int stickyButtonHeight = theme->getMinHeight(SkinType::ButtonSticky); + // Handle close button if (mCloseButton) { - gcn::Rectangle closeButtonRect( - getWidth() - mSkin->getCloseImage()->getWidth() - getPadding(), - getPadding(), - mSkin->getCloseImage()->getWidth(), - mSkin->getCloseImage()->getHeight()); + gcn::Rectangle closeButtonRect(getWidth() - closeButtonWidth - getPadding(), + getPadding(), + closeButtonWidth, + closeButtonHeight); if (closeButtonRect.isPointInRect(x, y)) - { close(); - } } // Handle sticky button if (mStickyButton) { - Image *button = mSkin->getStickyImage(mSticky); - int rx = getWidth() - button->getWidth() - getPadding(); + int stickyButtonX = getWidth() - stickyButtonWidth - getPadding(); if (mCloseButton) - rx -= mSkin->getCloseImage()->getWidth(); - gcn::Rectangle stickyButtonRect(rx, getPadding(), - button->getWidth(), button->getHeight()); + stickyButtonX -= closeButtonWidth; + + gcn::Rectangle stickyButtonRect(stickyButtonX, + getPadding(), + stickyButtonWidth, + stickyButtonHeight); if (stickyButtonRect.isPointInRect(x, y)) - { setSticky(!isSticky()); - } } // Handle window resizing diff --git a/src/gui/widgets/window.h b/src/gui/widgets/window.h index 686e5f43..916d767b 100644 --- a/src/gui/widgets/window.h +++ b/src/gui/widgets/window.h @@ -53,10 +53,9 @@ class Window : public gcn::Window, gcn::WidgetListener * @param parent The parent window. This is the window standing above * this one in the window hiearchy. When reordering, * a window will never go below its parent window. - * @param skin The location where the window's skin XML can be found. */ Window(const std::string &caption = "Window", bool modal = false, - Window *parent = nullptr, const std::string &skin = "window.xml"); + Window *parent = nullptr); /** * Destructor. Deletes all the added widgets. @@ -398,8 +397,6 @@ class Window : public gcn::Window, gcn::WidgetListener static int instances; /**< Number of Window instances */ - Skin *mSkin; /**< Skin in use by this window */ - /** * The width of the resize border. Is independent of the actual window * border width, and determines mostly the size of the corner area diff --git a/src/resources/theme.cpp b/src/resources/theme.cpp index 41d09ab2..a3cbbabd 100644 --- a/src/resources/theme.cpp +++ b/src/resources/theme.cpp @@ -33,10 +33,9 @@ #include "resources/resourcemanager.h" #include "utils/filesystem.h" -#include "utils/stringutils.h" -#include "utils/xml.h" #include <guichan/font.hpp> +#include <guichan/widget.hpp> #include <algorithm> #include <optional> @@ -71,425 +70,207 @@ static std::optional<std::string> findThemePath(const std::string &theme) } -Skin::Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown): - mBorder(std::move(skin)), - mCloseImage(close), - mStickyImageUp(stickyUp), - mStickyImageDown(stickyDown) -{} - -Skin::~Skin() = default; - -void Skin::updateAlpha(float alpha) +WidgetState::WidgetState(const gcn::Widget *widget) + : width(widget->getWidth()) + , height(widget->getHeight()) { - mBorder.setAlpha(alpha); + // x and y are not set based on the widget because the rendering usually + // happens in local coordinates. - mCloseImage->setAlpha(alpha); - mStickyImageUp->setAlpha(alpha); - mStickyImageDown->setAlpha(alpha); + if (!widget->isEnabled()) + flags |= STATE_DISABLED; + if (widget->isFocused()) + flags |= STATE_FOCUSED; } -int Skin::getMinWidth() const + +Skin::~Skin() { - return mBorder.grid[ImageRect::UPPER_LEFT]->getWidth() + - mBorder.grid[ImageRect::UPPER_RIGHT]->getWidth(); + // Raw Image* need explicit deletion + for (auto &state : mStates) + for (auto &part : state.parts) + if (auto image = std::get_if<Image *>(&part.data)) + delete *image; } -int Skin::getMinHeight() const +void Skin::addState(SkinState state) { - return mBorder.grid[ImageRect::UPPER_LEFT]->getHeight() + - mBorder.grid[ImageRect::LOWER_LEFT]->getHeight(); + mStates.emplace_back(std::move(state)); } - -enum { - BUTTON_MODE_STANDARD, // 0 - BUTTON_MODE_HIGHLIGHTED, // 1 - BUTTON_MODE_PRESSED, // 2 - BUTTON_MODE_DISABLED, // 3 - BUTTON_MODE_COUNT // 4 - Must be last. -}; - -enum { - TAB_STANDARD, // 0 - TAB_HIGHLIGHTED, // 1 - TAB_SELECTED, // 2 - TAB_UNUSED, // 3 - TAB_COUNT // 4 - Must be last. -}; - -Theme::Theme(const std::string &path) - : Palette(THEME_COLORS_END) - , mThemePath(path) - , mProgressColors(THEME_PROG_END) +void Skin::draw(Graphics *graphics, const WidgetState &state) const { - listen(Event::ConfigChannel); - loadColors(); - - mColors[HIGHLIGHT].ch = 'H'; - mColors[CHAT].ch = 'C'; - mColors[GM].ch = 'G'; - mColors[PLAYER].ch = 'Y'; - mColors[WHISPER].ch = 'W'; - mColors[IS].ch = 'I'; - mColors[PARTY].ch = 'P'; - mColors[GUILD].ch = 'U'; - mColors[SERVER].ch = 'S'; - mColors[LOGGER].ch = 'L'; - mColors[HYPERLINK].ch = '<'; - - // Button skin - struct ButtonData + for (const auto &skinState : mStates) { - char const *file; - int gridX; - int gridY; - }; - - constexpr ButtonData data[BUTTON_MODE_COUNT] = { - { "button.png", 0, 0 }, - { "buttonhi.png", 9, 4 }, - { "buttonpress.png", 16, 19 }, - { "button_disabled.png", 25, 23 } - }; + if (skinState.stateFlags != (skinState.setFlags & state.flags)) + continue; - for (int mode = 0; mode < BUTTON_MODE_COUNT; ++mode) - { - auto modeImage = getImage(data[mode].file); - int a = 0; - for (int y = 0; y < 3; y++) + for (const auto &part : skinState.parts) { - for (int x = 0; x < 3; x++) - { - mButton[mode].grid[a] = modeImage->getSubImage( - data[x].gridX, data[y].gridY, - data[x + 1].gridX - data[x].gridX + 1, - data[y + 1].gridY - data[y].gridY + 1); - a++; - } + std::visit([&](const auto &data) { + using T = std::decay_t<decltype(data)>; + + if constexpr (std::is_same_v<T, ImageRect>) + { + graphics->drawImageRect(state.x + part.offsetX, + state.y + part.offsetY, + state.width, + state.height, + data); + } + else if constexpr (std::is_same_v<T, Image*>) + { + graphics->drawImage(data, state.x + part.offsetX, state.y + part.offsetY); + } + }, part.data); } - } - // Tab skin - struct TabData - { - char const *file; - int gridX[4]; - int gridY[4]; - }; + break; // Only draw the first matching state + } +} - constexpr TabData tabData[TAB_COUNT] = { - { "tab.png", {0, 9, 16, 25}, {0, 13, 19, 20} }, - { "tab_hilight.png", {0, 9, 16, 25}, {0, 13, 19, 20} }, - { "tabselected.png", {0, 9, 16, 25}, {0, 4, 12, 20} }, - { "tab.png", {0, 9, 16, 25}, {0, 13, 19, 20} } - }; +int Skin::getMinWidth() const +{ + int minWidth = 0; - for (int mode = 0; mode < TAB_COUNT; mode++) + for (const auto &state : mStates) { - auto tabImage = getImage(tabData[mode].file); - int a = 0; - for (int y = 0; y < 3; y++) + for (const auto &part : state.parts) { - for (int x = 0; x < 3; x++) - { - mTabImg[mode].grid[a] = tabImage->getSubImage( - tabData[mode].gridX[x], tabData[mode].gridY[y], - tabData[mode].gridX[x + 1] - tabData[mode].gridX[x] + 1, - tabData[mode].gridY[y + 1] - tabData[mode].gridY[y] + 1); - a++; - } + if (auto imageRect = std::get_if<ImageRect>(&part.data)) + minWidth = std::max(minWidth, imageRect->minWidth()); + else if (auto img = std::get_if<Image *>(&part.data)) + minWidth = std::max(minWidth, (*img)->getWidth()); } - mTabImg[mode].setAlpha(mAlpha); } - // TextField images - auto deepBox = getImage("deepbox.png"); - constexpr int gridx[4] = {0, 3, 28, 31}; - constexpr int gridy[4] = {0, 3, 28, 31}; - int a = 0; + return minWidth; +} + +int Skin::getMinHeight() const +{ + int minHeight = 0; - for (int y = 0; y < 3; y++) + for (const auto &state : mStates) { - for (int x = 0; x < 3; x++) + for (const auto &part : state.parts) { - mDeepBoxImageRect.grid[a] = deepBox->getSubImage(gridx[x], - gridy[y], - gridx[x + 1] - gridx[x] + 1, - gridy[y + 1] - gridy[y] + 1); - a++; + if (auto imageRect = std::get_if<ImageRect>(&part.data)) + minHeight = std::max(minHeight, imageRect->minHeight()); + else if (auto img = std::get_if<Image *>(&part.data)) + minHeight = std::max(minHeight, (*img)->getHeight()); } } - // CheckBox images - auto checkBox = getImage("checkbox.png"); - mCheckBoxNormal.reset(checkBox->getSubImage(0, 0, 9, 10)); - mCheckBoxChecked.reset(checkBox->getSubImage(9, 0, 9, 10)); - mCheckBoxDisabled.reset(checkBox->getSubImage(18, 0, 9, 10)); - mCheckBoxDisabledChecked.reset(checkBox->getSubImage(27, 0, 9, 10)); - mCheckBoxNormalHi.reset(checkBox->getSubImage(36, 0, 9, 10)); - mCheckBoxCheckedHi.reset(checkBox->getSubImage(45, 0, 9, 10)); - - // RadioButton images - mRadioNormal = getImage("radioout.png"); - mRadioChecked = getImage("radioin.png"); - mRadioDisabled = getImage("radioout.png"); - mRadioDisabledChecked = getImage("radioin.png"); - mRadioNormalHi = getImage("radioout_highlight.png"); - mRadioCheckedHi = getImage("radioin_highlight.png"); - - // Slider images - int x, y, w, h, o1, o2; - auto slider = getImage("slider.png"); - auto sliderHi = getImage("slider_hilight.png"); - - x = 0; y = 0; - w = 15; h = 6; - o1 = 4; o2 = 11; - hStart.reset(slider->getSubImage(x, y, o1 - x, h)); - hMid.reset(slider->getSubImage(o1, y, o2 - o1, h)); - hEnd.reset(slider->getSubImage(o2, y, w - o2 + x, h)); - hStartHi.reset(sliderHi->getSubImage(x, y, o1 - x, h)); - hMidHi.reset(sliderHi->getSubImage(o1, y, o2 - o1, h)); - hEndHi.reset(sliderHi->getSubImage(o2, y, w - o2 + x, h)); - - x = 6; y = 8; - w = 9; h = 10; - hGrip.reset(slider->getSubImage(x, y, w, h)); - hGripHi.reset(sliderHi->getSubImage(x, y, w, h)); - - x = 0; y = 6; - w = 6; h = 21; - o1 = 10; o2 = 18; - vStart.reset(slider->getSubImage(x, y, w, o1 - y)); - vMid.reset(slider->getSubImage(x, o1, w, o2 - o1)); - vEnd.reset(slider->getSubImage(x, o2, w, h - o2 + y)); - vStartHi.reset(sliderHi->getSubImage(x, y, w, o1 - y)); - vMidHi.reset(sliderHi->getSubImage(x, o1, w, o2 - o1)); - vEndHi.reset(sliderHi->getSubImage(x, o2, w, h - o2 + y)); - - x = 6; y = 8; - w = 9; h = 10; - vGrip.reset(slider->getSubImage(x, y, w, h)); - vGripHi.reset(sliderHi->getSubImage(x, y, w, h)); - - // ProgressBar and ScrollArea images - auto vscroll = getImage("vscroll_grey.png"); - auto vscrollHi = getImage("vscroll_highlight.png"); - - constexpr int vsgridx[4] = {0, 4, 7, 11}; - constexpr int vsgridy[4] = {0, 4, 15, 19}; - a = 0; - - for (int y = 0; y < 3; y++) + return minHeight; +} + +void Skin::updateAlpha(float alpha) +{ + for (auto &state : mStates) { - for (int x = 0; x < 3; x++) + for (auto &part : state.parts) { - mScrollBarMarker.grid[a] = vscroll->getSubImage( - vsgridx[x], vsgridy[y], - vsgridx[x + 1] - vsgridx[x], - vsgridy[y + 1] - vsgridy[y]); - mScrollBarMarkerHi.grid[a] = vscrollHi->getSubImage( - vsgridx[x], vsgridy[y], - vsgridx[x + 1] - vsgridx[x], - vsgridy[y + 1] - vsgridy[y]); - a++; + if (auto rect = std::get_if<ImageRect>(&part.data)) + rect->setAlpha(alpha); + else if (auto img = std::get_if<Image *>(&part.data)) + (*img)->setAlpha(alpha); } } - - mScrollBarMarker.setAlpha(config.guiAlpha); - mScrollBarMarkerHi.setAlpha(config.guiAlpha); - - // DropDown and ScrollArea buttons - mArrowButtons[ARROW_UP][0] = getImage("vscroll_up_default.png"); - mArrowButtons[ARROW_DOWN][0] = getImage("vscroll_down_default.png"); - mArrowButtons[ARROW_LEFT][0] = getImage("hscroll_left_default.png"); - mArrowButtons[ARROW_RIGHT][0] = getImage("hscroll_right_default.png"); - mArrowButtons[ARROW_UP][1] = getImage("vscroll_up_pressed.png"); - mArrowButtons[ARROW_DOWN][1] = getImage("vscroll_down_pressed.png"); - mArrowButtons[ARROW_LEFT][1] = getImage("hscroll_left_pressed.png"); - mArrowButtons[ARROW_RIGHT][1] = getImage("hscroll_right_pressed.png"); - - mResizeGripImage = getImage("resize.png"); } -Theme::~Theme() = default; -const gcn::Color &Theme::getThemeColor(int type, int alpha) +Theme::Theme(const std::string &path) + : Palette(THEME_COLORS_END) + , mThemePath(path) + , mProgressColors(THEME_PROG_END) { - return gui->getTheme()->getColor(type, alpha); -} + listen(Event::ConfigChannel); + readTheme("theme.xml"); -const gcn::Color &Theme::getThemeColor(char c, bool &valid) -{ - return gui->getTheme()->getColor(c, valid); + mColors[HIGHLIGHT].ch = 'H'; + mColors[CHAT].ch = 'C'; + mColors[GM].ch = 'G'; + mColors[PLAYER].ch = 'Y'; + mColors[WHISPER].ch = 'W'; + mColors[IS].ch = 'I'; + mColors[PARTY].ch = 'P'; + mColors[GUILD].ch = 'U'; + mColors[SERVER].ch = 'S'; + mColors[LOGGER].ch = 'L'; + mColors[HYPERLINK].ch = '<'; } -gcn::Color Theme::getProgressColor(int type, float progress) -{ - const auto &dye = gui->getTheme()->mProgressColors[type]; - - int color[3] = {0, 0, 0}; - dye->getColor(progress, color); - - return gcn::Color(color[0], color[1], color[2]); -} +Theme::~Theme() = default; -Skin *Theme::load(const std::string &filename) +std::string Theme::prepareThemePath() { - // Check if this skin was already loaded - auto skinIterator = mSkins.find(filename); - if (skinIterator != mSkins.end()) - { - auto &skin = skinIterator->second; - skin->instances++; - return skin.get(); - } + initDefaultThemePath(); - auto skin = readSkin(filename); - if (!skin) - { - logger->error(strprintf("Error: Loading default skin '%s' failed. " - "Make sure the skin file is valid.", - mThemePath.c_str())); - } + // Try theme from settings + auto themePath = findThemePath(config.theme); + + // Try theme from branding + if (!themePath) + themePath = findThemePath(branding.getStringValue("theme")); - // Add the skin to the loaded skins - return (mSkins[filename] = std::move(skin)).get(); + return themePath.value_or(defaultThemePath); } -void Theme::drawButton(Graphics *graphics, const WidgetState &state) const +std::string Theme::resolvePath(const std::string &path) const { - int mode; - - if (!state.enabled) - mode = BUTTON_MODE_DISABLED; - else if (state.selected) - mode = BUTTON_MODE_PRESSED; - else if (state.hovered || state.focused) - mode = BUTTON_MODE_HIGHLIGHTED; + // Need to strip off any dye info for the existence tests + int pos = path.find('|'); + std::string file; + if (pos > 0) + file = path.substr(0, pos); else - mode = BUTTON_MODE_STANDARD; + file = path; - graphics->drawImageRect(0, 0, state.width, state.height, mButton[mode]); + // Try the theme + file = mThemePath + "/" + file; + if (FS::exists(file)) + return mThemePath + "/" + path; + + // Backup + return defaultThemePath + "/" + path; } -void Theme::drawTextFieldFrame(Graphics *graphics, const WidgetState &state) const +ResourceRef<Image> Theme::getImage(const std::string &path) const { - graphics->drawImageRect(0, 0, state.width, state.height, mDeepBoxImageRect); + return ResourceManager::getInstance()->getImage(resolvePath(path)); } -void Theme::drawTab(Graphics *graphics, const WidgetState &state) const +ResourceRef<Image> Theme::getImageFromTheme(const std::string &path) { - int mode = TAB_STANDARD; - - if (state.selected) - mode = TAB_SELECTED; - else if (state.hovered) - mode = TAB_HIGHLIGHTED; - - graphics->drawImageRect(0, 0, state.width, state.height, mTabImg[mode]); + return gui->getTheme()->getImage(path); } -void Theme::drawCheckBox(gcn::Graphics *graphics, const WidgetState &state) const +const gcn::Color &Theme::getThemeColor(int type, int alpha) { - Image *box; - - if (state.enabled) - { - if (state.selected) - { - if (state.hovered) - box = mCheckBoxCheckedHi.get(); - else - box = mCheckBoxChecked.get(); - } - else - { - if (state.hovered) - box = mCheckBoxNormalHi.get(); - else - box = mCheckBoxNormal.get(); - } - } - else - { - if (state.selected) - box = mCheckBoxDisabledChecked.get(); - else - box = mCheckBoxDisabled.get(); - } - - static_cast<Graphics*>(graphics)->drawImage(box, 2, 2); + return gui->getTheme()->getColor(type, alpha); } -void Theme::drawRadioButton(gcn::Graphics *graphics, const WidgetState &state) const +const gcn::Color &Theme::getThemeColor(char c, bool &valid) { - Image *box = nullptr; - - if (state.enabled) - { - if (state.selected) - { - if (state.hovered) - box = mRadioCheckedHi; - else - box = mRadioChecked; - } - else - { - if (state.hovered) - box = mRadioNormalHi; - else - box = mRadioNormal; - } - } - else - { - if (state.selected) - box = mRadioDisabledChecked; - else - box = mRadioDisabled; - } - - static_cast<Graphics*>(graphics)->drawImage(box, 2, 2); + return gui->getTheme()->getColor(c, valid); } -void Theme::drawSlider(Graphics *graphics, const WidgetState &state, int markerPosition) const +gcn::Color Theme::getProgressColor(int type, float progress) { - auto start = state.hovered ? hStartHi.get() : hStart.get(); - auto mid = state.hovered ? hMidHi.get() : hMid.get(); - auto end = state.hovered ? hEndHi.get() : hEnd.get(); - auto grip = state.hovered ? hGripHi.get() : hGrip.get(); - - int w = state.width; - int h = state.height; - int x = 0; - int y = (h - start->getHeight()) / 2; - - graphics->drawImage(start, x, y); - - w -= start->getWidth() + end->getWidth(); - x += start->getWidth(); - - graphics->drawImagePattern(mid, x, y, w, mid->getHeight()); - - x += w; - graphics->drawImage(end, x, y); + const auto &dye = gui->getTheme()->mProgressColors[type]; - graphics->drawImage(grip, markerPosition, (state.height - grip->getHeight()) / 2); -} + int color[3] = {0, 0, 0}; + dye->getColor(progress, color); -void Theme::drawDropDownFrame(Graphics *graphics, const WidgetState &state) const -{ - graphics->drawImageRect(0, 0, state.width, state.height, mDeepBoxImageRect); + return gcn::Color(color[0], color[1], color[2]); } -void Theme::drawDropDownButton(Graphics *graphics, const WidgetState &state) const +void Theme::drawSkin(Graphics *graphics, SkinType type, const WidgetState &state) const { - const auto buttonDir = state.selected ? ARROW_UP : ARROW_DOWN; - const Image *img = mArrowButtons[buttonDir][state.hovered]; - graphics->drawImage(img, state.width - img->getHeight() - 2, 2); + auto it = mSkins.find(type); + if (it != mSkins.end()) + it->second.draw(graphics, state); } void Theme::drawProgressBar(Graphics *graphics, const gcn::Rectangle &area, @@ -499,7 +280,13 @@ void Theme::drawProgressBar(Graphics *graphics, const gcn::Rectangle &area, gcn::Font *oldFont = graphics->getFont(); gcn::Color oldColor = graphics->getColor(); - graphics->drawImageRect(area, mScrollBarMarker); + WidgetState state; + state.x = area.x; + state.y = area.y; + state.width = area.width; + state.height = area.height; + + drawSkin(graphics, SkinType::ProgressBar, state); // The bar if (progress > 0) @@ -532,29 +319,20 @@ void Theme::drawProgressBar(Graphics *graphics, const gcn::Rectangle &area, graphics->setColor(oldColor); } -void Theme::drawScrollAreaFrame(Graphics *graphics, const WidgetState &state) const +int Theme::getMinWidth(SkinType skinType) const { - graphics->drawImageRect(0, 0, state.width, state.height, mDeepBoxImageRect); + auto it = mSkins.find(skinType); + if (it != mSkins.end()) + return it->second.getMinWidth(); + return 0; } -void Theme::drawScrollAreaButton(Graphics *graphics, - ArrowButtonDirection dir, - bool pressed, - const gcn::Rectangle &dim) const +int Theme::getMinHeight(SkinType skinType) const { - const int state = pressed ? 1 : 0; - graphics->drawImage(mArrowButtons[dir][state], dim.x, dim.y); -} - -void Theme::drawScrollAreaMarker(Graphics *graphics, bool hovered, const gcn::Rectangle &dim) const -{ - const auto &imageRect = hovered ? mScrollBarMarkerHi : mScrollBarMarker; - graphics->drawImageRect(dim.x, dim.y, dim.width, dim.height, imageRect); -} - -int Theme::getSliderMarkerLength() const -{ - return hGrip->getWidth(); + auto it = mSkins.find(skinType); + if (it != mSkins.end()) + return it->second.getMinHeight(); + return 0; } void Theme::setMinimumOpacity(float minimumOpacity) @@ -575,61 +353,7 @@ void Theme::updateAlpha() mAlpha = alpha; for (auto &skin : mSkins) - skin.second->updateAlpha(mAlpha); - - for (auto &mode : mButton) - mode.setAlpha(mAlpha); - - for (auto &t : mTabImg) - t.setAlpha(mAlpha); - - mDeepBoxImageRect.setAlpha(mAlpha); - - mCheckBoxNormal->setAlpha(mAlpha); - mCheckBoxChecked->setAlpha(mAlpha); - mCheckBoxDisabled->setAlpha(mAlpha); - mCheckBoxDisabledChecked->setAlpha(mAlpha); - mCheckBoxNormalHi->setAlpha(mAlpha); - mCheckBoxCheckedHi->setAlpha(mAlpha); - - mRadioNormal->setAlpha(mAlpha); - mRadioChecked->setAlpha(mAlpha); - mRadioDisabled->setAlpha(mAlpha); - mRadioDisabledChecked->setAlpha(mAlpha); - mRadioNormalHi->setAlpha(mAlpha); - mRadioCheckedHi->setAlpha(mAlpha); - - hStart->setAlpha(mAlpha); - hMid->setAlpha(mAlpha); - hEnd->setAlpha(mAlpha); - hGrip->setAlpha(mAlpha); - hStartHi->setAlpha(mAlpha); - hMidHi->setAlpha(mAlpha); - hEndHi->setAlpha(mAlpha); - hGripHi->setAlpha(mAlpha); - - vStart->setAlpha(mAlpha); - vMid->setAlpha(mAlpha); - vEnd->setAlpha(mAlpha); - vGrip->setAlpha(mAlpha); - vStartHi->setAlpha(mAlpha); - vMidHi->setAlpha(mAlpha); - vEndHi->setAlpha(mAlpha); - vGripHi->setAlpha(mAlpha); - - mScrollBarMarker.setAlpha(mAlpha); - mScrollBarMarkerHi.setAlpha(mAlpha); - - mArrowButtons[ARROW_UP][0]->setAlpha(mAlpha); - mArrowButtons[ARROW_DOWN][0]->setAlpha(mAlpha); - mArrowButtons[ARROW_LEFT][0]->setAlpha(mAlpha); - mArrowButtons[ARROW_RIGHT][0]->setAlpha(mAlpha); - mArrowButtons[ARROW_UP][1]->setAlpha(mAlpha); - mArrowButtons[ARROW_DOWN][1]->setAlpha(mAlpha); - mArrowButtons[ARROW_LEFT][1]->setAlpha(mAlpha); - mArrowButtons[ARROW_RIGHT][1]->setAlpha(mAlpha); - - mResizeGripImage->setAlpha(mAlpha); + skin.second.updateAlpha(mAlpha); } void Theme::event(Event::Channel channel, const Event &event) @@ -642,149 +366,204 @@ void Theme::event(Event::Channel channel, const Event &event) } } -std::unique_ptr<Skin> Theme::readSkin(const std::string &filename) const +static bool check(bool value, const char *msg, ...) { - if (filename.empty()) - return nullptr; + if (!value) + { + va_list args; + va_start(args, msg); + logger->log(msg, args); + va_end(args); + } + return !value; +} - logger->log("Loading skin '%s'.", filename.c_str()); +bool Theme::readTheme(const std::string &filename) +{ + logger->log("Loading theme '%s'.", filename.c_str()); XML::Document doc(resolvePath(filename)); XML::Node rootNode = doc.rootNode(); - if (!rootNode || rootNode.name() != "skinset") - return nullptr; + if (!rootNode || rootNode.name() != "theme") + return false; - const std::string skinSetImage = rootNode.getProperty("image", ""); - - if (skinSetImage.empty()) + for (auto childNode : rootNode.children()) { - logger->log("Theme::readSkin(): Skinset does not define an image!"); - return nullptr; + if (childNode.name() == "skin") + readSkinNode(childNode); + else if (childNode.name() == "color") + readColorNode(childNode); + else if (childNode.name() == "progressbar") + readProgressBarNode(childNode); } - logger->log("Theme::load(): <skinset> defines '%s' as a skin image.", - skinSetImage.c_str()); + logger->log("Finished loading theme."); + + for (auto &[_, skin] : mSkins) + skin.updateAlpha(mAlpha); + + return true; +} + +static std::optional<SkinType> readSkinType(std::string_view type) +{ + if (type == "Window") + return SkinType::Window; + if (type == "Popup") + return SkinType::Popup; + if (type == "SpeechBubble") + return SkinType::SpeechBubble; + if (type == "Button") + return SkinType::Button; + if (type == "ButtonUp") + return SkinType::ButtonUp; + if (type == "ButtonDown") + return SkinType::ButtonDown; + if (type == "ButtonLeft") + return SkinType::ButtonLeft; + if (type == "ButtonRight") + return SkinType::ButtonRight; + if (type == "ButtonClose") + return SkinType::ButtonClose; + if (type == "ButtonSticky") + return SkinType::ButtonSticky; + if (type == "CheckBox") + return SkinType::CheckBox; + if (type == "RadioButton") + return SkinType::RadioButton; + if (type == "TextField") + return SkinType::TextField; + if (type == "Tab") + return SkinType::Tab; + if (type == "ScrollArea") + return SkinType::ScrollArea; + if (type == "ScrollBar") + return SkinType::ScrollBar; + if (type == "DropDownFrame") + return SkinType::DropDownFrame; + if (type == "DropDownButton") + return SkinType::DropDownButton; + if (type == "ProgressBar") + return SkinType::ProgressBar; + if (type == "Slider") + return SkinType::Slider; + if (type == "SliderHandle") + return SkinType::SliderHandle; + if (type == "ResizeGrip") + return SkinType::ResizeGrip; + + return {}; +} + +void Theme::readSkinNode(XML::Node node) +{ + const auto skinTypeStr = node.getProperty("type", std::string()); + const auto skinType = readSkinType(skinTypeStr); + if (check(skinType.has_value(), "Theme: Unknown skin type '%s'", skinTypeStr.c_str())) + return; - auto dBorders = getImage(skinSetImage); - ImageRect border; + auto &skin = mSkins[*skinType]; - // iterate <widget>'s - for (auto widgetNode : rootNode.children()) + for (auto childNode : node.children()) + if (childNode.name() == "state") + readSkinStateNode(childNode, skin); +} + +void Theme::readSkinStateNode(XML::Node node, Skin &skin) const +{ + SkinState state; + + auto readFlag = [&] (const char *name, int flag) { - if (widgetNode.name() != "widget") - continue; + std::optional<bool> value; + node.attribute(name, value); - const std::string widgetType = - widgetNode.getProperty("type", "unknown"); - if (widgetType == "Window") - { - // Iterate through <part>'s - // LEEOR / TODO: - // We need to make provisions to load in a CloseButton image. For - // now it can just be hard-coded. - for (auto partNode : widgetNode.children()) - { - if (partNode.name() != "part") - continue; - - const std::string partType = - partNode.getProperty("type", "unknown"); - // TOP ROW - const int xPos = partNode.getProperty("xpos", 0); - const int yPos = partNode.getProperty("ypos", 0); - const int width = partNode.getProperty("width", 1); - const int height = partNode.getProperty("height", 1); - - if (partType == "top-left-corner") - border.grid[0] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "top-edge") - border.grid[1] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "top-right-corner") - border.grid[2] = dBorders->getSubImage(xPos, yPos, width, height); - - // MIDDLE ROW - else if (partType == "left-edge") - border.grid[3] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "bg-quad") - border.grid[4] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "right-edge") - border.grid[5] = dBorders->getSubImage(xPos, yPos, width, height); - - // BOTTOM ROW - else if (partType == "bottom-left-corner") - border.grid[6] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "bottom-edge") - border.grid[7] = dBorders->getSubImage(xPos, yPos, width, height); - else if (partType == "bottom-right-corner") - border.grid[8] = dBorders->getSubImage(xPos, yPos, width, height); - - else - logger->log("Theme::readSkin(): Unknown part type '%s'", - partType.c_str()); - } - } - else + if (value.has_value()) { - logger->log("Theme::readSkin(): Unknown widget type '%s'", - widgetType.c_str()); + state.setFlags |= flag; + state.stateFlags |= *value ? flag : 0; } - } + }; - logger->log("Finished loading skin."); + readFlag("selected", STATE_SELECTED); + readFlag("disabled", STATE_DISABLED); + readFlag("hovered", STATE_HOVERED); + readFlag("focused", STATE_FOCUSED); - // Hard-coded for now until we update the above code to look for window buttons - auto closeImage = getImage("close_button.png"); - auto sticky = getImage("sticky_button.png"); - Image *stickyImageUp = sticky->getSubImage(0, 0, 15, 15); - Image *stickyImageDown = sticky->getSubImage(15, 0, 15, 15); + for (auto childNode : node.children()) + if (childNode.name() == "img") + readSkinStateImgNode(childNode, state); - auto skin = std::make_unique<Skin>(std::move(border), closeImage, stickyImageUp, stickyImageDown); - skin->updateAlpha(mAlpha); - return skin; + skin.addState(std::move(state)); } -std::string Theme::prepareThemePath() +void Theme::readSkinStateImgNode(XML::Node node, SkinState &state) const { - initDefaultThemePath(); - - // Try theme from settings - auto themePath = findThemePath(config.theme); + const std::string src = node.getProperty("src", std::string()); + if (check(!src.empty(), "Theme: 'img' element has empty 'src' attribute!")) + return; - // Try theme from branding - if (!themePath) - themePath = findThemePath(branding.getStringValue("theme")); + auto image = getImage(src); + if (check(image, "Theme: Failed to load image '%s'!", src.c_str())) + return; - return themePath.value_or(defaultThemePath); -} + int left = 0; + int right = 0; + int top = 0; + int bottom = 0; + int x = 0; + int y = 0; + int width = image->getWidth(); + int height = image->getHeight(); + + node.attribute("left", left); + node.attribute("right", right); + node.attribute("top", top); + node.attribute("bottom", bottom); + node.attribute("x", x); + node.attribute("y", y); + node.attribute("width", width); + node.attribute("height", height); + + if (check(left >= 0 || right >= 0 || top >= 0 || bottom >= 0, "Theme: Invalid border value!")) + return; + if (check(x >= 0 || y >= 0, "Theme: Invalid position value!")) + return; + if (check(width >= 0 || height >= 0, "Theme: Invalid size value!")) + return; + if (check(x + width <= image->getWidth() || y + height <= image->getHeight(), "Theme: Image size out of bounds!")) + return; -std::string Theme::resolvePath(const std::string &path) const -{ - // Need to strip off any dye info for the existence tests - int pos = path.find('|'); - std::string file; - if (pos > 0) - file = path.substr(0, pos); - else - file = path; + auto &part = state.parts.emplace_back(); - // Try the theme - file = mThemePath + "/" + file; - if (FS::exists(file)) - return mThemePath + "/" + path; + node.attribute("offsetX", part.offsetX); + node.attribute("offsetY", part.offsetY); - // Backup - return defaultThemePath + "/" + path; -} + if (left + right + top + bottom > 0) + { + auto &border = part.data.emplace<ImageRect>(); -ResourceRef<Image> Theme::getImage(const std::string &path) const -{ - return ResourceManager::getInstance()->getImage(resolvePath(path)); -} + const int gridx[4] = {x, x + left, x + width - right, x + width}; + const int gridy[4] = {y, y + top, y + height - bottom, y + height}; + unsigned a = 0; -ResourceRef<Image> Theme::getImageFromTheme(const std::string &path) -{ - return gui->getTheme()->getImage(path); + for (unsigned y = 0; y < 3; y++) + { + for (unsigned x = 0; x < 3; x++) + { + border.grid[a] = image->getSubImage(gridx[x], + gridy[y], + gridx[x + 1] - gridx[x], + gridy[y + 1] - gridy[y]); + a++; + } + } + } + else + { + part.data = image->getSubImage(x, y, width, height); + } } static int readColorType(const std::string &type) @@ -805,6 +584,7 @@ static int readColorType(const std::string &type) "SHOP_WARNING", "ITEM_EQUIPPED", "CHAT", + "BUBBLE_TEXT", "GM", "PLAYER", "WHISPER", @@ -892,6 +672,22 @@ static Palette::GradientType readColorGradient(const std::string &grad) return Palette::STATIC; } +void Theme::readColorNode(XML::Node node) +{ + const int type = readColorType(node.getProperty("id", std::string())); + if (type < 0) // invalid or no type given + return; + + const std::string temp = node.getProperty("color", std::string()); + if (temp.empty()) // no color set, so move on + return; + + const gcn::Color color = readColor(temp); + const GradientType grad = readColorGradient(node.getProperty("effect", std::string())); + + mColors[type].set(type, color, grad, 10); +} + static int readProgressType(const std::string &type) { static const char *colors[] = { @@ -915,43 +711,11 @@ static int readProgressType(const std::string &type) return -1; } -void Theme::loadColors() +void Theme::readProgressBarNode(XML::Node node) { - std::string file = resolvePath("colors.xml"); - - XML::Document doc(file); - XML::Node root = doc.rootNode(); - - if (!root || root.name() != "colors") - { - logger->log("Error loading colors file: %s", file.c_str()); + const int type = readProgressType(node.getProperty("id", std::string())); + if (type < 0) // invalid or no type given return; - } - - for (auto node : root.children()) - { - if (node.name() == "color") - { - const int type = readColorType(node.getProperty("id", std::string())); - if (type < 0) // invalid or no type given - continue; - - const std::string temp = node.getProperty("color", std::string()); - if (temp.empty()) // no color set, so move on - continue; - - const gcn::Color color = readColor(temp); - const GradientType grad = readColorGradient(node.getProperty("effect", std::string())); - - mColors[type].set(type, color, grad, 10); - } - else if (node.name() == "progressbar") - { - const int type = readProgressType(node.getProperty("id", std::string())); - if (type < 0) // invalid or no type given - continue; - mProgressColors[type] = std::make_unique<DyePalette>(node.getProperty("color", std::string())); - } - } + mProgressColors[type] = std::make_unique<DyePalette>(node.getProperty("color", std::string())); } diff --git a/src/resources/theme.h b/src/resources/theme.h index 8aba8769..20b94823 100644 --- a/src/resources/theme.h +++ b/src/resources/theme.h @@ -28,37 +28,92 @@ #include "gui/palette.h" #include "resources/image.h" +#include "utils/xml.h" #include <map> #include <memory> #include <string> +#include <variant> + +namespace gcn { +class Widget; +} class DyePalette; class Image; class ImageSet; class ProgressBar; +enum class SkinType +{ + Window, + Popup, + SpeechBubble, + Button, + ButtonUp, + ButtonDown, + ButtonLeft, + ButtonRight, + ButtonClose, + ButtonSticky, + CheckBox, + RadioButton, + TextField, + Tab, + ScrollArea, + ScrollBar, + DropDownFrame, + DropDownButton, + ProgressBar, + Slider, + SliderHandle, + ResizeGrip, +}; + +enum StateFlags : uint8_t +{ + STATE_NORMAL = 0x01, + STATE_HOVERED = 0x02, + STATE_SELECTED = 0x04, + STATE_DISABLED = 0x08, + STATE_FOCUSED = 0x10, +}; + +struct SkinPart +{ + int offsetX = 0; + int offsetY = 0; + std::variant<ImageRect, Image *> data; +}; + +struct SkinState +{ + uint8_t stateFlags = 0; + uint8_t setFlags = 0; + std::vector<SkinPart> parts; +}; + +struct WidgetState +{ + WidgetState() = default; + explicit WidgetState(const gcn::Widget *widget); + + int x = 0; + int y = 0; + int width = 0; + int height = 0; + uint8_t flags = 0; +}; + class Skin { public: - Skin(ImageRect skin, Image *close, Image *stickyUp, Image *stickyDown); + Skin() = default; ~Skin(); - /** - * Returns the background skin. - */ - const ImageRect &getBorder() const { return mBorder; } - - /** - * Returns the image used by a close button for this skin. - */ - Image *getCloseImage() const { return mCloseImage; } + void addState(SkinState state); - /** - * Returns the image used by a sticky button for this skin. - */ - Image *getStickyImage(bool state) const - { return state ? mStickyImageDown.get() : mStickyImageUp.get(); } + void draw(Graphics *graphics, const WidgetState &state) const; /** * Returns the minimum width which can be used with this skin. @@ -75,13 +130,8 @@ class Skin */ void updateAlpha(float alpha); - int instances = 0; - private: - ImageRect mBorder; /**< The window border and background */ - ResourceRef<Image> mCloseImage; /**< Close Button Image */ - std::unique_ptr<Image> mStickyImageUp; /**< Sticky Button Image */ - std::unique_ptr<Image> mStickyImageDown; /**< Sticky Button Image */ + std::vector<SkinState> mStates; }; class Theme : public Palette, public EventListener @@ -99,13 +149,6 @@ class Theme : public Palette, public EventListener std::string resolvePath(const std::string &path) const; static ResourceRef<Image> getImageFromTheme(const std::string &path); - enum ArrowButtonDirection { - ARROW_UP, - ARROW_DOWN, - ARROW_LEFT, - ARROW_RIGHT - }; - enum ThemePalette { TEXT, SHADOW, @@ -122,6 +165,7 @@ class Theme : public Palette, public EventListener SHOP_WARNING, ITEM_EQUIPPED, CHAT, + BUBBLE_TEXT, GM, PLAYER, WHISPER, @@ -175,43 +219,15 @@ class Theme : public Palette, public EventListener static gcn::Color getProgressColor(int type, float progress); - /** - * Loads a skin. - */ - Skin *load(const std::string &filename); - - struct WidgetState - { - int width = 0; - int height = 0; - bool enabled = true; - bool hovered = false; - bool selected = false; - bool focused = false; - }; - - void drawButton(Graphics *graphics, const WidgetState &state) const; - void drawTextFieldFrame(Graphics *graphics, const WidgetState &state) const; - void drawTab(Graphics *graphics, const WidgetState &state) const; - void drawCheckBox(gcn::Graphics *graphics, const WidgetState &state) const; - void drawRadioButton(gcn::Graphics *graphics, const WidgetState &state) const; - void drawSlider(Graphics *graphics, const WidgetState &state, int markerPosition) const; - void drawDropDownFrame(Graphics *graphics, const WidgetState &state) const; - void drawDropDownButton(Graphics *graphics, const WidgetState &state) const; + void drawSkin(Graphics *graphics, SkinType type, const WidgetState &state) const; void drawProgressBar(Graphics *graphics, const gcn::Rectangle &area, const gcn::Color &color, float progress, const std::string &text = std::string()) const; - void drawScrollAreaFrame(Graphics *graphics, const WidgetState &state) const; - void drawScrollAreaButton(Graphics *graphics, - ArrowButtonDirection dir, - bool pressed, - const gcn::Rectangle &dim) const; - void drawScrollAreaMarker(Graphics *graphics, bool hovered, const gcn::Rectangle &dim) const; - int getSliderMarkerLength() const; - const Image *getResizeGripImage() const { return mResizeGripImage; } + int getMinWidth(SkinType skinType) const; + int getMinHeight(SkinType skinType) const; /** * Get the current GUI alpha value. @@ -237,16 +253,17 @@ class Theme : public Palette, public EventListener */ void updateAlpha(); - std::unique_ptr<Skin> readSkin(const std::string &filename) const; - ResourceRef<Image> getImage(const std::string &path) const; - // Map containing all window skins - std::map<std::string, std::unique_ptr<Skin>> mSkins; + bool readTheme(const std::string &filename); + void readSkinNode(XML::Node node); + void readSkinStateNode(XML::Node node, Skin &skin) const; + void readSkinStateImgNode(XML::Node node, SkinState &state) const; + void readColorNode(XML::Node node); + void readProgressBarNode(XML::Node node); std::string mThemePath; - - void loadColors(); + std::map<SkinType, Skin> mSkins; /** * Tells if the current skins opacity @@ -256,33 +273,4 @@ class Theme : public Palette, public EventListener float mAlpha = 1.0; std::vector<std::unique_ptr<DyePalette>> mProgressColors; - - ImageRect mButton[4]; /**< Button state graphics */ - ImageRect mTabImg[4]; /**< Tab state graphics */ - ImageRect mDeepBoxImageRect; - - std::unique_ptr<Image> mCheckBoxNormal; - std::unique_ptr<Image> mCheckBoxChecked; - std::unique_ptr<Image> mCheckBoxDisabled; - std::unique_ptr<Image> mCheckBoxDisabledChecked; - std::unique_ptr<Image> mCheckBoxNormalHi; - std::unique_ptr<Image> mCheckBoxCheckedHi; - - ResourceRef<Image> mRadioNormal; - ResourceRef<Image> mRadioChecked; - ResourceRef<Image> mRadioDisabled; - ResourceRef<Image> mRadioDisabledChecked; - ResourceRef<Image> mRadioNormalHi; - ResourceRef<Image> mRadioCheckedHi; - - std::unique_ptr<Image> hStart, hMid, hEnd, hGrip; - std::unique_ptr<Image> vStart, vMid, vEnd, vGrip; - std::unique_ptr<Image> hStartHi, hMidHi, hEndHi, hGripHi; - std::unique_ptr<Image> vStartHi, vMidHi, vEndHi, vGripHi; - - ImageRect mScrollBarMarker; - ImageRect mScrollBarMarkerHi; - ResourceRef<Image> mArrowButtons[4][2]; - - ResourceRef<Image> mResizeGripImage; }; diff --git a/src/text.cpp b/src/text.cpp index f45cb706..4698aa87 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -22,53 +22,34 @@ #include "text.h" -#include "configuration.h" #include "textmanager.h" #include "textrenderer.h" #include "gui/gui.h" -#include "resources/image.h" #include "resources/theme.h" #include <guichan/font.hpp> int Text::mInstances = 0; -ImageRect Text::mBubble; -Image *Text::mBubbleArrow; -Text::Text(const std::string &text, int x, int y, +Text::Text(const std::string &text, + int x, + int y, gcn::Graphics::Alignment alignment, - const gcn::Color* color, bool isSpeech, - gcn::Font *font) : - mText(text), - mColor(color), - mIsSpeech(isSpeech) + const gcn::Color *color, + bool isSpeech, + gcn::Font *font) + : mText(text) + , mColor(color) + , mFont(font ? font : gui->getFont()) + , mIsSpeech(isSpeech) { - if (!font) - mFont = gui->getFont(); - else - mFont = font; - if (textManager == nullptr) - { textManager = new TextManager; - auto sbImage = Theme::getImageFromTheme("bubble.png|W:#" - + config.speechBubblecolor); - mBubble.grid[0] = sbImage->getSubImage(0, 0, 5, 5); - mBubble.grid[1] = sbImage->getSubImage(5, 0, 5, 5); - mBubble.grid[2] = sbImage->getSubImage(10, 0, 5, 5); - mBubble.grid[3] = sbImage->getSubImage(0, 5, 5, 5); - mBubble.grid[4] = sbImage->getSubImage(5, 5, 5, 5); - mBubble.grid[5] = sbImage->getSubImage(10, 5, 5, 5); - mBubble.grid[6] = sbImage->getSubImage(0, 10, 5, 5); - mBubble.grid[7] = sbImage->getSubImage(5, 10, 5, 5); - mBubble.grid[8] = sbImage->getSubImage(10, 10, 5, 5); - mBubbleArrow = sbImage->getSubImage(0, 15, 15, 10); - mBubble.setAlpha(config.speechBubbleAlpha); - mBubbleArrow->setAlpha(config.speechBubbleAlpha); - } + ++mInstances; + mHeight = mFont->getHeight(); mWidth = mFont->getWidth(text); @@ -84,8 +65,10 @@ Text::Text(const std::string &text, int x, int y, mXOffset = mWidth; break; } + mX = x - mXOffset; mY = y; + textManager->addText(this); } @@ -96,12 +79,6 @@ Text::~Text() { delete textManager; textManager = nullptr; - for (auto &img : mBubble.grid) - { - delete img; - img = nullptr; - } - delete mBubbleArrow; } } @@ -119,9 +96,15 @@ void Text::draw(gcn::Graphics *graphics, int xOff, int yOff) { if (mIsSpeech) { - static_cast<Graphics*>(graphics)->drawImageRect( - mX - xOff - 5, mY - yOff - 5, mWidth + 10, mHeight + 10, - mBubble); + WidgetState state; + state.x = mX - xOff - 5; + state.y = mY - yOff - 5; + state.width = mWidth + 10; + state.height = mHeight + 10; + + auto theme = gui->getTheme(); + theme->drawSkin(static_cast<Graphics *>(graphics), SkinType::SpeechBubble, state); + /* if (mWidth >= 15) { @@ -22,11 +22,11 @@ #pragma once -#include "graphics.h" - #include "utils/time.h" #include <guichan/color.hpp> +#include <guichan/font.hpp> +#include <guichan/graphics.hpp> class TextManager; @@ -74,10 +74,6 @@ class Text const gcn::Color *mColor; /**< The color of the text. */ gcn::Font *mFont; /**< The font of the text */ bool mIsSpeech; /**< Is this text a speech bubble? */ - - protected: - static ImageRect mBubble; /**< Speech bubble graphic */ - static Image *mBubbleArrow; /**< Speech bubble arrow graphic */ }; class FlashText : public Text |