osb/source/windowing/StarWidgetParsing.cpp
Kae 888cde79ef feat: middle click objects in the inventory to open their interface
makes carrying around shop objects easier

might need to restrict the allowed interaction types more, as some may break due to the source entity being the player
2024-03-27 16:00:13 +11:00

830 lines
33 KiB
C++

#include "StarWidgetParsing.hpp"
#include "StarRoot.hpp"
#include "StarJsonExtra.hpp"
#include "StarImageMetadataDatabase.hpp"
#include "StarPane.hpp"
#include "StarButtonGroup.hpp"
#include "StarButtonWidget.hpp"
#include "StarCanvasWidget.hpp"
#include "StarImageWidget.hpp"
#include "StarImageStretchWidget.hpp"
#include "StarPortraitWidget.hpp"
#include "StarFuelWidget.hpp"
#include "StarProgressWidget.hpp"
#include "StarLargeCharPlateWidget.hpp"
#include "StarTextBoxWidget.hpp"
#include "StarItemSlotWidget.hpp"
#include "StarItemGridWidget.hpp"
#include "StarListWidget.hpp"
#include "StarSliderBar.hpp"
#include "StarStackWidget.hpp"
#include "StarScrollArea.hpp"
#include "StarAssets.hpp"
#include "StarFlowLayout.hpp"
#include "StarVerticalLayout.hpp"
#include "StarTabSet.hpp"
namespace Star {
WidgetConstructResult::WidgetConstructResult() : zlevel() {}
WidgetConstructResult::WidgetConstructResult(WidgetPtr obj, String const& name, float zlevel)
: obj(obj), name(name), zlevel(zlevel) {}
WidgetParser::WidgetParser() {
// only the non-interactive ones by default
m_constructors["widget"] = [=](String const& name, Json const& config) { return widgetHandler(name, config); };
m_constructors["canvas"] = [=](String const& name, Json const& config) { return canvasHandler(name, config); };
m_constructors["image"] = [=](String const& name, Json const& config) { return imageHandler(name, config); };
m_constructors["imageStretch"] = [=](String const& name, Json const& config) { return imageStretchHandler(name, config); };
m_constructors["label"] = [=](String const& name, Json const& config) { return labelHandler(name, config); };
m_constructors["portrait"] = [=](String const& name, Json const& config) { return portraitHandler(name, config); };
m_constructors["fuelGauge"] = [=](String const& name, Json const& config) { return fuelGaugeHandler(name, config); };
m_constructors["progress"] = [=](String const& name, Json const& config) { return progressHandler(name, config); };
m_constructors["largeCharPlate"] = [=](
String const& name, Json const& config) { return largeCharPlateHandler(name, config); };
m_constructors["container"] = [=](String const& name, Json const& config) { return containerHandler(name, config); };
m_constructors["layout"] = [=](String const& name, Json const& config) { return layoutHandler(name, config); };
m_callbacks["null"] = [](Widget*) {};
}
void WidgetParser::construct(Json const& config, Widget* widget) {
m_pane = dynamic_cast<Pane*>(widget);
constructImpl(config, widget);
}
void WidgetParser::constructImpl(Json const& config, Widget* widget) {
List<WidgetConstructResult> widgets = constructor(config);
sort(widgets,
[&](WidgetConstructResult const& a, WidgetConstructResult const& b) -> bool {
if (a.zlevel == b.zlevel)
return a.obj->position() < b.obj->position();
return a.zlevel < b.zlevel;
});
for (auto const& res : widgets) {
widget->addChild(res.name, res.obj);
if (res.obj->hasFocus()) {
if (m_pane)
m_pane->setFocus(res.obj.get());
}
}
}
WidgetPtr WidgetParser::makeSingle(String const& name, Json const& config) {
if (!m_constructors.contains(config.getString("type"))) {
throw WidgetParserException(strf("Unknown type in gui json. {}", config.getString("type")));
}
auto constructResult = m_constructors.get(config.getString("type"))(name, config);
return constructResult.obj;
}
List<WidgetConstructResult> WidgetParser::constructor(Json const& config) {
List<WidgetConstructResult> widgets;
auto addWidget = [this, &widgets](Json const& memberConfig) {
if (memberConfig.type() != Json::Type::Object || !memberConfig.contains("type") || !memberConfig.contains("name"))
throw WidgetParserException(
"Malformed gui json: member configuration is either not a map, or does not specify a widget name and type");
String type = memberConfig.getString("type");
if (type == "include") {
widgets.appendAll(constructor(Root::singleton().assets()->json(memberConfig.getString("file"))));
} else {
if (!m_constructors.contains(type)) {
throw WidgetParserException(strf("Unknown type in gui json. {}", type));
}
auto constructResult =
m_constructors.get(memberConfig.getString("type"))(memberConfig.getString("name"), memberConfig);
if (constructResult.obj)
widgets.append(constructResult);
}
};
if (config.isType(Json::Type::Object)) {
for (auto const& kvpair : config.iterateObject())
addWidget(kvpair.second.set("name", kvpair.first));
} else if (config.isType(Json::Type::Array)) {
for (auto const& elem : config.iterateArray())
addWidget(elem);
} else {
throw WidgetParserException(strf("Malformed gui json, expected a Map or a List. Instead got {}", config));
}
return widgets;
}
void WidgetParser::registerCallback(String const& name, WidgetCallbackFunc callback) {
m_callbacks[name] = callback;
}
WidgetConstructResult WidgetParser::buttonHandler(String const& name, Json const& config) {
String baseImage;
bool invisible = config.getBool("invisible", false);
try {
if (!invisible)
baseImage = config.getString("base");
} catch (MapException const& e) {
throw WidgetParserException(
strf("Malformed gui json, missing a required value in the map. {}", outputException(e, false)));
}
String hoverImage = config.getString("hover", "");
String pressedImage = config.getString("pressed", "");
String disabledImage = config.getString("disabledImage", "");
String baseImageChecked = config.getString("baseImageChecked", "");
String hoverImageChecked = config.getString("hoverImageChecked", "");
String pressedImageChecked = config.getString("pressedImageChecked", "");
String disabledImageChecked = config.getString("disabledImageChecked", "");
String callback = config.getString("callback", name);
if (!m_callbacks.contains(callback))
throw WidgetParserException::format("Failed to find callback named: {}", callback);
WidgetCallbackFunc callbackFunc = m_callbacks.get(callback);
auto button = make_shared<ButtonWidget>(callbackFunc, baseImage, hoverImage, pressedImage, disabledImage);
button->setCheckedImages(baseImageChecked, hoverImageChecked, pressedImageChecked, disabledImageChecked);
common(button, config);
button->setInvisible(invisible);
if (config.contains("caption"))
button->setText(config.getString("caption"));
if (config.contains("pressedOffset"))
button->setPressedOffset(jsonToVec2I(config.get("pressedOffset")));
if (config.contains("textOffset"))
button->setTextOffset(jsonToVec2I(config.get("textOffset")));
if (config.contains("checkable"))
button->setCheckable(config.getBool("checkable"));
if (config.contains("checked"))
button->setChecked(config.getBool("checked"));
String hAnchor = config.getString("textAlign", "center");
if (hAnchor == "right") {
button->setTextAlign(HorizontalAnchor::RightAnchor);
} else if (hAnchor == "center") {
button->setTextAlign(HorizontalAnchor::HMidAnchor);
} else if (hAnchor == "left") {
button->setTextAlign(HorizontalAnchor::LeftAnchor);
} else {
throw WidgetParserException(strf(
"Malformed gui json, expected textAlign to be one of \"left\", \"right\", or \"center\", got {}", hAnchor));
}
if (config.contains("fontSize"))
button->setFontSize(config.getInt("fontSize"));
if (config.contains("fontDirectives"))
button->setFontDirectives(config.getString("fontDirectives"));
if (config.contains("fontColor"))
button->setFontColor(jsonToColor(config.get("fontColor")));
if (config.contains("fontColorDisabled"))
button->setFontColorDisabled(jsonToColor(config.get("fontColorDisabled")));
if (config.contains("disabled"))
button->setEnabled(!config.getBool("disabled"));
return WidgetConstructResult(button, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::imageHandler(String const& name, Json const& config) {
auto image = make_shared<ImageWidget>();
common(image, config);
if (config.contains("file"))
image->setImage(config.getString("file"));
if (config.contains("drawables"))
image->setDrawables(config.getArray("drawables").transformed([](Json const& d) { return Drawable(d); } ));
if (config.contains("scale"))
image->setScale(config.getFloat("scale"));
if (config.contains("rotation"))
image->setRotation(config.getFloat("rotation"));
if (config.contains("centered"))
image->setCentered(config.getBool("centered"));
if (config.contains("trim"))
image->setTrim(config.getBool("trim"));
else
image->setTrim(image->centered()); // once upon a time in a magical kingdom, these settings were linked
if (config.contains("offset"))
image->setOffset(jsonToVec2I(config.get("offset")));
if (config.contains("maxSize"))
image->setMaxSize(jsonToVec2I(config.get("maxSize")));
if (config.contains("minSize"))
image->setMinSize(jsonToVec2I(config.get("minSize")));
return WidgetConstructResult(image, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::imageStretchHandler(String const& name, Json const& config) {
ImageStretchSet stretchSet = parseImageStretchSet(config.get("stretchSet"));
GuiDirection direction = GuiDirectionNames.getLeft(config.getString("direction", "horizontal"));
auto imageStretch = make_shared<ImageStretchWidget>(stretchSet, direction);
common(imageStretch, config);
return WidgetConstructResult(imageStretch, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::spinnerHandler(String const& name, Json const& config) {
auto container = make_shared<Widget>();
common(container, config);
String callback = config.getString("callback", name);
if (!m_callbacks.contains(callback + ".up"))
throw WidgetParserException::format("Failed to find spinner callback named: '{}.up'", name);
if (!m_callbacks.contains(callback + ".down"))
throw WidgetParserException::format("Failed to find spinner callback named: '{}.down'", name);
WidgetCallbackFunc callbackDown = m_callbacks.get(callback + ".down");
WidgetCallbackFunc callbackUp = m_callbacks.get(callback + ".up");
auto assets = Root::singleton().assets();
auto imgMetadata = Root::singleton().imageMetadataDatabase();
auto leftBase = assets->json("/interface.config:spinner.leftBase").toString();
auto leftHover = assets->json("/interface.config:spinner.leftHover").toString();
auto rightBase = assets->json("/interface.config:spinner.rightBase").toString();
auto rightHover = assets->json("/interface.config:spinner.rightHover").toString();
auto imageSize = imgMetadata->imageSize(leftBase);
auto padding = assets->json("/interface.config:spinner.defaultPadding").toInt();
float upOffset = config.getFloat("upOffset", (float)imageSize[0] + padding);
auto down = make_shared<ButtonWidget>(
callbackDown, config.getString("leftBase", leftBase), config.getString("leftHover", leftHover));
auto up = make_shared<ButtonWidget>(
callbackUp, config.getString("rightBase", rightBase), config.getString("rightHover", rightHover));
up->setPosition(up->position() + Vec2I(upOffset, 0));
container->addChild("down", down);
container->addChild("up", up);
container->disableScissoring();
container->markAsContainer();
container->determineSizeFromChildren();
return WidgetConstructResult(container, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::radioGroupHandler(String const& name, Json const& config) {
auto buttonGroup = make_shared<ButtonGroupWidget>();
common(buttonGroup, config);
buttonGroup->markAsContainer();
buttonGroup->disableScissoring();
buttonGroup->setToggle(config.getBool("toggleMode", false));
String callback = config.getString("callback", name);
JsonArray buttons;
try {
buttons = config.getArray("buttons");
} catch (MapException const& e) {
throw WidgetParserException(
strf("Malformed gui json, missing a required value in the map. {}", outputException(e, false)));
}
if (!m_callbacks.contains(callback))
throw WidgetParserException::format("Failed to find callback named: '{}'", callback);
String baseImage = config.getString("baseImage", "");
String hoverImage = config.getString("hoverImage", "");
String baseImageChecked = config.getString("baseImageChecked", "");
String hoverImageChecked = config.getString("hoverImageChecked", "");
String pressedImage = config.getString("pressedImage", "");
String disabledImage = config.getString("disabledImage", "");
String pressedImageChecked = config.getString("pressedImageChecked", "");
String disabledImageChecked = config.getString("disabledImageChecked", "");
for (auto btnConfig : buttons) {
try {
auto overlayImage = btnConfig.getString("image", "");
auto id = btnConfig.getInt("id", ButtonGroup::NoButton);
auto button = make_shared<ButtonWidget>();
button->setButtonGroup(buttonGroup, id);
button->setImages(btnConfig.getString("baseImage", baseImage),
btnConfig.getString("hoverImage", hoverImage),
btnConfig.getString("pressedImage", pressedImage),
btnConfig.getString("disabledImage", disabledImage));
button->setCheckedImages(btnConfig.getString("baseImageChecked", baseImageChecked),
btnConfig.getString("hoverImageChecked", hoverImageChecked),
btnConfig.getString("pressedImageChecked", pressedImageChecked),
btnConfig.getString("hoverImageChecked", disabledImageChecked));
button->setOverlayImage(overlayImage);
if (btnConfig.getBool("disabled", false))
button->disable();
if (btnConfig.getBool("selected", false))
button->check();
if (btnConfig.contains("fontSize"))
button->setFontSize(btnConfig.getInt("fontSize"));
if (btnConfig.contains("fontColor"))
button->setFontColor(jsonToColor(btnConfig.get("fontColor")));
if (btnConfig.contains("fontColorChecked"))
button->setFontColorChecked(jsonToColor(btnConfig.get("fontColorChecked")));
if (btnConfig.contains("fontColorDisabled"))
button->setFontColorDisabled(jsonToColor(btnConfig.get("fontColorDisabled")));
if (btnConfig.contains("text"))
button->setText(btnConfig.getString("text"));
if (btnConfig.contains("pressedOffset"))
button->setPressedOffset(jsonToVec2I(btnConfig.get("pressedOffset")));
common(button, btnConfig);
buttonGroup->addChild(toString(button->buttonGroupId()), button);
} catch (MapException const& e) {
throw WidgetParserException(
strf("Malformed gui json, missing a required value in the map. {}", outputException(e, false)));
}
}
// Set callback after all other buttons are loaded, to avoid callbacks being
// called during reading.
buttonGroup->setCallback(m_callbacks.get(callback));
return WidgetConstructResult(buttonGroup, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::portraitHandler(String const& name, Json const& config) {
auto portrait = make_shared<PortraitWidget>();
if (config.contains("portraitMode"))
portrait->setMode(PortraitModeNames.getLeft(config.getString("portraitMode")));
if (config.contains("renderHumanoid"))
portrait->setRenderHumanoid(config.getBool("renderHumanoid"));
portrait->setScale(config.getFloat("scale", 1));
common(portrait, config);
return WidgetConstructResult(portrait, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::textboxHandler(String const& name, Json const& config) {
String callback = config.getString("callback", name);
if (!m_callbacks.contains(callback))
throw WidgetParserException::format("Failed to find textbox callback named: '{}'", name);
WidgetCallbackFunc callbackFunc = m_callbacks.get(callback);
String initialText = config.getString("value", "");
String hintText = config.getString("hint", "");
auto textbox = make_shared<TextBoxWidget>(initialText, hintText, callbackFunc);
if (config.contains("blur"))
textbox->setOnBlurCallback(m_callbacks.get(config.getString("blur")));
if (config.contains("enterKey"))
textbox->setOnEnterKeyCallback(m_callbacks.get(config.getString("enterKey")));
if (config.contains("escapeKey"))
textbox->setOnEscapeKeyCallback(m_callbacks.get(config.getString("escapeKey")));
if (config.contains("nextFocus"))
textbox->setNextFocus(config.getString("nextFocus"));
if (config.contains("prevFocus"))
textbox->setPrevFocus(config.getString("prevFocus"));
String hAnchor = config.getString("textAlign", "left");
if (hAnchor == "right") {
textbox->setTextAlign(HorizontalAnchor::RightAnchor);
} else if (hAnchor == "center") {
textbox->setTextAlign(HorizontalAnchor::HMidAnchor);
} else if (hAnchor != "left") {
throw WidgetParserException(strf(
"Malformed gui json, expected textAlign to be one of \"left\", \"right\", or \"center\", got {}", hAnchor));
}
if (config.contains("fontSize"))
textbox->setFontSize(config.getInt("fontSize"));
if (config.contains("color"))
textbox->setColor(jsonToColor(config.get("color")));
if (config.contains("directives"))
textbox->setDirectives(config.getString("directives"));
if (config.contains("border"))
textbox->setDrawBorder(config.getBool("border"));
if (config.contains("maxWidth"))
textbox->setMaxWidth(config.getInt("maxWidth"));
if (config.contains("regex"))
textbox->setRegex(config.getString("regex"));
if (config.contains("hidden"))
textbox->setHidden(config.getBool("hidden"));
common(textbox, config);
return WidgetConstructResult(textbox, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::labelHandler(String const& name, Json const& config) {
String text = config.getString("value", "");
Color color = Color::White;
if (config.contains("color"))
color = jsonToColor(config.get("color"));
HorizontalAnchor hAnchor = HorizontalAnchorNames.getLeft(config.getString("hAnchor", "left"));
VerticalAnchor vAnchor = VerticalAnchorNames.getLeft(config.getString("vAnchor", "bottom"));
auto label = make_shared<LabelWidget>(text, color, hAnchor, vAnchor);
common(label, config);
if (config.contains("fontSize"))
label->setFontSize(config.getInt("fontSize"));
if (config.contains("wrapWidth"))
label->setWrapWidth(config.getInt("wrapWidth"));
if (config.contains("charLimit"))
label->setTextCharLimit(config.getInt("charLimit"));
if (config.contains("lineSpacing"))
label->setLineSpacing(config.getFloat("lineSpacing"));
if (config.contains("directives"))
label->setDirectives(config.getString("directives"));
return WidgetConstructResult(label, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::itemSlotHandler(String const& name, Json const& config) {
String backingImage = config.getString("backingImage", "");
String callback = config.getString("callback", name);
String rightClickCallback = callback.equals("null") ? callback : callback + ".right";
rightClickCallback = config.getString("rightClickCallback", rightClickCallback);
String middleClickCallback = callback.equals("null") ? callback : callback + ".middle";
middleClickCallback = config.getString("middleClickCallback", middleClickCallback);
auto itemSlot = make_shared<ItemSlotWidget>(ItemPtr(), backingImage);
if (auto leftClickCallback = m_callbacks.ptr(callback))
itemSlot->setCallback(*leftClickCallback);
else
throw WidgetParserException::format("Failed to find ItemSlot callback named: '{}'", callback);
if (auto callback = m_callbacks.ptr(rightClickCallback))
itemSlot->setRightClickCallback(*callback);
else
throw WidgetParserException::format("Failed to find ItemSlot rightClickCallback named: '{}'", rightClickCallback);
if (auto callback = m_callbacks.ptr(middleClickCallback))
itemSlot->setMiddleClickCallback(*callback);
itemSlot->setBackingImageAffinity(config.getBool("showBackingImageWhenFull", false), config.getBool("showBackingImageWhenEmpty", true));
itemSlot->showDurability(config.getBool("showDurability", false));
itemSlot->showCount(config.getBool("showCount", true));
itemSlot->showRarity(config.getBool("showRarity", true));
common(itemSlot, config);
return WidgetConstructResult(itemSlot, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::itemGridHandler(String const& name, Json const& config) {
Vec2I dimensions;
Vec2I rowSpacing;
Vec2I columnSpacing;
try {
dimensions = jsonToVec2I(config.get("dimensions"));
if (config.contains("spacing")) {
auto spacing = jsonToVec2I(config.get("spacing"));
rowSpacing = {spacing[0], 0};
columnSpacing = {0, spacing[1]};
} else {
rowSpacing = jsonToVec2I(config.get("rowSpacing"));
columnSpacing = jsonToVec2I(config.get("columnSpacing"));
}
} catch (MapException const& e) {
throw WidgetParserException::format("Malformed gui json, missing a required value in the map. {}", outputException(e, false));
}
String callback = config.getString("callback", name);
String rightClickCallback = callback.equals("null") ? callback : callback + ".right";
rightClickCallback = config.getString("rightClickCallback", rightClickCallback);
String middleClickCallback = callback.equals("null") ? callback : callback + ".middle";
middleClickCallback = config.getString("middleClickCallback", middleClickCallback);
unsigned slotOffset = config.getInt("slotOffset", 0);
String backingImage = config.getString("backingImage", "");
auto itemGrid = make_shared<ItemGridWidget>(ItemBagConstPtr(), dimensions, rowSpacing, columnSpacing, backingImage, slotOffset);
itemGrid->setBackingImageAffinity(
config.getBool("showBackingImageWhenFull", false), config.getBool("showBackingImageWhenEmpty", true));
itemGrid->showDurability(config.getBool("showDurability", false));
if (auto leftClickCallback = m_callbacks.ptr(callback))
itemGrid->setCallback(*leftClickCallback);
else
throw WidgetParserException::format("Failed to find ItemGrid callback named: '{}'", callback);
if (auto callback = m_callbacks.ptr(rightClickCallback))
itemGrid->setRightClickCallback(*callback);
else
throw WidgetParserException::format("Failed to find ItemGrid rightClickCallback named: '{}'", rightClickCallback);
if (auto callback = m_callbacks.ptr(middleClickCallback))
itemGrid->setMiddleClickCallback(*callback);
common(itemGrid, config);
return WidgetConstructResult(itemGrid, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::listHandler(String const& name, Json const& config) {
Json schema;
try {
schema = config.get("schema");
} catch (MapException const& e) {
throw WidgetParserException(
strf("Malformed gui json, missing a required value in the map. {}", outputException(e, false)));
}
auto list = make_shared<ListWidget>(schema);
common(list, config);
if (auto callback = m_callbacks.value(config.getString("callback", name)))
list->setCallback(callback);
list->setFillDown(config.getBool("fillDown", false));
list->setColumns(config.getUInt("columns", 1));
return WidgetConstructResult(list, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::sliderHandler(String const& name, Json const& config) {
try {
auto grid = config.getString("gridImage");
auto slider = make_shared<SliderBarWidget>(grid, config.getBool("showSpinner", true));
common(slider, config);
if (auto callback = m_callbacks.value(config.getString("callback", name)))
slider->setCallback(callback);
if (config.contains("range")) {
auto rangeConfig = config.getArray("range");
slider->setRange(rangeConfig.get(0).toInt(), rangeConfig.get(1).toInt(), rangeConfig.get(2).toInt());
}
if (config.contains("jogImages")) {
auto jogConfig = config.get("jogImages");
slider->setJogImages(jogConfig.getString("baseImage"),
jogConfig.getString("hoverImage", ""),
jogConfig.getString("pressedImage", ""),
jogConfig.getString("disabledImage", ""));
}
if (config.contains("disabled"))
slider->setEnabled(!config.getBool("disabled"));
return WidgetConstructResult(slider, name, config.getFloat("zlevel", 0));
} catch (MapException const& e) {
throw WidgetParserException::format(
"Malformed gui json, missing a required value in the map. {}", outputException(e, false));
}
}
WidgetConstructResult WidgetParser::largeCharPlateHandler(String const& name, Json const& config) {
String callback = config.getString("callback", name);
if (!m_callbacks.contains(callback))
throw WidgetParserException::format("Failed to find callback named: '{}'", name);
auto charPlate = make_shared<LargeCharPlateWidget>(m_callbacks.get(callback));
common(charPlate, config);
return WidgetConstructResult(charPlate, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::tabSetHandler(String const& name, Json const& config) {
TabSetConfig tabSetConfig;
tabSetConfig.tabButtonBaseImage = config.getString("tabButtonBaseImage");
tabSetConfig.tabButtonHoverImage = config.getString("tabButtonHoverImage");
tabSetConfig.tabButtonPressedImage = config.getString("tabButtonPressedImage", tabSetConfig.tabButtonHoverImage);
tabSetConfig.tabButtonBaseImageSelected =
config.getString("tabButtonBaseImageSelected", tabSetConfig.tabButtonBaseImage);
tabSetConfig.tabButtonHoverImageSelected =
config.getString("tabButtonHoverImageSelected", tabSetConfig.tabButtonHoverImage);
tabSetConfig.tabButtonPressedImageSelected =
config.getString("tabButtonPressedImageSelected", tabSetConfig.tabButtonHoverImageSelected);
Json defaultPressedOffset = Root::singleton().assets()->json("/interface.config:buttonPressedOffset");
tabSetConfig.tabButtonPressedOffset = jsonToVec2I(config.get("tabButtonPressedOffset", defaultPressedOffset));
tabSetConfig.tabButtonTextOffset = config.opt("tabButtonTextOffset").apply(jsonToVec2I).value();
tabSetConfig.tabButtonSpacing = config.opt("tabButtonSpacing").apply(jsonToVec2I).value();
auto tabSet = make_shared<TabSetWidget>(tabSetConfig);
common(tabSet, config);
try {
for (auto entry : config.get("tabs").iterateArray()) {
auto widget = make_shared<Widget>();
constructImpl(entry.get("children"), widget.get());
widget->determineSizeFromChildren();
tabSet->addTab(entry.getString("tabName"), widget, entry.getString("tabTitle"));
}
} catch (JsonException const& e) {
throw WidgetParserException(strf("Malformed gui json. {}", outputException(e, false)));
}
return WidgetConstructResult(tabSet, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::widgetHandler(String const& name, Json const& config) {
auto widget = make_shared<Widget>();
common(widget, config);
return WidgetConstructResult(widget, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::containerHandler(String const& name, Json const& config) {
auto widget = widgetHandler(name, config);
widget.obj->disableScissoring();
widget.obj->markAsContainer();
return widget;
}
WidgetConstructResult WidgetParser::layoutHandler(String const& name, Json const& config) {
String type;
try {
type = config.getString("layoutType");
} catch (JsonException const&) {
throw WidgetParserException("Failed to find layout type. Options are: \"basic\", \"flow\", \"vertical\".");
}
WidgetPtr widget;
if (type == "flow") {
widget = make_shared<FlowLayout>();
auto flow = convert<FlowLayout>(widget);
try {
flow->setSpacing(jsonToVec2I(config.get("spacing")));
} catch (JsonException const& e) {
throw WidgetParserException(strf("Parameter \"spacing\" in FlowLayout specification is invalid: {}.", outputException(e, false)));
}
} else if (type == "vertical") {
widget = make_shared<VerticalLayout>();
auto vert = convert<VerticalLayout>(widget);
vert->setHorizontalAnchor(HorizontalAnchorNames.getLeft(config.getString("hAnchor", "left")));
vert->setVerticalAnchor(VerticalAnchorNames.getLeft(config.getString("vAnchor", "top")));
vert->setVerticalSpacing(config.getInt("spacing", 0));
vert->setFillDown(config.getBool("fillDown", false));
} else if (type == "basic") {
widget = make_shared<Layout>();
} else {
throw WidgetParserException(strf("Invalid layout type \"{}\". Options are \"basic\", \"flow\", \"vertical\".", type));
}
common(widget, config);
if (config.contains("children"))
constructImpl(config.get("children"), widget.get());
widget->update(0);
return WidgetConstructResult(widget, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::canvasHandler(String const& name, Json const& config) {
auto canvas = make_shared<CanvasWidget>();
canvas->setCaptureKeyboardEvents(config.getBool("captureKeyboardEvents", false));
canvas->setCaptureMouseEvents(config.getBool("captureMouseEvents", false));
common(canvas, config);
return WidgetConstructResult(canvas, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::fuelGaugeHandler(String const& name, Json const& config) {
auto fuelGauge = make_shared<FuelWidget>();
common(fuelGauge, config);
return WidgetConstructResult(fuelGauge, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::progressHandler(String const& name, Json const& config) {
String background, overlay;
ImageStretchSet progressSet;
background = config.get("background", "").toString();
overlay = config.get("overlay", "").toString();
progressSet = parseImageStretchSet(config.get("progressSet"));
GuiDirection direction = GuiDirectionNames.getLeft(config.getString("direction", "horizontal"));
auto progress = make_shared<ProgressWidget>(background, overlay, progressSet, direction);
common(progress, config);
if (config.contains("barColor"))
progress->setColor(jsonToColor(config.get("barColor")));
if (config.contains("max"))
progress->setMaxProgressLevel(config.getFloat("max"));
if (config.contains("initial"))
progress->setCurrentProgressLevel(config.getFloat("initial"));
return WidgetConstructResult(progress, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::stackHandler(String const& name, Json const& config) {
auto stack = make_shared<StackWidget>();
if (config.contains("stack")) {
auto stackList = config.getArray("stack");
for (auto widgetCfg : stackList) {
auto widget = make_shared<Widget>();
constructImpl(widgetCfg, widget.get());
widget->determineSizeFromChildren();
stack->addChild(toString(stack->numChildren()), widget);
}
}
stack->determineSizeFromChildren();
common(stack, config);
return WidgetConstructResult(stack, name, config.getFloat("zlevel", 0));
}
WidgetConstructResult WidgetParser::scrollAreaHandler(String const& name, Json const& config) {
auto scrollArea = make_shared<ScrollArea>();
if (config.contains("buttons"))
scrollArea->setButtonImages(config.get("buttons"));
if (config.contains("thumbs"))
scrollArea->setThumbImages(config.get("thumbs"));
if (config.contains("children"))
constructImpl(config.get("children"), scrollArea.get());
if (config.contains("horizontalScroll"))
scrollArea->setHorizontalScroll(config.getBool("horizontalScroll"));
if (config.contains("verticalScroll"))
scrollArea->setVerticalScroll(config.getBool("verticalScroll"));
common(scrollArea, config);
return WidgetConstructResult(scrollArea, name, config.getFloat("zlevel", 0));
}
void WidgetParser::common(WidgetPtr widget, Json const& config) {
if (config.contains("rect")) {
auto rect = jsonToRectI(config.get("rect"));
widget->setPosition(rect.min());
widget->setSize(rect.size());
} else {
if (config.contains("size")) {
widget->setSize(jsonToVec2I(config.get("size")));
}
if (config.contains("position")) {
widget->setPosition(jsonToVec2I(config.get("position")));
}
}
if (config.contains("visible"))
widget->setVisibility(config.getBool("visible"));
if (config.getBool("focus", false))
widget->focus();
if (config.contains("data"))
widget->setData(config.get("data"));
if (!config.getBool("scissoring", true))
widget->disableScissoring();
widget->setMouseTransparent(config.getBool("mouseTransparent", false));
}
ImageStretchSet WidgetParser::parseImageStretchSet(Json const& config) {
ImageStretchSet res;
res.begin = config.get("begin", "").toString();
res.inner = config.get("inner", "").toString();
res.end = config.get("end", "").toString();
String type = config.get("type", "stretch").toString();
if (type == "repeat") {
res.type = ImageStretchSet::ImageStretchType::Repeat;
} else if (type == "stretch") {
res.type = ImageStretchSet::ImageStretchType::Stretch;
} else {
throw WidgetParserException(strf("Could not parse Image Stretch Set, unknown type: {}"));
}
return res;
}
}