summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-03-17 10:39:38 +0100
committerThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-03-24 13:47:11 +0100
commit5274cc92c1055a3209dfae7e5346bfe52c35e4a8 (patch)
tree8bb9ceab97fb752a07294a52dcf2d390e79616fe /src
parentd5f49e4bef99e2ae4b39bdf1b7c644c28a85c5a2 (diff)
downloadmana-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.cpp4
-rw-r--r--src/graphics.cpp11
-rw-r--r--src/graphics.h2
-rw-r--r--src/gui/minimap.cpp3
-rw-r--r--src/gui/speechbubble.cpp13
-rw-r--r--src/gui/widgets/button.cpp20
-rw-r--r--src/gui/widgets/checkbox.cpp11
-rw-r--r--src/gui/widgets/dropdown.cpp32
-rw-r--r--src/gui/widgets/popup.cpp25
-rw-r--r--src/gui/widgets/popup.h23
-rw-r--r--src/gui/widgets/radiobutton.cpp11
-rw-r--r--src/gui/widgets/resizegrip.cpp13
-rw-r--r--src/gui/widgets/scrollarea.cpp76
-rw-r--r--src/gui/widgets/scrollarea.h8
-rw-r--r--src/gui/widgets/slider.cpp16
-rw-r--r--src/gui/widgets/tab.cpp16
-rw-r--r--src/gui/widgets/textfield.cpp8
-rw-r--r--src/gui/widgets/window.cpp81
-rw-r--r--src/gui/widgets/window.h5
-rw-r--r--src/resources/theme.cpp918
-rw-r--r--src/resources/theme.h176
-rw-r--r--src/text.cpp63
-rw-r--r--src/text.h8
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)
{
diff --git a/src/text.h b/src/text.h
index c4cb3954..b71dde37 100644
--- a/src/text.h
+++ b/src/text.h
@@ -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