summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-03-31 15:34:22 +0200
committerThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-03-31 16:56:38 +0200
commit49beab3c3415d40d9a1d4326474d16547c4ae9ac (patch)
tree7065a4dcca9089e711eeba2ef4206cbcd955f80c /src
parent83339662e47270f2b38e7430775090f409348ae2 (diff)
downloadmana-49beab3c3415d40d9a1d4326474d16547c4ae9ac.tar.gz
mana-49beab3c3415d40d9a1d4326474d16547c4ae9ac.tar.bz2
mana-49beab3c3415d40d9a1d4326474d16547c4ae9ac.tar.xz
mana-49beab3c3415d40d9a1d4326474d16547c4ae9ac.zip
GUI: Support customizing widget text format through theme
The following widgets now support setting the font, text color, outline color and shadow color through the theme: * Button * Tab * Window (title) * ProgressBar The text format can be specified differently per skin state.
Diffstat (limited to 'src')
-rw-r--r--src/gui/widgets/button.cpp48
-rw-r--r--src/gui/widgets/label.cpp35
-rw-r--r--src/gui/widgets/label.h27
-rw-r--r--src/gui/widgets/tab.cpp33
-rw-r--r--src/gui/widgets/tab.h2
-rw-r--r--src/gui/widgets/window.cpp32
-rw-r--r--src/resources/theme.cpp126
-rw-r--r--src/resources/theme.h16
-rw-r--r--src/textrenderer.h96
9 files changed, 291 insertions, 124 deletions
diff --git a/src/gui/widgets/button.cpp b/src/gui/widgets/button.cpp
index 31fab2c4..31c3a677 100644
--- a/src/gui/widgets/button.cpp
+++ b/src/gui/widgets/button.cpp
@@ -28,6 +28,7 @@
#include "resources/image.h"
#include "resources/theme.h"
+#include "textrenderer.h"
#include <guichan/exception.hpp>
#include <guichan/font.hpp>
@@ -125,33 +126,32 @@ void Button::init()
void Button::draw(gcn::Graphics *graphics)
{
- WidgetState state(this);
+ WidgetState widgetState(this);
if (mHasMouse)
- state.flags |= STATE_HOVERED;
+ widgetState.flags |= STATE_HOVERED;
if (isPressed())
- state.flags |= STATE_SELECTED;
+ widgetState.flags |= STATE_SELECTED;
- gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::Button, state);
+ auto &skin = gui->getTheme()->getSkin(SkinType::Button);
+ skin.draw(static_cast<Graphics *>(graphics), widgetState);
+
+ auto skinState = skin.getState(widgetState.flags);
+ auto font = (skinState && skinState->textFormat.bold) ? boldFont : getFont();
int mode;
- if (state.flags & STATE_DISABLED)
+ if (widgetState.flags & STATE_DISABLED)
mode = BUTTON_DISABLED;
- else if (state.flags & STATE_SELECTED)
+ else if (widgetState.flags & STATE_SELECTED)
mode = BUTTON_PRESSED;
- else if (state.flags & (STATE_HOVERED | STATE_FOCUSED))
+ else if (widgetState.flags & (STATE_HOVERED | STATE_FOCUSED))
mode = BUTTON_HIGHLIGHTED;
else
mode = BUTTON_STANDARD;
- if (mode == BUTTON_DISABLED)
- graphics->setColor(Theme::getThemeColor(Theme::BUTTON_DISABLED));
- else
- graphics->setColor(Theme::getThemeColor(Theme::BUTTON));
-
Image *icon = mButtonIcon.empty() ? nullptr : mButtonIcon[mode].get();
int textX = 0;
- int textY = getHeight() / 2 - getFont()->getHeight() / 2;
+ int textY = getHeight() / 2 - font->getHeight() / 2;
int btnIconX = 0;
int btnIconY = getHeight() / 2 - (icon ? icon->getHeight() / 2 : 0);
int btnIconWidth = icon ? icon->getWidth() : 0;
@@ -172,9 +172,8 @@ void Button::draw(gcn::Graphics *graphics)
case gcn::Graphics::CENTER:
if (btnIconWidth)
{
- btnIconX = getWidth() / 2 - (getFont()->getWidth(mCaption)
- + icon->getWidth() + 2) / 2;
- textX = getWidth() / 2 + icon->getWidth() / 2 + 2;
+ btnIconX = (getWidth() - font->getWidth(mCaption) - icon->getWidth() - 2) / 2;
+ textX = (getWidth() + icon->getWidth()) / 2 + 2;
}
else
{
@@ -183,15 +182,13 @@ void Button::draw(gcn::Graphics *graphics)
break;
case gcn::Graphics::RIGHT:
if (btnIconWidth)
- btnIconX = getWidth() - 4 - getFont()->getWidth(mCaption) - 2;
+ btnIconX = getWidth() - 4 - font->getWidth(mCaption) - 2;
textX = getWidth() - 4;
break;
default:
throw GCN_EXCEPTION("Button::draw(). Unknown alignment.");
}
- graphics->setFont(getFont());
-
if (isPressed())
{
textX++; textY++;
@@ -200,7 +197,18 @@ void Button::draw(gcn::Graphics *graphics)
if (btnIconWidth)
static_cast<Graphics *>(graphics)->drawImage(icon, btnIconX, btnIconY);
- graphics->drawText(getCaption(), textX, textY, getAlignment());
+
+ if (auto skinState = skin.getState(widgetState.flags))
+ {
+ auto &textFormat = skinState->textFormat;
+ TextRenderer::renderText(static_cast<Graphics *>(graphics),
+ getCaption(),
+ textX,
+ textY,
+ getAlignment(),
+ font,
+ textFormat);
+ }
}
void Button::adjustSize()
diff --git a/src/gui/widgets/label.cpp b/src/gui/widgets/label.cpp
index af5220ef..53a82e14 100644
--- a/src/gui/widgets/label.cpp
+++ b/src/gui/widgets/label.cpp
@@ -21,8 +21,13 @@
#include "gui/widgets/label.h"
+#include "textrenderer.h"
+
#include "resources/theme.h"
+#include <guichan/exception.hpp>
+#include <guichan/font.hpp>
+
Label::Label()
{
setForegroundColor(Theme::getThemeColor(Theme::TEXT));
@@ -36,5 +41,33 @@ Label::Label(const std::string &caption) :
void Label::draw(gcn::Graphics *graphics)
{
- gcn::Label::draw(static_cast<gcn::Graphics*>(graphics));
+ int textX;
+ int textY = getHeight() / 2 - getFont()->getHeight() / 2;
+
+ switch (getAlignment())
+ {
+ case Graphics::LEFT:
+ textX = 0;
+ break;
+ case Graphics::CENTER:
+ textX = getWidth() / 2;
+ break;
+ case Graphics::RIGHT:
+ textX = getWidth();
+ break;
+ default:
+ throw GCN_EXCEPTION("Unknown alignment.");
+ }
+
+ TextRenderer::renderText(static_cast<Graphics *>(graphics),
+ getCaption(),
+ textX,
+ textY,
+ getAlignment(),
+ getForegroundColor(),
+ getFont(),
+ mOutlineColor.has_value(),
+ mShadowColor.has_value(),
+ mOutlineColor,
+ mShadowColor);
}
diff --git a/src/gui/widgets/label.h b/src/gui/widgets/label.h
index a383517f..85bcbe23 100644
--- a/src/gui/widgets/label.h
+++ b/src/gui/widgets/label.h
@@ -22,10 +22,11 @@
#pragma once
#include <guichan/widgets/label.hpp>
+#include <optional>
/**
* Label widget. Same as the Guichan label but modified to use the palette
- * system.
+ * system and support outlines and shadows.
*
* \ingroup GUI
*/
@@ -41,7 +42,31 @@ class Label : public gcn::Label
Label(const std::string &caption);
/**
+ * Sets the color of the outline.
+ */
+ void setOutlineColor(std::optional<gcn::Color> color);
+
+ /**
+ * Sets the color of the shadow.
+ */
+ void setShadowColor(std::optional<gcn::Color> color);
+
+ /**
* Draws the label.
*/
void draw(gcn::Graphics *graphics) override;
+
+ private:
+ std::optional<gcn::Color> mOutlineColor;
+ std::optional<gcn::Color> mShadowColor;
};
+
+inline void Label::setOutlineColor(std::optional<gcn::Color> color)
+{
+ mOutlineColor = color;
+}
+
+inline void Label::setShadowColor(std::optional<gcn::Color> color)
+{
+ mShadowColor = color;
+}
diff --git a/src/gui/widgets/tab.cpp b/src/gui/widgets/tab.cpp
index 3bfa405f..b2779c4f 100644
--- a/src/gui/widgets/tab.cpp
+++ b/src/gui/widgets/tab.cpp
@@ -24,17 +24,22 @@
#include "graphics.h"
#include "gui/gui.h"
+#include "gui/widgets/label.h"
#include "gui/widgets/tabbedarea.h"
#include "resources/theme.h"
#include <guichan/widgets/label.hpp>
-Tab::Tab() :
- mTabColor(&Theme::getThemeColor(Theme::TAB))
+Tab::Tab()
{
setFocusable(false);
+ // Replace the label with customized version
+ delete mLabel;
+ mLabel = new Label();
+ add(mLabel);
+
auto &skin = gui->getTheme()->getSkin(SkinType::Tab);
setFrameSize(skin.frameSize);
mPadding = skin.padding;
@@ -62,10 +67,26 @@ void Tab::draw(gcn::Graphics *graphics)
if (mTabbedArea && mTabbedArea->isTabSelected(this))
mFlash = false;
- if (mFlash)
- mLabel->setForegroundColor(Theme::getThemeColor(Theme::TAB_FLASH));
- else
- mLabel->setForegroundColor(*mTabColor);
+ uint8_t flags = 0;
+ if (mHasMouse)
+ flags |= STATE_HOVERED;
+ if (mTabbedArea && mTabbedArea->isTabSelected(this))
+ flags |= STATE_SELECTED;
+
+ auto &skin = gui->getTheme()->getSkin(SkinType::Tab);
+ if (auto state = skin.getState(flags))
+ {
+ gcn::Color foregroundColor = state->textFormat.color;
+ if (mFlash)
+ foregroundColor = Theme::getThemeColor(Theme::TAB_FLASH);
+ else if (mTabColor)
+ foregroundColor = *mTabColor;
+
+ auto label = static_cast<Label*>(mLabel);
+ label->setForegroundColor(foregroundColor);
+ label->setOutlineColor(state->textFormat.outlineColor);
+ label->setShadowColor(state->textFormat.shadowColor);
+ }
// draw label
drawChildren(graphics);
diff --git a/src/gui/widgets/tab.h b/src/gui/widgets/tab.h
index d67321c6..534abaff 100644
--- a/src/gui/widgets/tab.h
+++ b/src/gui/widgets/tab.h
@@ -66,7 +66,7 @@ class Tab : public gcn::Tab
virtual void setCurrent() {}
private:
- const gcn::Color *mTabColor;
+ const gcn::Color *mTabColor = nullptr;
bool mFlash = false;
int mPadding = 8;
};
diff --git a/src/gui/widgets/window.cpp b/src/gui/widgets/window.cpp
index 15267019..4a0b8286 100644
--- a/src/gui/widgets/window.cpp
+++ b/src/gui/widgets/window.cpp
@@ -23,6 +23,7 @@
#include "configuration.h"
#include "log.h"
+#include "textrenderer.h"
#include "gui/gui.h"
#include "gui/viewport.h"
@@ -124,21 +125,26 @@ void Window::drawFrame(gcn::Graphics *graphics)
auto g = static_cast<Graphics*>(graphics);
auto theme = gui->getTheme();
- WidgetState state(this);
- state.width += getFrameSize() * 2;
- state.height += getFrameSize() * 2;
+ WidgetState widgetState(this);
+ widgetState.width += getFrameSize() * 2;
+ widgetState.height += getFrameSize() * 2;
auto &skin = theme->getSkin(SkinType::Window);
- skin.draw(g, state);
+ skin.draw(g, widgetState);
if (mShowTitle)
{
- g->setColor(Theme::getThemeColor(Theme::TEXT));
- g->setFont(getFont());
- g->drawText(getCaption(),
- getFrameSize() + skin.titleOffsetX,
- getFrameSize() + skin.titleOffsetY,
- gcn::Graphics::LEFT);
+ if (auto skinState = skin.getState(widgetState.flags))
+ {
+ auto &textFormat = skinState->textFormat;
+ TextRenderer::renderText(g,
+ getCaption(),
+ getFrameSize() + skin.titleOffsetX,
+ getFrameSize() + skin.titleOffsetY,
+ gcn::Graphics::LEFT,
+ textFormat.bold ? boldFont : getFont(),
+ textFormat);
+ }
}
}
@@ -686,6 +692,9 @@ int Window::getResizeHandles(gcn::MouseEvent &event)
gcn::Rectangle Window::getCloseButtonRect() const
{
+ if (!mCloseButton)
+ return {};
+
auto theme = gui->getTheme();
auto &closeButtonSkin = theme->getSkin(SkinType::ButtonClose);
@@ -702,6 +711,9 @@ gcn::Rectangle Window::getCloseButtonRect() const
gcn::Rectangle Window::getStickyButtonRect() const
{
+ if (!mStickyButton)
+ return {};
+
auto theme = gui->getTheme();
auto &closeButtonSkin = theme->getSkin(SkinType::ButtonClose);
diff --git a/src/resources/theme.cpp b/src/resources/theme.cpp
index 01c6b6f1..9dd602c7 100644
--- a/src/resources/theme.cpp
+++ b/src/resources/theme.cpp
@@ -108,44 +108,50 @@ void Skin::addState(SkinState state)
void Skin::draw(Graphics *graphics, const WidgetState &state) const
{
- for (const auto &skinState : mStates)
- {
- if (skinState.stateFlags != (skinState.setFlags & state.flags))
- continue;
+ // Only draw the first matching state
+ auto skinState = getState(state.flags);
+ if (!skinState)
+ return;
- for (const auto &part : skinState.parts)
- {
- 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);
- }
- else if constexpr (std::is_same_v<T, ColoredRectangle>)
- {
- graphics->setColor(data.color);
- graphics->fillRectangle(gcn::Rectangle(state.x + part.offsetX,
- state.y + part.offsetY,
- state.width,
- state.height));
- graphics->setColor(gcn::Color(255, 255, 255));
- }
- }, part.data);
- }
+ for (const auto &part : skinState->parts)
+ {
+ std::visit([&](const auto &data) {
+ using T = std::decay_t<decltype(data)>;
- break; // Only draw the first matching state
+ 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);
+ }
+ else if constexpr (std::is_same_v<T, ColoredRectangle>)
+ {
+ graphics->setColor(data.color);
+ graphics->fillRectangle(gcn::Rectangle(state.x + part.offsetX,
+ state.y + part.offsetY,
+ state.width,
+ state.height));
+ graphics->setColor(gcn::Color(255, 255, 255));
+ }
+ }, part.data);
}
}
+const SkinState *Skin::getState(uint8_t flags) const
+{
+ for (const auto &skinState : mStates)
+ if (skinState.stateFlags == (skinState.setFlags & flags))
+ return &skinState;
+
+ return nullptr;
+}
+
int Skin::getMinWidth() const
{
int minWidth = 0;
@@ -295,13 +301,14 @@ void Theme::drawProgressBar(Graphics *graphics, const gcn::Rectangle &area,
gcn::Font *oldFont = graphics->getFont();
gcn::Color oldColor = graphics->getColor();
- WidgetState state;
- state.x = area.x;
- state.y = area.y;
- state.width = area.width;
- state.height = area.height;
+ WidgetState widgetState;
+ widgetState.x = area.x;
+ widgetState.y = area.y;
+ widgetState.width = area.width;
+ widgetState.height = area.height;
- drawSkin(graphics, SkinType::ProgressBar, state);
+ auto &skin = getSkin(SkinType::ProgressBar);
+ skin.draw(graphics, widgetState);
// The bar
if (progress > 0)
@@ -316,18 +323,20 @@ void Theme::drawProgressBar(Graphics *graphics, const gcn::Rectangle &area,
// The label
if (!text.empty())
{
- const int textX = area.x + area.width / 2;
- const int textY = area.y + (area.height - boldFont->getHeight()) / 2;
-
- TextRenderer::renderText(graphics,
- text,
- textX,
- textY,
- gcn::Graphics::CENTER,
- Theme::getThemeColor(Theme::PROGRESS_BAR),
- gui->getFont(),
- true,
- false);
+ if (auto skinState = skin.getState(widgetState.flags))
+ {
+ auto font = skinState->textFormat.bold ? boldFont : gui->getFont();
+ const int textX = area.x + area.width / 2;
+ const int textY = area.y + (area.height - font->getHeight()) / 2;
+
+ TextRenderer::renderText(graphics,
+ text,
+ textX,
+ textY,
+ gcn::Graphics::CENTER,
+ font,
+ skinState->textFormat);
+ }
}
graphics->setFont(oldFont);
@@ -500,11 +509,22 @@ void Theme::readSkinStateNode(XML::Node node, Skin &skin) const
readSkinStateImgNode(childNode, state);
else if (childNode.name() == "rect")
readSkinStateRectNode(childNode, state);
+ else if (childNode.name() == "text")
+ readSkinStateTextNode(childNode, state);
}
skin.addState(std::move(state));
}
+void Theme::readSkinStateTextNode(XML::Node node, SkinState &state) const
+{
+ auto &textFormat = state.textFormat;
+ node.attribute("bold", textFormat.bold);
+ node.attribute("color", textFormat.color);
+ node.attribute("outlineColor", textFormat.outlineColor);
+ node.attribute("shadowColor", textFormat.shadowColor);
+}
+
template<>
inline void fromString(const char *str, FillMode &value)
{
@@ -631,10 +651,6 @@ static int readColorType(const std::string &type)
"TEXT",
"SHADOW",
"OUTLINE",
- "PROGRESS_BAR",
- "BUTTON",
- "BUTTON_DISABLED",
- "TAB",
"PARTY_CHAT_TAB",
"PARTY_SOCIAL_TAB",
"BACKGROUND",
diff --git a/src/resources/theme.h b/src/resources/theme.h
index 55092127..4f3535b4 100644
--- a/src/resources/theme.h
+++ b/src/resources/theme.h
@@ -95,10 +95,19 @@ struct SkinPart
std::variant<ImageRect, Image *, ColoredRectangle> data;
};
+struct TextFormat
+{
+ bool bold = false;
+ gcn::Color color;
+ std::optional<gcn::Color> outlineColor;
+ std::optional<gcn::Color> shadowColor;
+};
+
struct SkinState
{
uint8_t stateFlags = 0;
uint8_t setFlags = 0;
+ TextFormat textFormat;
std::vector<SkinPart> parts;
};
@@ -125,6 +134,8 @@ class Skin
void draw(Graphics *graphics, const WidgetState &state) const;
+ const SkinState *getState(uint8_t flags) const;
+
/**
* Returns the minimum width which can be used with this skin.
*/
@@ -169,10 +180,6 @@ class Theme : public Palette, public EventListener
TEXT,
SHADOW,
OUTLINE,
- PROGRESS_BAR,
- BUTTON,
- BUTTON_DISABLED,
- TAB,
PARTY_CHAT_TAB,
PARTY_SOCIAL_TAB,
BACKGROUND,
@@ -276,6 +283,7 @@ class Theme : public Palette, public EventListener
bool readTheme(const std::string &filename);
void readSkinNode(XML::Node node);
void readSkinStateNode(XML::Node node, Skin &skin) const;
+ void readSkinStateTextNode(XML::Node node, SkinState &state) const;
void readSkinStateImgNode(XML::Node node, SkinState &state) const;
void readSkinStateRectNode(XML::Node node, SkinState &state) const;
void readColorNode(XML::Node node);
diff --git a/src/textrenderer.h b/src/textrenderer.h
index a646b2f5..c9cd9310 100644
--- a/src/textrenderer.h
+++ b/src/textrenderer.h
@@ -21,13 +21,13 @@
#pragma once
-#include "graphics.h"
-
#include "resources/theme.h"
+#include <guichan/exception.hpp>
+#include <guichan/font.hpp>
+
/**
- * Class for text rendering. Used by the TextParticle, the Text and FlashText
- * objects and the Preview in the color dialog.
+ * Class for text rendering which can apply an outline and shadow.
*/
class TextRenderer
{
@@ -38,46 +38,90 @@ public:
static void renderText(gcn::Graphics *graphics,
const std::string &text,
int x, int y,
- gcn::Graphics::Alignment align,
+ gcn::Graphics::Alignment alignment,
const gcn::Color &color,
gcn::Font *font,
bool outline = false,
- bool shadow = false)
+ bool shadow = false,
+ const std::optional<gcn::Color> &outlineColor = {},
+ const std::optional<gcn::Color> &shadowColor = {})
{
- graphics->setFont(font);
+ switch (alignment)
+ {
+ case gcn::Graphics::LEFT:
+ break;
+ case gcn::Graphics::CENTER:
+ x -= font->getWidth(text) / 2;
+ break;
+ case gcn::Graphics::RIGHT:
+ x -= font->getWidth(text);
+ break;
+ default:
+ throw GCN_EXCEPTION("Unknown alignment.");
+ }
// Text shadow
if (shadow)
{
- graphics->setColor(Theme::getThemeColor(Theme::SHADOW,
- color.a / 2));
+ if (shadowColor)
+ graphics->setColor(*shadowColor);
+ else
+ graphics->setColor(Theme::getThemeColor(Theme::SHADOW, color.a / 2));
+
if (outline)
- {
- graphics->drawText(text, x + 2, y + 2, align);
- }
+ font->drawString(graphics, text, x + 2, y + 2);
else
- {
- graphics->drawText(text, x + 1, y + 1, align);
- }
+ font->drawString(graphics, text, x + 1, y + 1);
}
- if (outline) {
-/* graphics->setColor(guiPalette->getColor(Palette::OUTLINE,
+ if (outline)
+ {
+ /*
+ graphics->setColor(guiPalette->getColor(Palette::OUTLINE,
alpha/4));
// TODO: Reanable when we can draw it nicely in software mode
- graphics->drawText(text, x + 2, y + 2, align);
- graphics->drawText(text, x + 1, y + 2, align);
- graphics->drawText(text, x + 2, y + 1, align);*/
+ font->drawString(graphics, text, x + 2, y + 2);
+ font->drawString(graphics, text, x + 1, y + 2);
+ font->drawString(graphics, text, x + 2, y + 1);
+ */
// Text outline
- graphics->setColor(Theme::getThemeColor(Theme::OUTLINE, color.a));
- graphics->drawText(text, x + 1, y, align);
- graphics->drawText(text, x - 1, y, align);
- graphics->drawText(text, x, y + 1, align);
- graphics->drawText(text, x, y - 1, align);
+ if (outlineColor)
+ graphics->setColor(*outlineColor);
+ else
+ graphics->setColor(Theme::getThemeColor(Theme::OUTLINE, color.a));
+
+ font->drawString(graphics, text, x + 1, y);
+ font->drawString(graphics, text, x - 1, y);
+ font->drawString(graphics, text, x, y + 1);
+ font->drawString(graphics, text, x, y - 1);
}
graphics->setColor(color);
- graphics->drawText(text, x, y, align);
+ font->drawString(graphics, text, x, y);
+ }
+
+ /**
+ * Renders a specified text.
+ */
+ static void renderText(gcn::Graphics *graphics,
+ const std::string &text,
+ int x,
+ int y,
+ gcn::Graphics::Alignment align,
+ gcn::Font *font,
+ const TextFormat &format)
+ {
+ renderText(graphics,
+ text,
+ x,
+ y,
+ align,
+ format.color,
+ font,
+ format.outlineColor.has_value(),
+ format.shadowColor.has_value(),
+ format.outlineColor,
+ format.shadowColor);
}
};