v1.4.4
This commit is contained in:
commit
9c94d113d3
10260 changed files with 1237388 additions and 0 deletions
76
source/windowing/CMakeLists.txt
Normal file
76
source/windowing/CMakeLists.txt
Normal file
|
@ -0,0 +1,76 @@
|
|||
INCLUDE_DIRECTORIES (
|
||||
${STAR_EXTERN_INCLUDES}
|
||||
${STAR_CORE_INCLUDES}
|
||||
${STAR_BASE_INCLUDES}
|
||||
${STAR_GAME_INCLUDES}
|
||||
${STAR_PLATFORM_INCLUDES}
|
||||
${STAR_APPLICATION_INCLUDES}
|
||||
${STAR_RENDERING_INCLUDES}
|
||||
${STAR_WINDOWING_INCLUDES}
|
||||
)
|
||||
|
||||
SET (star_windowing_HEADERS
|
||||
StarButtonGroup.hpp
|
||||
StarButtonWidget.hpp
|
||||
StarCanvasWidget.hpp
|
||||
StarFlowLayout.hpp
|
||||
StarFuelWidget.hpp
|
||||
StarGuiContext.hpp
|
||||
StarGuiReader.hpp
|
||||
StarGuiTypes.hpp
|
||||
StarImageWidget.hpp
|
||||
StarImageStretchWidget.hpp
|
||||
StarItemGridWidget.hpp
|
||||
StarItemSlotWidget.hpp
|
||||
StarKeyBindings.hpp
|
||||
StarLabelWidget.hpp
|
||||
StarLargeCharPlateWidget.hpp
|
||||
StarLayout.hpp
|
||||
StarListWidget.hpp
|
||||
StarPane.hpp
|
||||
StarPaneManager.hpp
|
||||
StarPortraitWidget.hpp
|
||||
StarProgressWidget.hpp
|
||||
StarScrollArea.hpp
|
||||
StarSliderBar.hpp
|
||||
StarStackWidget.hpp
|
||||
StarTabSet.hpp
|
||||
StarTextBoxWidget.hpp
|
||||
StarVerticalLayout.hpp
|
||||
StarWidget.hpp
|
||||
StarWidgetParsing.hpp
|
||||
)
|
||||
|
||||
SET (star_windowing_SOURCES
|
||||
StarButtonGroup.cpp
|
||||
StarButtonWidget.cpp
|
||||
StarCanvasWidget.cpp
|
||||
StarFlowLayout.cpp
|
||||
StarFuelWidget.cpp
|
||||
StarGuiContext.cpp
|
||||
StarGuiReader.cpp
|
||||
StarGuiTypes.cpp
|
||||
StarImageWidget.cpp
|
||||
StarImageStretchWidget.cpp
|
||||
StarItemGridWidget.cpp
|
||||
StarItemSlotWidget.cpp
|
||||
StarKeyBindings.cpp
|
||||
StarLayout.cpp
|
||||
StarLabelWidget.cpp
|
||||
StarLargeCharPlateWidget.cpp
|
||||
StarListWidget.cpp
|
||||
StarPane.cpp
|
||||
StarPaneManager.cpp
|
||||
StarPortraitWidget.cpp
|
||||
StarProgressWidget.cpp
|
||||
StarScrollArea.cpp
|
||||
StarSliderBar.cpp
|
||||
StarStackWidget.cpp
|
||||
StarTabSet.cpp
|
||||
StarTextBoxWidget.cpp
|
||||
StarVerticalLayout.cpp
|
||||
StarWidget.cpp
|
||||
StarWidgetParsing.cpp
|
||||
)
|
||||
|
||||
ADD_LIBRARY (star_windowing OBJECT ${star_windowing_SOURCES} ${star_windowing_HEADERS})
|
90
source/windowing/StarButtonGroup.cpp
Normal file
90
source/windowing/StarButtonGroup.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#include "StarButtonGroup.hpp"
|
||||
#include "StarButtonWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
void ButtonGroup::setCallback(WidgetCallbackFunc callback) {
|
||||
m_callback = callback;
|
||||
}
|
||||
|
||||
ButtonWidget* ButtonGroup::button(int id) const {
|
||||
return m_buttons.value(id);
|
||||
}
|
||||
|
||||
List<ButtonWidget*> ButtonGroup::buttons() const {
|
||||
return m_buttons.values();
|
||||
}
|
||||
|
||||
size_t ButtonGroup::buttonCount() const {
|
||||
return m_buttons.size();
|
||||
}
|
||||
|
||||
int ButtonGroup::addButton(ButtonWidget* button, int id) {
|
||||
if (!button)
|
||||
return NoButton;
|
||||
else if (m_buttonIds.contains(button))
|
||||
return m_buttonIds.get(button);
|
||||
|
||||
// If we are auto-generating an id, start at the last id and work forward.
|
||||
if (id == NoButton && !m_buttons.empty())
|
||||
id = (prev(m_buttons.end()))->first;
|
||||
|
||||
while (m_buttons.contains(id))
|
||||
++id;
|
||||
|
||||
m_buttons[id] = button;
|
||||
m_buttonIds[button] = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
void ButtonGroup::removeButton(ButtonWidget* button) {
|
||||
if (m_buttonIds.contains(button)) {
|
||||
m_buttons.remove(m_buttonIds.get(button));
|
||||
m_buttonIds.remove(button);
|
||||
}
|
||||
}
|
||||
|
||||
int ButtonGroup::id(ButtonWidget* button) const {
|
||||
if (m_buttonIds.contains(button))
|
||||
return m_buttonIds.get(button);
|
||||
else
|
||||
return NoButton;
|
||||
}
|
||||
|
||||
ButtonWidget* ButtonGroup::checkedButton() const {
|
||||
for (auto const& pair : m_buttons) {
|
||||
if (pair.second->isChecked())
|
||||
return pair.second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int ButtonGroup::checkedId() const {
|
||||
return id(checkedButton());
|
||||
}
|
||||
|
||||
void ButtonGroup::select(int id) {
|
||||
auto b = button(id);
|
||||
if (!b->isChecked())
|
||||
b->check();
|
||||
}
|
||||
|
||||
void ButtonGroup::wasChecked(ButtonWidget* self) {
|
||||
for (auto const& pair : m_buttons) {
|
||||
if (pair.second != self)
|
||||
pair.second->setChecked(false);
|
||||
}
|
||||
|
||||
if (m_callback)
|
||||
m_callback(self);
|
||||
}
|
||||
|
||||
bool ButtonGroup::toggle() const {
|
||||
return m_toggle;
|
||||
}
|
||||
|
||||
void ButtonGroup::setToggle(bool toggleMode) {
|
||||
m_toggle = toggleMode;
|
||||
}
|
||||
|
||||
}
|
59
source/windowing/StarButtonGroup.hpp
Normal file
59
source/windowing/StarButtonGroup.hpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
#ifndef STAR_BUTTON_GROUP_HPP
|
||||
#define STAR_BUTTON_GROUP_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ButtonWidget);
|
||||
STAR_CLASS(ButtonGroup);
|
||||
STAR_CLASS(ButtonGroupWidget);
|
||||
|
||||
// Manages group of buttons in which *at most* a single button can be checked
|
||||
// at any time.
|
||||
class ButtonGroup {
|
||||
public:
|
||||
friend class ButtonWidget;
|
||||
|
||||
static int const NoButton = -1;
|
||||
|
||||
// Callback is called when any child buttons checked state is changed, and
|
||||
// its parameter is the button being checked.
|
||||
void setCallback(WidgetCallbackFunc callback);
|
||||
|
||||
ButtonWidget* button(int id) const;
|
||||
List<ButtonWidget*> buttons() const;
|
||||
size_t buttonCount() const;
|
||||
|
||||
int addButton(ButtonWidget* button, int id = NoButton);
|
||||
void removeButton(ButtonWidget* button);
|
||||
|
||||
int id(ButtonWidget* button) const;
|
||||
|
||||
void select(int id);
|
||||
|
||||
// Will return null if no button is checked.
|
||||
ButtonWidget* checkedButton() const;
|
||||
// Will return NoButton if no button is checked.
|
||||
int checkedId() const;
|
||||
|
||||
// when true it is not required for one of the buttons to be selected
|
||||
bool toggle() const;
|
||||
void setToggle(bool toggleMode);
|
||||
|
||||
protected:
|
||||
// Should be called by child button widgets when they are changed from
|
||||
// unchecked to checked.
|
||||
void wasChecked(ButtonWidget* self);
|
||||
|
||||
private:
|
||||
WidgetCallbackFunc m_callback;
|
||||
Map<int, ButtonWidget*> m_buttons;
|
||||
Map<ButtonWidget*, int> m_buttonIds;
|
||||
bool m_toggle;
|
||||
};
|
||||
|
||||
class ButtonGroupWidget : public ButtonGroup, public Widget {};
|
||||
}
|
||||
|
||||
#endif
|
380
source/windowing/StarButtonWidget.cpp
Normal file
380
source/windowing/StarButtonWidget.cpp
Normal file
|
@ -0,0 +1,380 @@
|
|||
#include "StarButtonWidget.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarRandom.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ButtonWidget::ButtonWidget() {
|
||||
m_hovered = false;
|
||||
m_pressed = false;
|
||||
m_checkable = false;
|
||||
m_checked = false;
|
||||
m_disabled = false;
|
||||
m_highlighted = false;
|
||||
m_hasCheckedImages = false;
|
||||
m_sustain = false;
|
||||
m_invisible = false;
|
||||
m_hTextAnchor = HorizontalAnchor::HMidAnchor;
|
||||
m_fontColor = Color::White;
|
||||
m_fontColorDisabled = Color::Gray;
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
auto interfaceConfig = assets->json("/interface.config");
|
||||
m_pressedOffset = jsonToVec2I(interfaceConfig.get("buttonPressedOffset"));
|
||||
m_fontSize = interfaceConfig.query("font.buttonSize").toInt();
|
||||
}
|
||||
|
||||
ButtonWidget::ButtonWidget(WidgetCallbackFunc callback,
|
||||
String const& baseImage,
|
||||
String const& hoverImage,
|
||||
String const& pressedImage,
|
||||
String const& disabledImage)
|
||||
: ButtonWidget() {
|
||||
setCallback(callback);
|
||||
setImages(baseImage, hoverImage, pressedImage, disabledImage);
|
||||
}
|
||||
|
||||
ButtonWidget::~ButtonWidget() {
|
||||
if (m_buttonGroup)
|
||||
m_buttonGroup->removeButton(this);
|
||||
}
|
||||
|
||||
void ButtonWidget::renderImpl() {
|
||||
if (isPressed() && sustainCallbackOnDownHold())
|
||||
if (m_callback)
|
||||
m_callback(this);
|
||||
|
||||
Vec2F position = Vec2F(screenPosition());
|
||||
Vec2F textPosition = position + Vec2F(m_textOffset);
|
||||
if (m_hTextAnchor == HorizontalAnchor::HMidAnchor)
|
||||
textPosition += Vec2F(size()) / 2;
|
||||
else if (m_hTextAnchor == HorizontalAnchor::RightAnchor)
|
||||
textPosition += Vec2F(size()[0], size()[1] / 2);
|
||||
else if (m_hTextAnchor == HorizontalAnchor::LeftAnchor)
|
||||
textPosition += Vec2F(0, size()[1] / 2);
|
||||
// We need to show the down button offset if we're pressing the button or
|
||||
// don't have checked images and thus need some way to show that the button
|
||||
// is checked (there's probably some better default behavior in that case)
|
||||
if (m_pressed || (m_checked && !m_hasCheckedImages)) {
|
||||
position += Vec2F(m_pressedOffset);
|
||||
textPosition += Vec2F(m_pressedOffset);
|
||||
}
|
||||
|
||||
if (m_hasCheckedImages && m_checked) {
|
||||
if (m_disabled)
|
||||
drawButtonPart(m_disabledImageChecked, position);
|
||||
else if ((m_pressed || m_highlighted) && !m_pressedImageChecked.empty())
|
||||
drawButtonPart(m_pressedImageChecked, position);
|
||||
else if ((m_pressed || m_hovered || m_highlighted) && !m_hoverImageChecked.empty())
|
||||
drawButtonPart(m_hoverImageChecked, position);
|
||||
else
|
||||
drawButtonPart(m_baseImageChecked, position);
|
||||
} else {
|
||||
if (m_disabled)
|
||||
drawButtonPart(m_disabledImage, position);
|
||||
else if ((m_pressed || m_highlighted) && !m_pressedImage.empty())
|
||||
drawButtonPart(m_pressedImage, position);
|
||||
else if ((m_pressed || m_hovered || m_highlighted) && !m_hoverImage.empty())
|
||||
drawButtonPart(m_hoverImage, position);
|
||||
else if (!m_invisible)
|
||||
drawButtonPart(m_baseImage, position);
|
||||
}
|
||||
|
||||
if (!m_overlayImage.empty())
|
||||
drawButtonPart(m_overlayImage, position);
|
||||
|
||||
if (!m_text.empty()) {
|
||||
auto& guiContext = GuiContext::singleton();
|
||||
guiContext.setFontSize(m_fontSize);
|
||||
if (m_disabled)
|
||||
guiContext.setFontColor(m_fontColorDisabled.toRgba());
|
||||
else if (m_fontColorChecked && m_checked)
|
||||
guiContext.setFontColor(m_fontColorChecked.value().toRgba());
|
||||
else
|
||||
guiContext.setFontColor(m_fontColor.toRgba());
|
||||
guiContext.setFontMode(FontMode::Shadow);
|
||||
guiContext.renderInterfaceText(m_text, {textPosition, m_hTextAnchor, VerticalAnchor::VMidAnchor});
|
||||
guiContext.setFontMode(FontMode::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
bool ButtonWidget::sendEvent(InputEvent const& event) {
|
||||
if (m_visible && !m_disabled) {
|
||||
if (event.is<MouseButtonDownEvent>() && event.get<MouseButtonDownEvent>().mouseButton == MouseButton::Left) {
|
||||
if (inMember(*context()->mousePosition(event))) {
|
||||
if (!isPressed()) {
|
||||
auto assets = Root::singleton().assets();
|
||||
auto sound = Random::randValueFrom(assets->json("/interface.config:buttonClickSound").toArray(), "").toString();
|
||||
if (!sound.empty())
|
||||
context()->playAudio(sound);
|
||||
}
|
||||
setPressed(true);
|
||||
if (m_callback) {
|
||||
focus();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
blur();
|
||||
return false;
|
||||
}
|
||||
} else if (event.is<MouseButtonUpEvent>()) {
|
||||
setPressed(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ButtonWidget::mouseOver() {
|
||||
Widget::mouseOver();
|
||||
if (!m_disabled) {
|
||||
if (!m_hovered) {
|
||||
auto assets = Root::singleton().assets();
|
||||
auto sound = Random::randValueFrom(assets->json("/interface.config:buttonHoverSound").toArray(), "").toString();
|
||||
if (!sound.empty())
|
||||
context()->playAudio(sound);
|
||||
}
|
||||
m_hovered = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonWidget::mouseOut() {
|
||||
Widget::mouseOut();
|
||||
m_hovered = false;
|
||||
m_pressed = false;
|
||||
}
|
||||
|
||||
void ButtonWidget::mouseReturnStillDown() {
|
||||
Widget::mouseReturnStillDown();
|
||||
m_hovered = true;
|
||||
m_pressed = true;
|
||||
}
|
||||
|
||||
void ButtonWidget::hide() {
|
||||
Widget::hide();
|
||||
m_pressed = false;
|
||||
m_hovered = false;
|
||||
}
|
||||
|
||||
void ButtonWidget::setCallback(WidgetCallbackFunc callback) {
|
||||
m_callback = callback;
|
||||
}
|
||||
|
||||
ButtonGroupPtr ButtonWidget::buttonGroup() const {
|
||||
return m_buttonGroup;
|
||||
}
|
||||
|
||||
void ButtonWidget::setButtonGroup(ButtonGroupPtr newGroup, int id) {
|
||||
if (m_buttonGroup != newGroup) {
|
||||
if (m_buttonGroup)
|
||||
m_buttonGroup->removeButton(this);
|
||||
|
||||
m_buttonGroup = move(newGroup);
|
||||
|
||||
if (m_buttonGroup) {
|
||||
setCheckable(true);
|
||||
m_buttonGroup->addButton(this, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ButtonWidget::buttonGroupId() {
|
||||
if (m_buttonGroup)
|
||||
return m_buttonGroup->id(this);
|
||||
else
|
||||
return ButtonGroup::NoButton;
|
||||
}
|
||||
|
||||
bool ButtonWidget::isHovered() const {
|
||||
return m_hovered;
|
||||
}
|
||||
|
||||
bool ButtonWidget::isPressed() const {
|
||||
return m_pressed;
|
||||
}
|
||||
|
||||
void ButtonWidget::setPressed(bool pressed) {
|
||||
if (m_pressed != pressed) {
|
||||
// Button action is triggered when the button is released after being pressed
|
||||
if (m_pressed) {
|
||||
check();
|
||||
if (m_callback) {
|
||||
m_callback(this);
|
||||
}
|
||||
}
|
||||
m_pressed = pressed;
|
||||
}
|
||||
}
|
||||
|
||||
bool ButtonWidget::isCheckable() const {
|
||||
return m_checkable;
|
||||
}
|
||||
|
||||
void ButtonWidget::setCheckable(bool checkable) {
|
||||
m_checkable = checkable;
|
||||
}
|
||||
|
||||
bool ButtonWidget::isHighlighted() const {
|
||||
return m_highlighted;
|
||||
}
|
||||
|
||||
void ButtonWidget::setHighlighted(bool highlighted) {
|
||||
m_highlighted = highlighted;
|
||||
}
|
||||
|
||||
bool ButtonWidget::isChecked() const {
|
||||
return m_checked;
|
||||
}
|
||||
|
||||
void ButtonWidget::setChecked(bool checked) {
|
||||
// might cause button groups to have multiple selected against its rules, be careful with direct poking, use check()
|
||||
// instead.
|
||||
m_checked = checked;
|
||||
}
|
||||
|
||||
void ButtonWidget::check() {
|
||||
if (m_checkable) {
|
||||
// If we are part of an exclusive button group, then don't uncheck if
|
||||
// we are already checked and pressed again.
|
||||
if (m_buttonGroup) {
|
||||
if (m_buttonGroup->toggle() || !isChecked()) {
|
||||
setChecked(!m_buttonGroup->toggle() || !isChecked());
|
||||
m_buttonGroup->wasChecked(this);
|
||||
}
|
||||
} else {
|
||||
setChecked(!isChecked());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ButtonWidget::sustainCallbackOnDownHold() {
|
||||
return m_sustain;
|
||||
}
|
||||
|
||||
void ButtonWidget::setSustainCallbackOnDownHold(bool sustain) {
|
||||
m_sustain = sustain;
|
||||
}
|
||||
|
||||
void ButtonWidget::setImages(String const& baseImage, String const& hoverImage, String const& pressedImage, String const& disabledImage) {
|
||||
m_baseImage = baseImage;
|
||||
m_hoverImage = hoverImage;
|
||||
m_pressedImage = pressedImage;
|
||||
m_disabledImage = disabledImage;
|
||||
if (m_disabledImage.empty() && !m_baseImage.empty())
|
||||
m_disabledImage = m_baseImage + Root::singleton().assets()->json("/interface.config:disabledButton").toString();
|
||||
updateSize();
|
||||
}
|
||||
|
||||
void ButtonWidget::setCheckedImages(String const& baseImage, String const& hoverImage, String const& pressedImage, String const& disabledImage) {
|
||||
m_hasCheckedImages = !baseImage.empty();
|
||||
m_baseImageChecked = baseImage;
|
||||
m_hoverImageChecked = hoverImage;
|
||||
m_pressedImageChecked = pressedImage;
|
||||
m_disabledImageChecked = disabledImage;
|
||||
if (m_hasCheckedImages && m_disabledImageChecked.empty())
|
||||
m_disabledImageChecked = m_baseImageChecked + Root::singleton().assets()->json("/interface.config:disabledButton").toString();
|
||||
updateSize();
|
||||
}
|
||||
|
||||
void ButtonWidget::setOverlayImage(String const& overlayImage) {
|
||||
m_overlayImage = overlayImage;
|
||||
}
|
||||
|
||||
Vec2I const& ButtonWidget::pressedOffset() const {
|
||||
return m_pressedOffset;
|
||||
}
|
||||
|
||||
void ButtonWidget::setPressedOffset(Vec2I const& offset) {
|
||||
m_pressedOffset = offset;
|
||||
}
|
||||
|
||||
void ButtonWidget::setText(String const& text) {
|
||||
m_text = text;
|
||||
}
|
||||
|
||||
void ButtonWidget::setFontSize(int size) {
|
||||
m_fontSize = size;
|
||||
}
|
||||
|
||||
void ButtonWidget::setTextOffset(Vec2I textOffset) {
|
||||
m_textOffset = textOffset;
|
||||
}
|
||||
|
||||
void ButtonWidget::setTextAlign(HorizontalAnchor hAnchor) {
|
||||
m_hTextAnchor = hAnchor;
|
||||
}
|
||||
|
||||
void ButtonWidget::setFontColor(Color color) {
|
||||
m_fontColor = color;
|
||||
}
|
||||
|
||||
void ButtonWidget::setFontColorDisabled(Color color) {
|
||||
m_fontColorDisabled = color;
|
||||
}
|
||||
|
||||
void ButtonWidget::setFontColorChecked(Color color) {
|
||||
m_fontColorChecked = color;
|
||||
}
|
||||
|
||||
// Although ButtonWidget wraps other widgets from time to time. These should never be "accessible"
|
||||
WidgetPtr ButtonWidget::getChildAt(Vec2I const&) {
|
||||
return {};
|
||||
}
|
||||
|
||||
void ButtonWidget::disable() {
|
||||
m_disabled = true;
|
||||
m_pressed = false;
|
||||
}
|
||||
|
||||
void ButtonWidget::enable() {
|
||||
m_disabled = false;
|
||||
}
|
||||
|
||||
void ButtonWidget::setEnabled(bool enabled) {
|
||||
m_disabled = !enabled;
|
||||
m_pressed &= enabled; // and off the pressed flag if the button is no longer enabled.
|
||||
}
|
||||
|
||||
void ButtonWidget::setInvisible(bool invisible) {
|
||||
m_invisible = invisible;
|
||||
}
|
||||
|
||||
RectI ButtonWidget::getScissorRect() const {
|
||||
if (m_pressed) {
|
||||
return RectI::withSize(screenPosition(), size() + m_pressedOffset);
|
||||
}
|
||||
return RectI::withSize(screenPosition(), size());
|
||||
}
|
||||
|
||||
void ButtonWidget::drawButtonPart(String const& image, Vec2F const& position) {
|
||||
auto& guiContext = GuiContext::singleton();
|
||||
auto imageSize = guiContext.textureSize(image);
|
||||
guiContext.drawInterfaceQuad(image, position + Vec2F(m_buttonBoundSize - imageSize) / 2);
|
||||
}
|
||||
|
||||
void ButtonWidget::updateSize() {
|
||||
if (m_invisible || m_baseImage.empty())
|
||||
return;
|
||||
auto& guiContext = GuiContext::singleton();
|
||||
m_buttonBoundSize = guiContext.textureSize(m_baseImage);
|
||||
if (!m_hoverImage.empty())
|
||||
m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_hoverImage));
|
||||
if (!m_pressedImage.empty())
|
||||
m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_pressedImage));
|
||||
if (!m_baseImageChecked.empty())
|
||||
m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_baseImageChecked));
|
||||
if (!m_hoverImageChecked.empty())
|
||||
m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_hoverImageChecked));
|
||||
if (!m_pressedImageChecked.empty())
|
||||
m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_pressedImageChecked));
|
||||
if (!m_disabledImageChecked.empty())
|
||||
m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_disabledImageChecked));
|
||||
|
||||
setSize(Vec2I(m_buttonBoundSize));
|
||||
}
|
||||
|
||||
}
|
138
source/windowing/StarButtonWidget.hpp
Normal file
138
source/windowing/StarButtonWidget.hpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
#ifndef STAR_BUTTON_WIDGET_HPP
|
||||
#define STAR_BUTTON_WIDGET_HPP
|
||||
|
||||
#include "StarButtonGroup.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ButtonWidget);
|
||||
|
||||
class ButtonWidget : public Widget {
|
||||
public:
|
||||
ButtonWidget();
|
||||
ButtonWidget(WidgetCallbackFunc callback,
|
||||
String const& baseImage,
|
||||
String const& hoverImage = "",
|
||||
String const& pressedImage = "",
|
||||
String const& disabledImage = "");
|
||||
virtual ~ButtonWidget();
|
||||
|
||||
virtual bool sendEvent(InputEvent const& event) override;
|
||||
virtual void mouseOver() override;
|
||||
virtual void mouseOut() override;
|
||||
virtual void mouseReturnStillDown() override;
|
||||
virtual void hide() override;
|
||||
|
||||
// Callback is called when the checked / pressed state is changed.
|
||||
void setCallback(WidgetCallbackFunc callback);
|
||||
|
||||
ButtonGroupPtr buttonGroup() const;
|
||||
// Sets the button group for this widget, and adds it to the button group if
|
||||
// it is not already added. Additionally, sets the button as checkable.
|
||||
void setButtonGroup(ButtonGroupPtr buttonGroup, int id = ButtonGroup::NoButton);
|
||||
// If a button group is set, returns this button's id in the button group.
|
||||
int buttonGroupId();
|
||||
|
||||
bool isHovered() const;
|
||||
|
||||
bool isPressed() const;
|
||||
void setPressed(bool pressed);
|
||||
|
||||
bool isCheckable() const;
|
||||
void setCheckable(bool checkable);
|
||||
|
||||
bool isHighlighted() const;
|
||||
void setHighlighted(bool highlighted);
|
||||
|
||||
bool isChecked() const;
|
||||
void setChecked(bool checked);
|
||||
// Either checks a button, or toggles the state, depending on whether the
|
||||
// button is part of an exclusive group or not.
|
||||
void check();
|
||||
|
||||
bool sustainCallbackOnDownHold();
|
||||
void setSustainCallbackOnDownHold(bool sustain);
|
||||
|
||||
void setImages(String const& baseImage,
|
||||
String const& hoverImage = "",
|
||||
String const& pressedImage = "",
|
||||
String const& disabledImage = "");
|
||||
void setCheckedImages(String const& baseImage,
|
||||
String const& hoverImage = "",
|
||||
String const& pressedImage = "",
|
||||
String const& disabledImage = "");
|
||||
void setOverlayImage(String const& overlayImage = "");
|
||||
|
||||
// Used to offset drawing when the button is being pressed / checked
|
||||
Vec2I const& pressedOffset() const;
|
||||
void setPressedOffset(Vec2I const& offset);
|
||||
|
||||
virtual void setText(String const& text);
|
||||
virtual void setFontSize(int size);
|
||||
virtual void setTextOffset(Vec2I textOffset);
|
||||
|
||||
void setTextAlign(HorizontalAnchor hAnchor);
|
||||
void setFontColor(Color color);
|
||||
void setFontColorDisabled(Color color);
|
||||
void setFontColorChecked(Color color);
|
||||
|
||||
virtual WidgetPtr getChildAt(Vec2I const& pos) override;
|
||||
|
||||
void disable();
|
||||
void enable();
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
void setInvisible(bool invisible);
|
||||
|
||||
protected:
|
||||
virtual RectI getScissorRect() const override;
|
||||
virtual void renderImpl() override;
|
||||
|
||||
void drawButtonPart(String const& image, Vec2F const& position);
|
||||
void updateSize();
|
||||
|
||||
WidgetCallbackFunc m_callback;
|
||||
ButtonGroupPtr m_buttonGroup;
|
||||
|
||||
bool m_hovered;
|
||||
bool m_pressed;
|
||||
bool m_checkable;
|
||||
bool m_checked;
|
||||
|
||||
bool m_disabled;
|
||||
bool m_highlighted;
|
||||
|
||||
String m_baseImage;
|
||||
String m_hoverImage;
|
||||
String m_pressedImage;
|
||||
String m_disabledImage;
|
||||
|
||||
bool m_hasCheckedImages;
|
||||
String m_baseImageChecked;
|
||||
String m_hoverImageChecked;
|
||||
String m_pressedImageChecked;
|
||||
String m_disabledImageChecked;
|
||||
|
||||
String m_overlayImage;
|
||||
|
||||
bool m_invisible;
|
||||
|
||||
Vec2I m_pressedOffset;
|
||||
Vec2U m_buttonBoundSize;
|
||||
|
||||
int m_fontSize;
|
||||
String m_text;
|
||||
Vec2I m_textOffset;
|
||||
|
||||
bool m_sustain;
|
||||
|
||||
private:
|
||||
HorizontalAnchor m_hTextAnchor;
|
||||
Color m_fontColor;
|
||||
Color m_fontColorDisabled;
|
||||
Maybe<Color> m_fontColorChecked;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
238
source/windowing/StarCanvasWidget.cpp
Normal file
238
source/windowing/StarCanvasWidget.cpp
Normal file
|
@ -0,0 +1,238 @@
|
|||
#include "StarCanvasWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
CanvasWidget::CanvasWidget() {
|
||||
m_captureKeyboard = false;
|
||||
m_captureMouse = false;
|
||||
}
|
||||
|
||||
void CanvasWidget::setCaptureMouseEvents(bool captureMouse) {
|
||||
m_captureMouse = captureMouse;
|
||||
}
|
||||
|
||||
void CanvasWidget::setCaptureKeyboardEvents(bool captureKeyboard) {
|
||||
m_captureKeyboard = captureKeyboard;
|
||||
}
|
||||
|
||||
void CanvasWidget::clear() {
|
||||
m_renderOps.clear();
|
||||
}
|
||||
|
||||
void CanvasWidget::drawImage(String texName, Vec2F const& position, float scale, Vec4B const& color) {
|
||||
m_renderOps.append(make_tuple(move(texName), position, scale, color, false));
|
||||
}
|
||||
|
||||
void CanvasWidget::drawImageCentered(String texName, Vec2F const& position, float scale, Vec4B const& color) {
|
||||
m_renderOps.append(make_tuple(move(texName), position, scale, color, true));
|
||||
}
|
||||
|
||||
void CanvasWidget::drawImageRect(String texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) {
|
||||
m_renderOps.append(make_tuple(move(texName), texCoords, screenCoords, color));
|
||||
}
|
||||
|
||||
void CanvasWidget::drawDrawable(Drawable drawable, Vec2F const& screenPos) {
|
||||
m_renderOps.append(make_tuple(move(drawable), screenPos));
|
||||
}
|
||||
|
||||
void CanvasWidget::drawDrawables(List<Drawable> drawables, Vec2F const& screenPos) {
|
||||
for (auto& drawable : drawables)
|
||||
drawDrawable(move(drawable), screenPos);
|
||||
}
|
||||
|
||||
void CanvasWidget::drawTiledImage(String texName, float textureScale, Vec2D const& offset, RectF const& screenCoords, Vec4B const& color) {
|
||||
m_renderOps.append(make_tuple(move(texName), textureScale, offset, screenCoords, color));
|
||||
}
|
||||
|
||||
void CanvasWidget::drawLine(Vec2F const& begin, Vec2F const end, Vec4B const& color, float lineWidth) {
|
||||
m_renderOps.append(make_tuple(begin, end, color, lineWidth));
|
||||
}
|
||||
|
||||
void CanvasWidget::drawRect(RectF const& coords, Vec4B const& color) {
|
||||
m_renderOps.append(make_tuple(coords, color));
|
||||
}
|
||||
|
||||
void CanvasWidget::drawPoly(PolyF const& poly, Vec4B const& color, float lineWidth) {
|
||||
m_renderOps.append(make_tuple(poly, color, lineWidth));
|
||||
}
|
||||
|
||||
void CanvasWidget::drawTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color) {
|
||||
m_renderOps.append(make_tuple(triangles, color));
|
||||
}
|
||||
|
||||
void CanvasWidget::drawText(String s, TextPositioning position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing) {
|
||||
m_renderOps.append(make_tuple(move(s), move(position), fontSize, color, mode, lineSpacing));
|
||||
}
|
||||
|
||||
Vec2I CanvasWidget::mousePosition() const {
|
||||
return m_mousePosition;
|
||||
}
|
||||
|
||||
List<CanvasWidget::ClickEvent> CanvasWidget::pullClickEvents() {
|
||||
return take(m_clickEvents);
|
||||
}
|
||||
|
||||
List<CanvasWidget::KeyEvent> CanvasWidget::pullKeyEvents() {
|
||||
return take(m_keyEvents);
|
||||
}
|
||||
|
||||
bool CanvasWidget::sendEvent(InputEvent const& event) {
|
||||
if (!m_visible)
|
||||
return false;
|
||||
|
||||
auto& context = GuiContext::singleton();
|
||||
if (auto mouseButtonDown = event.ptr<MouseButtonDownEvent>()) {
|
||||
if (inMember(*context.mousePosition(event)) && m_captureMouse) {
|
||||
m_clickEvents.append({*context.mousePosition(event) - screenPosition(), mouseButtonDown->mouseButton, true});
|
||||
m_clickEvents.limitSizeBack(MaximumEventBuffer);
|
||||
return true;
|
||||
}
|
||||
} else if (auto mouseButtonUp = event.ptr<MouseButtonUpEvent>()) {
|
||||
if (m_captureMouse) {
|
||||
m_clickEvents.append({*context.mousePosition(event) - screenPosition(), mouseButtonUp->mouseButton, false});
|
||||
m_clickEvents.limitSizeBack(MaximumEventBuffer);
|
||||
return true;
|
||||
}
|
||||
} else if (event.is<MouseMoveEvent>()) {
|
||||
m_mousePosition = *context.mousePosition(event) - screenPosition();
|
||||
return false;
|
||||
} else if (auto keyDown = event.ptr<KeyDownEvent>()) {
|
||||
if (m_captureKeyboard) {
|
||||
m_keyEvents.append({keyDown->key, true});
|
||||
return true;
|
||||
}
|
||||
} else if (auto keyUp = event.ptr<KeyUpEvent>()) {
|
||||
if (m_captureKeyboard) {
|
||||
m_keyEvents.append({keyUp->key, false});
|
||||
m_keyEvents.limitSizeBack(MaximumEventBuffer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return Widget::sendEvent(event);
|
||||
}
|
||||
|
||||
KeyboardCaptureMode CanvasWidget::keyboardCaptured() const {
|
||||
return m_captureKeyboard ? KeyboardCaptureMode::KeyEvents : KeyboardCaptureMode::None;
|
||||
}
|
||||
|
||||
void CanvasWidget::renderImpl() {
|
||||
auto renderingOffset = Vec2F(screenPosition());
|
||||
|
||||
for (auto const& op : m_renderOps) {
|
||||
if (auto args = op.ptr<ImageOp>())
|
||||
tupleUnpackFunction(bind(&CanvasWidget::renderImage, this, renderingOffset, _1, _2, _3, _4, _5), *args);
|
||||
if (auto args = op.ptr<ImageRectOp>())
|
||||
tupleUnpackFunction(bind(&CanvasWidget::renderImageRect, this, renderingOffset, _1, _2, _3, _4), *args);
|
||||
if (auto args = op.ptr<DrawableOp>())
|
||||
tupleUnpackFunction(bind(&CanvasWidget::renderDrawable, this, renderingOffset, _1, _2), *args);
|
||||
if (auto args = op.ptr<TiledImageOp>())
|
||||
tupleUnpackFunction(bind(&CanvasWidget::renderTiledImage, this, renderingOffset, _1, _2, _3, _4, _5), *args);
|
||||
if (auto args = op.ptr<LineOp>())
|
||||
tupleUnpackFunction(bind(&CanvasWidget::renderLine, this, renderingOffset, _1, _2, _3, _4), *args);
|
||||
if (auto args = op.ptr<RectOp>())
|
||||
tupleUnpackFunction(bind(&CanvasWidget::renderRect, this, renderingOffset, _1, _2), *args);
|
||||
if (auto args = op.ptr<PolyOp>())
|
||||
tupleUnpackFunction(bind(&CanvasWidget::renderPoly, this, renderingOffset, _1, _2, _3), *args);
|
||||
if (auto args = op.ptr<TrianglesOp>())
|
||||
tupleUnpackFunction(bind(&CanvasWidget::renderTriangles, this, renderingOffset, _1, _2), *args);
|
||||
if (auto args = op.ptr<TextOp>())
|
||||
tupleUnpackFunction(bind(&CanvasWidget::renderText, this, renderingOffset, _1, _2, _3, _4, _5, _6), *args);
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasWidget::renderImage(Vec2F const& renderingOffset, String const& texName, Vec2F const& position, float scale, Vec4B const& color, bool centered) {
|
||||
auto& context = GuiContext::singleton();
|
||||
auto texSize = Vec2F(context.textureSize(texName));
|
||||
if (centered) {
|
||||
auto screenCoords = RectF::withSize(renderingOffset * context.interfaceScale() + (position - scale * texSize / 2.0f) * context.interfaceScale(), texSize * scale * context.interfaceScale());
|
||||
context.drawQuad(texName, screenCoords, color);
|
||||
} else {
|
||||
auto screenCoords = RectF::withSize(renderingOffset * context.interfaceScale() + position * context.interfaceScale(), texSize * scale * context.interfaceScale());
|
||||
context.drawQuad(texName, screenCoords, color);
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasWidget::renderImageRect(Vec2F const& renderingOffset, String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) {
|
||||
auto& context = GuiContext::singleton();
|
||||
context.drawQuad(texName, texCoords, screenCoords.scaled(context.interfaceScale()).translated(renderingOffset * context.interfaceScale()), color);
|
||||
}
|
||||
|
||||
void CanvasWidget::renderDrawable(Vec2F const& renderingOffset, Drawable drawable, Vec2F const& screenPos) {
|
||||
auto& context = GuiContext::singleton();
|
||||
drawable.scale(context.interfaceScale());
|
||||
context.drawDrawable(move(drawable), renderingOffset * context.interfaceScale() + screenPos * context.interfaceScale(), 1);
|
||||
}
|
||||
|
||||
void CanvasWidget::renderTiledImage(Vec2F const& renderingOffset, String const& texName, float textureScale, Vec2D const& offset, RectF const& screenCoords, Vec4B const& color) {
|
||||
auto& context = GuiContext::singleton();
|
||||
|
||||
Vec2F texSize = Vec2F(context.textureSize(texName));
|
||||
Vec2F texScaledSize = texSize * textureScale;
|
||||
Vec2I textureCount = Vec2I::ceil(screenCoords.size().piecewiseDivide(texScaledSize)) + Vec2I(2, 2);
|
||||
Vec2F screenLowerLeft = screenCoords.min() - Vec2F(pfmod<double>(texScaledSize[0] - offset[0], texScaledSize[0]), pfmod<double>(texScaledSize[1] - offset[1], texScaledSize[1]));
|
||||
|
||||
for (int x = 0; x < textureCount[0]; ++x) {
|
||||
for (int y = 0; y < textureCount[1]; ++y) {
|
||||
Vec2F screenPos = screenLowerLeft + texScaledSize.piecewiseMultiply({(float)x, (float)y});
|
||||
RectF screenRect = RectF::withSize(screenPos, texScaledSize);
|
||||
|
||||
RectF limitedScreenRect;
|
||||
limitedScreenRect.setXMin(max(screenRect.xMin(), screenCoords.xMin()));
|
||||
limitedScreenRect.setYMin(max(screenRect.yMin(), screenCoords.yMin()));
|
||||
limitedScreenRect.setXMax(min(screenRect.xMax(), screenCoords.xMax()));
|
||||
limitedScreenRect.setYMax(min(screenRect.yMax(), screenCoords.yMax()));
|
||||
|
||||
RectF limitedTexRect = limitedScreenRect.translated(-screenPos).scaled(1 / textureScale);
|
||||
|
||||
if (limitedScreenRect.isEmpty())
|
||||
continue;
|
||||
|
||||
context.drawQuad(texName, limitedTexRect, limitedScreenRect.translated(renderingOffset).scaled(context.interfaceScale()), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasWidget::renderLine(Vec2F const& renderingOffset, Vec2F const& begin, Vec2F const end, Vec4B const& color, float lineWidth) {
|
||||
auto& context = GuiContext::singleton();
|
||||
context.drawLine(
|
||||
renderingOffset * context.interfaceScale() + begin * context.interfaceScale(),
|
||||
renderingOffset * context.interfaceScale() + end * context.interfaceScale(),
|
||||
color, lineWidth);
|
||||
}
|
||||
|
||||
void CanvasWidget::renderRect(Vec2F const& renderingOffset, RectF const& coords, Vec4B const& color) {
|
||||
auto& context = GuiContext::singleton();
|
||||
context.drawQuad(coords.scaled(context.interfaceScale()).translated(renderingOffset * context.interfaceScale()), color);
|
||||
}
|
||||
|
||||
void CanvasWidget::renderPoly(Vec2F const& renderingOffset, PolyF poly, Vec4B const& color, float lineWidth) {
|
||||
auto& context = GuiContext::singleton();
|
||||
poly.translate(renderingOffset);
|
||||
context.drawInterfacePolyLines(poly, color, lineWidth);
|
||||
}
|
||||
|
||||
void CanvasWidget::renderTriangles(Vec2F const& renderingOffset, List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color) {
|
||||
auto& context = GuiContext::singleton();
|
||||
auto translated = triangles.transformed([&renderingOffset](tuple<Vec2F, Vec2F, Vec2F> const& poly) {
|
||||
return tuple<Vec2F, Vec2F, Vec2F>(get<0>(poly) + renderingOffset,
|
||||
get<1>(poly) + renderingOffset,
|
||||
get<2>(poly) + renderingOffset);
|
||||
});
|
||||
context.drawInterfaceTriangles(translated, color);
|
||||
}
|
||||
|
||||
void CanvasWidget::renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing) {
|
||||
auto& context = GuiContext::singleton();
|
||||
context.setFontSize(fontSize);
|
||||
context.setFontColor(color);
|
||||
context.setFontMode(mode);
|
||||
context.setLineSpacing(lineSpacing);
|
||||
|
||||
TextPositioning translatedPosition = position;
|
||||
translatedPosition.pos += renderingOffset;
|
||||
context.renderInterfaceText(s, translatedPosition);
|
||||
context.setDefaultLineSpacing();
|
||||
}
|
||||
|
||||
}
|
106
source/windowing/StarCanvasWidget.hpp
Normal file
106
source/windowing/StarCanvasWidget.hpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
#ifndef STAR_CANVAS_WIDGET_HPP
|
||||
#define STAR_CANVAS_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(CanvasWidget);
|
||||
|
||||
// Very simple Widget that allows easy drawing to its surface, to easily tie a
|
||||
// simplified rendering / input context into the regular widget / GuiReader
|
||||
// system.
|
||||
class CanvasWidget : public Widget {
|
||||
public:
|
||||
struct ClickEvent {
|
||||
Vec2I position;
|
||||
MouseButton button;
|
||||
// True when button down, false when key up
|
||||
bool buttonDown;
|
||||
};
|
||||
|
||||
struct KeyEvent {
|
||||
Key key;
|
||||
// True when key down, false when key up
|
||||
bool keyDown;
|
||||
};
|
||||
|
||||
unsigned MaximumEventBuffer = 16;
|
||||
|
||||
CanvasWidget();
|
||||
|
||||
void setCaptureMouseEvents(bool captureMouse);
|
||||
void setCaptureKeyboardEvents(bool captureKeyboard);
|
||||
|
||||
// Returns mouse position relative to the lower left of the drawing region.
|
||||
Vec2I mousePosition() const;
|
||||
|
||||
// Pulls recent click events relative to the lower left of the drawing
|
||||
// region, if configured to capture mouse events
|
||||
List<ClickEvent> pullClickEvents();
|
||||
|
||||
// Pulls recent key events captured by this Canvas, if configured to capture
|
||||
// key events.
|
||||
List<KeyEvent> pullKeyEvents();
|
||||
|
||||
bool sendEvent(InputEvent const& event) override;
|
||||
KeyboardCaptureMode keyboardCaptured() const override;
|
||||
|
||||
// Call before drawing to clear old draw data.
|
||||
void clear();
|
||||
|
||||
void drawImage(String texName, Vec2F const& position, float scale = 1.0f, Vec4B const& color = Vec4B(255, 255, 255, 255));
|
||||
void drawImageRect(String texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color);
|
||||
void drawImageCentered(String texName, Vec2F const& position, float scale = 1.0f, Vec4B const& color = Vec4B(255, 255, 255, 255));
|
||||
|
||||
void drawDrawable(Drawable drawable, Vec2F const& screenPos);
|
||||
void drawDrawables(List<Drawable> drawables, Vec2F const& screenPos);
|
||||
|
||||
// Draw an image whose texture is applied over the entire screen rect in a
|
||||
// tiled manner, so that it wraps in X and Y.
|
||||
void drawTiledImage(String texName, float textureScale, Vec2D const& offset, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||
|
||||
void drawLine(Vec2F const& begin, Vec2F const end, Vec4B const& color = Vec4B(255, 255, 255, 255), float lineWidth = 1.0f);
|
||||
void drawRect(RectF const& coords, Vec4B const& color = Vec4B(255, 255, 255, 255));
|
||||
void drawPoly(PolyF const& poly, Vec4B const& color = Vec4B(255, 255, 255, 255), float lineWidth = 1.0f);
|
||||
void drawTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& poly, Vec4B const& color = Vec4B(255, 255, 255, 255));
|
||||
|
||||
void drawText(String s, TextPositioning position, unsigned fontSize, Vec4B const& color = Vec4B(255, 255, 255, 255), FontMode mode = FontMode::Normal, float lineSpacing = Star::DefaultLineSpacing);
|
||||
|
||||
protected:
|
||||
void renderImpl() override;
|
||||
|
||||
void renderImage(Vec2F const& renderingOffset, String const& texName, Vec2F const& position, float scale, Vec4B const& color, bool centered);
|
||||
void renderImageRect(Vec2F const& renderingOffset, String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color);
|
||||
void renderDrawable(Vec2F const& renderingOffset, Drawable drawable, Vec2F const& screenPos);
|
||||
void renderTiledImage(Vec2F const& renderingOffset, String const& texName, float textureScale, Vec2D const& offset, RectF const& screenCoords, Vec4B const& color);
|
||||
void renderLine(Vec2F const& renderingOffset, Vec2F const& begin, Vec2F const end, Vec4B const& color, float lineWidth);
|
||||
void renderRect(Vec2F const& renderingOffset, RectF const& coords, Vec4B const& color);
|
||||
void renderPoly(Vec2F const& renderingOffset, PolyF poly, Vec4B const& color, float lineWidth);
|
||||
void renderTriangles(Vec2F const& renderingOffset, List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color);
|
||||
void renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing);
|
||||
|
||||
private:
|
||||
bool m_captureKeyboard;
|
||||
bool m_captureMouse;
|
||||
Vec2I m_mousePosition;
|
||||
List<ClickEvent> m_clickEvents;
|
||||
List<KeyEvent> m_keyEvents;
|
||||
|
||||
typedef tuple<RectF, Vec4B> RectOp;
|
||||
typedef tuple<String, Vec2F, float, Vec4B, bool> ImageOp;
|
||||
typedef tuple<String, RectF, RectF, Vec4B> ImageRectOp;
|
||||
typedef tuple<Drawable, Vec2F> DrawableOp;
|
||||
typedef tuple<String, float, Vec2D, RectF, Vec4B> TiledImageOp;
|
||||
typedef tuple<Vec2F, Vec2F, Vec4B, float> LineOp;
|
||||
typedef tuple<PolyF, Vec4B, float> PolyOp;
|
||||
typedef tuple<List<tuple<Vec2F, Vec2F, Vec2F>>, Vec4B> TrianglesOp;
|
||||
typedef tuple<String, TextPositioning, unsigned, Vec4B, FontMode, float> TextOp;
|
||||
|
||||
typedef MVariant<RectOp, ImageOp, ImageRectOp, DrawableOp, TiledImageOp, LineOp, PolyOp, TrianglesOp, TextOp> RenderOp;
|
||||
List<RenderOp> m_renderOps;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
36
source/windowing/StarFlowLayout.cpp
Normal file
36
source/windowing/StarFlowLayout.cpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#include "StarFlowLayout.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
FlowLayout::FlowLayout() : m_wrap(true) {}
|
||||
|
||||
void FlowLayout::update() {
|
||||
Layout::update();
|
||||
|
||||
int consumedWidth = 0;
|
||||
int rowHeight = 0;
|
||||
Vec2I currentOffset = {0, size()[1]};
|
||||
for (auto child : m_members) {
|
||||
if (m_wrap && consumedWidth + child->size()[0] > size()[0] && consumedWidth != 0) { // wrapping
|
||||
currentOffset[0] = 0;
|
||||
consumedWidth = 0;
|
||||
currentOffset[1] -= rowHeight + m_spacing[1];
|
||||
}
|
||||
if (rowHeight < child->size()[1]) {
|
||||
rowHeight = child->size()[1];
|
||||
}
|
||||
child->setPosition(Vec2I{currentOffset[0], currentOffset[1] - child->size()[1]});
|
||||
consumedWidth += child->size()[0] + m_spacing[0];
|
||||
currentOffset[0] = consumedWidth;
|
||||
}
|
||||
}
|
||||
|
||||
void FlowLayout::setSpacing(Vec2I const& spacing) {
|
||||
m_spacing = spacing;
|
||||
}
|
||||
|
||||
void FlowLayout::setWrapping(bool wrap) {
|
||||
m_wrap = wrap;
|
||||
}
|
||||
|
||||
}
|
25
source/windowing/StarFlowLayout.hpp
Normal file
25
source/windowing/StarFlowLayout.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#ifndef STAR_FLOW_LAYOUT_HPP
|
||||
#define STAR_FLOW_LAYOUT_HPP
|
||||
|
||||
#include "StarLayout.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Super simple, only supports left to right, top to bottom flow layouts
|
||||
// currently
|
||||
STAR_CLASS(FlowLayout);
|
||||
class FlowLayout : public Layout {
|
||||
public:
|
||||
FlowLayout();
|
||||
virtual void update() override;
|
||||
void setSpacing(Vec2I const& spacing);
|
||||
void setWrapping(bool wrap);
|
||||
|
||||
private:
|
||||
Vec2I m_spacing;
|
||||
bool m_wrap;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
111
source/windowing/StarFuelWidget.cpp
Normal file
111
source/windowing/StarFuelWidget.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
#include "StarFuelWidget.hpp"
|
||||
#include "StarInterpolation.hpp"
|
||||
#include "StarGameTypes.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
FuelWidget::FuelWidget() {
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
m_fontSize = assets->json("/interface.config:font.buttonSize").toInt();
|
||||
|
||||
m_fuelLevel = 0;
|
||||
m_maxLevel = 0;
|
||||
m_potential = 0;
|
||||
m_requested = 0;
|
||||
|
||||
m_pingTimeout = 0;
|
||||
disableScissoring();
|
||||
}
|
||||
|
||||
void FuelWidget::update() {
|
||||
m_pingTimeout -= WorldTimestep;
|
||||
if (m_pingTimeout < 0)
|
||||
m_pingTimeout = 0;
|
||||
}
|
||||
|
||||
void FuelWidget::renderImpl() {
|
||||
context()->resetInterfaceScissorRect();
|
||||
|
||||
Vec2F textureSize = Vec2F(context()->textureSize("/interface/fuel/fuelgauge.png"));
|
||||
RectF entireTex = RectF::withSize({}, textureSize);
|
||||
RectF entirePosition = RectF::withSize(Vec2F(screenPosition()), textureSize);
|
||||
Vec2F textPosition = entirePosition.center();
|
||||
|
||||
float fuel = 1;
|
||||
float fuelPotential = 1;
|
||||
float fuelRequested = 0;
|
||||
if (m_maxLevel > 0) {
|
||||
fuel = std::min(1.0f, m_fuelLevel / m_maxLevel);
|
||||
fuelPotential = std::min(1.0f, (m_fuelLevel + m_potential) / m_maxLevel);
|
||||
fuelRequested = std::min(1.0f, m_requested / m_maxLevel);
|
||||
fuel -= fuelRequested;
|
||||
if (fuel < 0)
|
||||
fuel = 0;
|
||||
}
|
||||
|
||||
auto shift = [](float begin, float end, RectF templ) {
|
||||
RectF result = templ;
|
||||
|
||||
result.min()[0] = lerp(begin, templ.min()[0], templ.max()[0]);
|
||||
result.max()[0] = lerp(end, templ.min()[0], templ.max()[0]);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
if (std::fmod(m_pingTimeout, 0.2f) > 0.1f)
|
||||
context()->drawInterfaceQuad("/interface/fuel/fuelgaugebackgroundflash.png", shift(0, 1, entireTex), shift(0, 1, entirePosition));
|
||||
else
|
||||
context()->drawInterfaceQuad("/interface/fuel/fuelgaugebackground.png", shift(0, 1, entireTex), shift(0, 1, entirePosition));
|
||||
|
||||
context()->drawInterfaceQuad("/interface/fuel/fuelgaugegreen.png",
|
||||
shift(fuel, fuelPotential, entireTex),
|
||||
shift(fuel, fuelPotential, entirePosition));
|
||||
|
||||
context()->drawInterfaceQuad("/interface/fuel/fuelgaugered.png",
|
||||
shift(fuel, fuelRequested, entireTex),
|
||||
shift(fuel, fuelRequested, entirePosition));
|
||||
|
||||
context()->drawInterfaceQuad("/interface/fuel/fuelgauge.png", shift(0, fuel, entireTex), shift(0, fuel, entirePosition));
|
||||
context()->drawInterfaceQuad("/interface/fuel/fuelgaugemarkings.png", shift(0, 1, entireTex), shift(0, 1, entirePosition));
|
||||
|
||||
auto& guiContext = GuiContext::singleton();
|
||||
guiContext.setFontSize(m_fontSize);
|
||||
if (m_potential != 0) {
|
||||
guiContext.setFontColor(Color::White.toRgba());
|
||||
} else if (m_fuelLevel == 0) {
|
||||
if ((m_requested != 0) && (m_requested == m_fuelLevel))
|
||||
guiContext.setFontColor(Color::Orange.toRgba());
|
||||
else
|
||||
guiContext.setFontColor(Color::Red.toRgba());
|
||||
} else {
|
||||
guiContext.setFontColor(Color::White.toRgba());
|
||||
}
|
||||
|
||||
guiContext.renderInterfaceText(strf("Fuel %s/%s", std::min(m_fuelLevel + m_potential, m_maxLevel), (int)m_maxLevel),
|
||||
{textPosition, HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor});
|
||||
}
|
||||
|
||||
void FuelWidget::setCurrentFuelLevel(float amount) {
|
||||
m_fuelLevel = amount;
|
||||
}
|
||||
|
||||
void FuelWidget::setMaxFuelLevel(float amount) {
|
||||
m_maxLevel = amount;
|
||||
}
|
||||
|
||||
void FuelWidget::setPotentialFuelAmount(float amount) {
|
||||
m_potential = amount;
|
||||
}
|
||||
|
||||
void FuelWidget::setRequestedFuelAmount(float amount) {
|
||||
m_requested = amount;
|
||||
}
|
||||
|
||||
void FuelWidget::ping() {
|
||||
m_pingTimeout = 1.0f;
|
||||
}
|
||||
|
||||
}
|
41
source/windowing/StarFuelWidget.hpp
Normal file
41
source/windowing/StarFuelWidget.hpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef STAR_FUEL_WIDGET_HPP
|
||||
#define STAR_FUEL_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(FuelWidget);
|
||||
|
||||
class FuelWidget : public Widget {
|
||||
public:
|
||||
FuelWidget();
|
||||
virtual ~FuelWidget() {}
|
||||
|
||||
virtual void update();
|
||||
|
||||
void setCurrentFuelLevel(float amount);
|
||||
void setMaxFuelLevel(float amount);
|
||||
void setPotentialFuelAmount(float amount);
|
||||
void setRequestedFuelAmount(float amount);
|
||||
|
||||
void ping();
|
||||
|
||||
protected:
|
||||
virtual void renderImpl();
|
||||
|
||||
float m_fuelLevel;
|
||||
float m_maxLevel;
|
||||
float m_potential;
|
||||
float m_requested;
|
||||
|
||||
float m_pingTimeout;
|
||||
|
||||
unsigned m_fontSize;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
427
source/windowing/StarGuiContext.cpp
Normal file
427
source/windowing/StarGuiContext.cpp
Normal file
|
@ -0,0 +1,427 @@
|
|||
#include "StarGuiContext.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarConfiguration.hpp"
|
||||
#include "StarMixer.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarImageMetadataDatabase.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
GuiContext* GuiContext::s_singleton;
|
||||
|
||||
GuiContext* GuiContext::singletonPtr() {
|
||||
return s_singleton;
|
||||
}
|
||||
|
||||
GuiContext& GuiContext::singleton() {
|
||||
if (!s_singleton)
|
||||
throw GuiContextException("GuiContext::singleton() called with no GuiContext instance available");
|
||||
else
|
||||
return *s_singleton;
|
||||
}
|
||||
|
||||
GuiContext::GuiContext(MixerPtr mixer, ApplicationControllerPtr appController) {
|
||||
if (s_singleton)
|
||||
throw GuiContextException("Singleton GuiContext has been constructed twice");
|
||||
|
||||
s_singleton = this;
|
||||
|
||||
m_mixer = move(mixer);
|
||||
m_applicationController = move(appController);
|
||||
|
||||
m_interfaceScale = 1;
|
||||
|
||||
m_shiftHeld = false;
|
||||
|
||||
refreshKeybindings();
|
||||
}
|
||||
|
||||
GuiContext::~GuiContext() {
|
||||
s_singleton = nullptr;
|
||||
}
|
||||
|
||||
void GuiContext::renderInit(RendererPtr renderer) {
|
||||
m_renderer = move(renderer);
|
||||
auto textureGroup = m_renderer->createTextureGroup();
|
||||
m_textureCollection = make_shared<AssetTextureGroup>(textureGroup);
|
||||
m_drawablePainter = make_shared<DrawablePainter>(m_renderer, m_textureCollection);
|
||||
m_textPainter = make_shared<TextPainter>(Root::singleton().assets()->font("/hobo.ttf")->clone(), m_renderer, textureGroup);
|
||||
}
|
||||
|
||||
MixerPtr const& GuiContext::mixer() const {
|
||||
return m_mixer;
|
||||
}
|
||||
|
||||
ApplicationControllerPtr const& GuiContext::applicationController() const {
|
||||
return m_applicationController;
|
||||
}
|
||||
|
||||
RendererPtr const& GuiContext::renderer() const {
|
||||
if (!m_renderer)
|
||||
throw GuiContextException("GuiContext::renderer() called before renderInit");
|
||||
return m_renderer;
|
||||
}
|
||||
|
||||
AssetTextureGroupPtr const& GuiContext::assetTextureGroup() const {
|
||||
if (!m_textureCollection)
|
||||
throw GuiContextException("GuiContext::assetTextureGroup() called before renderInit");
|
||||
return m_textureCollection;
|
||||
}
|
||||
|
||||
TextPainterPtr const& GuiContext::textPainter() const {
|
||||
if (!m_textPainter)
|
||||
throw GuiContextException("GuiContext::textPainter() called before renderInit");
|
||||
return m_textPainter;
|
||||
}
|
||||
|
||||
unsigned GuiContext::windowWidth() const {
|
||||
return renderer()->screenSize()[0];
|
||||
}
|
||||
|
||||
unsigned GuiContext::windowHeight() const {
|
||||
return renderer()->screenSize()[1];
|
||||
}
|
||||
|
||||
Vec2U GuiContext::windowSize() const {
|
||||
return renderer()->screenSize();
|
||||
}
|
||||
|
||||
Vec2U GuiContext::windowInterfaceSize() const {
|
||||
return Vec2U::ceil(Vec2F(windowSize()) / interfaceScale());
|
||||
}
|
||||
|
||||
int GuiContext::interfaceScale() const {
|
||||
return m_interfaceScale;
|
||||
}
|
||||
|
||||
void GuiContext::setInterfaceScale(int interfaceScale) {
|
||||
m_interfaceScale = interfaceScale;
|
||||
}
|
||||
|
||||
Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event) const {
|
||||
auto getInterfacePosition = [this](Vec2I pos) {
|
||||
return Vec2I(pos) / interfaceScale();
|
||||
};
|
||||
|
||||
if (auto mouseMoveEvent = event.ptr<MouseMoveEvent>())
|
||||
return getInterfacePosition(mouseMoveEvent->mousePosition);
|
||||
else if (auto mouseDownEvent = event.ptr<MouseButtonDownEvent>())
|
||||
return getInterfacePosition(mouseDownEvent->mousePosition);
|
||||
else if (auto mouseUpEvent = event.ptr<MouseButtonUpEvent>())
|
||||
return getInterfacePosition(mouseUpEvent->mousePosition);
|
||||
else if (auto mouseWheelEvent = event.ptr<MouseWheelEvent>())
|
||||
return getInterfacePosition(mouseWheelEvent->mousePosition);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
Set<InterfaceAction> GuiContext::actions(InputEvent const& event) const {
|
||||
return m_keyBindings.actions(event);
|
||||
}
|
||||
|
||||
Set<InterfaceAction> GuiContext::actionsForKey(Key key) const {
|
||||
return m_keyBindings.actionsForKey(key);
|
||||
}
|
||||
|
||||
void GuiContext::refreshKeybindings() {
|
||||
m_keyBindings = KeyBindings(Root::singleton().configuration()->get("bindings"));
|
||||
}
|
||||
|
||||
void GuiContext::setInterfaceScissorRect(RectI const& scissor) {
|
||||
renderer()->setScissorRect(RectI(scissor).scaled(interfaceScale()));
|
||||
}
|
||||
|
||||
void GuiContext::resetInterfaceScissorRect() {
|
||||
renderer()->setScissorRect({});
|
||||
}
|
||||
|
||||
Vec2U GuiContext::textureSize(String const& texName) {
|
||||
return assetTextureGroup()->loadTexture(texName)->size();
|
||||
}
|
||||
|
||||
void GuiContext::drawQuad(RectF const& screenCoords, Vec4B const& color) {
|
||||
renderer()->render(renderFlatRect(screenCoords, color, 0.0f));
|
||||
}
|
||||
|
||||
void GuiContext::drawQuad(String const& texName, RectF const& screenCoords, Vec4B const& color) {
|
||||
renderer()->render(renderTexturedRect(assetTextureGroup()->loadTexture(texName), screenCoords, color, 0.0f));
|
||||
}
|
||||
|
||||
void GuiContext::drawQuad(String const& texName, Vec2F const& screenPos, int pixelRatio, Vec4B const& color) {
|
||||
renderer()->render(renderTexturedRect(assetTextureGroup()->loadTexture(texName), screenPos, pixelRatio, color, 0.0f));
|
||||
}
|
||||
|
||||
void GuiContext::drawQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) {
|
||||
renderer()->render(RenderQuad{assetTextureGroup()->loadTexture(texName),
|
||||
RenderVertex{Vec2F(screenCoords.xMin(), screenCoords.yMin()), Vec2F(texCoords.xMin(), texCoords.yMin()), color, 0.0f},
|
||||
RenderVertex{Vec2F(screenCoords.xMax(), screenCoords.yMin()), Vec2F(texCoords.xMax(), texCoords.yMin()), color, 0.0f},
|
||||
RenderVertex{Vec2F(screenCoords.xMax(), screenCoords.yMax()), Vec2F(texCoords.xMax(), texCoords.yMax()), color, 0.0f},
|
||||
RenderVertex{Vec2F(screenCoords.xMin(), screenCoords.yMax()), Vec2F(texCoords.xMin(), texCoords.yMax()), color, 0.0f}});
|
||||
}
|
||||
|
||||
void GuiContext::drawDrawable(Drawable drawable, Vec2F const& screenPos, int pixelRatio, Vec4B const& color) {
|
||||
if (drawable.isLine())
|
||||
drawable.linePart().width *= pixelRatio;
|
||||
|
||||
drawable.scale(pixelRatio);
|
||||
drawable.translate(screenPos);
|
||||
drawable.color *= Color::rgba(color);
|
||||
m_drawablePainter->drawDrawable(drawable);
|
||||
}
|
||||
|
||||
void GuiContext::drawLine(Vec2F const& begin, Vec2F const end, Vec4B const& color, float lineWidth) {
|
||||
Vec2F left = vnorm(Vec2F(end) - Vec2F(begin)).rot90() * lineWidth / 2.0f;
|
||||
renderer()->render(RenderQuad{{},
|
||||
RenderVertex{Vec2F(begin) + left, Vec2F(), color, 0.0f},
|
||||
RenderVertex{Vec2F(begin) - left, Vec2F(), color, 0.0f},
|
||||
RenderVertex{Vec2F(end) - left, Vec2F(), color, 0.0f},
|
||||
RenderVertex{Vec2F(end) + left, Vec2F(), color, 0.0f}});
|
||||
}
|
||||
|
||||
void GuiContext::drawPolyLines(PolyF const& poly, Vec4B const& color, float lineWidth) {
|
||||
for (size_t i = 0; i < poly.sides(); ++i)
|
||||
drawLine(poly.vertex(i), poly.vertex(i + 1), color, lineWidth);
|
||||
}
|
||||
|
||||
void GuiContext::drawTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color) {
|
||||
for (auto poly : triangles) {
|
||||
renderer()->render(RenderTriangle{{},
|
||||
RenderVertex{get<0>(poly), Vec2F(), color, 0.0f},
|
||||
RenderVertex{get<1>(poly), Vec2F(), color, 0.0f},
|
||||
RenderVertex{get<2>(poly), Vec2F(), color, 0.0f}});
|
||||
}
|
||||
}
|
||||
|
||||
void GuiContext::drawInterfaceDrawable(Drawable drawable, Vec2F const& screenPos, Vec4B const& color) {
|
||||
drawDrawable(move(drawable), screenPos * interfaceScale(), (float)interfaceScale(), color);
|
||||
}
|
||||
|
||||
void GuiContext::drawInterfaceLine(Vec2F const& begin, Vec2F const end, Vec4B const& color, float lineWidth) {
|
||||
drawLine(begin * interfaceScale(), end * interfaceScale(), color, lineWidth * interfaceScale());
|
||||
}
|
||||
|
||||
void GuiContext::drawInterfacePolyLines(PolyF poly, Vec4B const& color, float lineWidth) {
|
||||
poly.scale(interfaceScale());
|
||||
drawPolyLines(poly, color, lineWidth * interfaceScale());
|
||||
}
|
||||
|
||||
void GuiContext::drawInterfaceQuad(RectF const& screenCoords, Vec4B const& color) {
|
||||
drawQuad(screenCoords.scaled(interfaceScale()), color);
|
||||
}
|
||||
|
||||
void GuiContext::drawInterfaceQuad(String const& texName, Vec2F const& screenCoords, Vec4B const& color) {
|
||||
drawQuad(texName, screenCoords * interfaceScale(), interfaceScale(), color);
|
||||
}
|
||||
|
||||
void GuiContext::drawInterfaceQuad(String const& texName, Vec2F const& screenCoords, float scale, Vec4B const& color) {
|
||||
drawQuad(texName, screenCoords * interfaceScale(), interfaceScale() * scale, color);
|
||||
}
|
||||
|
||||
void GuiContext::drawInterfaceQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) {
|
||||
drawQuad(texName, texCoords, screenCoords.scaled(interfaceScale()), color);
|
||||
}
|
||||
|
||||
void GuiContext::drawInterfaceTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color) {
|
||||
drawTriangles(triangles.transformed([this](tuple<Vec2F, Vec2F, Vec2F> const& poly) {
|
||||
return tuple<Vec2F, Vec2F, Vec2F>(get<0>(poly) * interfaceScale(), get<1>(poly) * interfaceScale(), get<2>(poly) * interfaceScale());
|
||||
}), color);
|
||||
}
|
||||
|
||||
void GuiContext::drawImageStretchSet(ImageStretchSet const& imageSet, RectF const& screenPos, GuiDirection direction, Vec4B const& color) {
|
||||
int innerSize;
|
||||
int innerOffset = 0;
|
||||
RectF begin, end, inner;
|
||||
if (direction == GuiDirection::Horizontal) {
|
||||
innerSize = screenPos.width();
|
||||
if (imageSet.begin.size())
|
||||
innerOffset = textureSize(imageSet.begin)[0];
|
||||
|
||||
if (imageSet.end.size())
|
||||
innerSize = std::max(0, innerSize - innerOffset - Vec2I(textureSize(imageSet.end))[0]);
|
||||
else
|
||||
innerSize = std::max(0, innerSize - innerOffset);
|
||||
|
||||
if (imageSet.begin.size())
|
||||
begin = RectF::withSize(screenPos.min(), Vec2F(textureSize(imageSet.begin)[0], screenPos.height()));
|
||||
else
|
||||
begin = RectF::withSize(screenPos.min(), Vec2F(0, screenPos.height()));
|
||||
|
||||
inner = RectF::withSize(screenPos.min() + Vec2F(innerOffset, 0), Vec2F(innerSize, screenPos.height()));
|
||||
if (imageSet.end.size())
|
||||
end = RectF::withSize(screenPos.min() + Vec2F(innerOffset + innerSize, 0), Vec2F(textureSize(imageSet.end)[0], screenPos.height()));
|
||||
else
|
||||
end = RectF::withSize(screenPos.min(), Vec2F(0, screenPos.height()));
|
||||
|
||||
} else {
|
||||
innerSize = screenPos.height();
|
||||
if (imageSet.begin.size())
|
||||
innerOffset = textureSize(imageSet.begin)[1];
|
||||
|
||||
if (imageSet.end.size())
|
||||
innerSize = std::max(0, innerSize - innerOffset - Vec2I(textureSize(imageSet.end))[1]);
|
||||
else
|
||||
innerSize = std::max(0, innerSize - innerOffset);
|
||||
|
||||
if (imageSet.begin.size())
|
||||
begin = RectF::withSize(screenPos.min(), Vec2F(screenPos.width(), textureSize(imageSet.begin)[1]));
|
||||
else
|
||||
begin = RectF::withSize(screenPos.min(), Vec2F(screenPos.width(), 0));
|
||||
|
||||
inner = RectF::withSize(screenPos.min() + Vec2F(0, innerOffset), Vec2F(screenPos.width(), innerSize));
|
||||
if (imageSet.end.size()) {
|
||||
end = RectF::withSize(screenPos.min() + Vec2F(0, innerOffset + innerSize), Vec2F(screenPos.width(), textureSize(imageSet.end)[1]));
|
||||
} else {
|
||||
end = RectF::withSize(screenPos.min(), Vec2F(screenPos.width(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (imageSet.begin.size())
|
||||
drawInterfaceQuad(imageSet.begin, RectF(Vec2F(), Vec2F(textureSize(imageSet.begin))), begin, color);
|
||||
|
||||
if (imageSet.type == ImageStretchSet::ImageStretchType::Stretch) {
|
||||
drawInterfaceQuad(imageSet.inner, RectF(Vec2F(), Vec2F(textureSize(imageSet.inner))), inner, color);
|
||||
|
||||
} else {
|
||||
int position = 0;
|
||||
auto texSize = Vec2F(textureSize(imageSet.inner));
|
||||
if (direction == GuiDirection::Horizontal) {
|
||||
starAssert(texSize[0] > 0);
|
||||
while (position < inner.width()) {
|
||||
RectF partialImage = RectF::withSize(Vec2F(), Vec2F(std::min(inner.width() - position, texSize[0]), texSize[1]));
|
||||
drawInterfaceQuad(imageSet.inner, partialImage, RectF::withSize(inner.min() + Vec2F(position, 0), partialImage.size()), color);
|
||||
position += partialImage.size()[0];
|
||||
}
|
||||
} else {
|
||||
starAssert(texSize[1] > 0);
|
||||
while (position < inner.height()) {
|
||||
RectF partialImage = RectF::withSize(
|
||||
Vec2F(0, max(0.0f, texSize[1] - (inner.height() - position))),
|
||||
Vec2F(texSize[0], std::min(inner.height() - position, texSize[1])));
|
||||
drawInterfaceQuad(imageSet.inner, partialImage, RectF::withSize(inner.min() + Vec2F(0, position), partialImage.size()), color);
|
||||
position += partialImage.size()[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (imageSet.end.size())
|
||||
drawInterfaceQuad(imageSet.end, RectF(Vec2F(), Vec2F(textureSize(imageSet.end))), end, color);
|
||||
}
|
||||
|
||||
RectF GuiContext::renderText(String const& s, TextPositioning const& position) {
|
||||
return textPainter()->renderText(s, position);
|
||||
}
|
||||
|
||||
RectF GuiContext::renderInterfaceText(String const& s, TextPositioning const& position) {
|
||||
auto res = renderText(s, {
|
||||
position.pos * interfaceScale(),
|
||||
position.hAnchor,
|
||||
position.vAnchor,
|
||||
position.wrapWidth.apply(bind(std::multiplies<int>(), _1, interfaceScale())),
|
||||
position.charLimit
|
||||
});
|
||||
return RectF(res).scaled(1.0f / interfaceScale());
|
||||
}
|
||||
|
||||
RectF GuiContext::determineTextSize(String const& s, TextPositioning const& positioning) {
|
||||
return textPainter()->determineTextSize(s, positioning);
|
||||
}
|
||||
|
||||
RectF GuiContext::determineInterfaceTextSize(String const& s, TextPositioning const& positioning) {
|
||||
auto res = determineTextSize(s, {
|
||||
positioning.pos * interfaceScale(),
|
||||
positioning.hAnchor,
|
||||
positioning.vAnchor,
|
||||
positioning.wrapWidth.apply(bind(std::multiplies<int>(), _1, interfaceScale()))
|
||||
});
|
||||
return RectF(res).scaled(1.0f / interfaceScale());
|
||||
}
|
||||
|
||||
void GuiContext::setFontSize(unsigned size) {
|
||||
setFontSize(size, interfaceScale());
|
||||
}
|
||||
|
||||
void GuiContext::setFontSize(unsigned size, int pixelRatio) {
|
||||
textPainter()->setFontSize(size * pixelRatio);
|
||||
}
|
||||
|
||||
void GuiContext::setFontColor(Vec4B const& color) {
|
||||
textPainter()->setFontColor(color);
|
||||
}
|
||||
|
||||
void GuiContext::setFontMode(FontMode mode) {
|
||||
textPainter()->setMode(mode);
|
||||
}
|
||||
|
||||
void GuiContext::setFontProcessingDirectives(String const& directives) {
|
||||
textPainter()->setProcessingDirectives(directives);
|
||||
}
|
||||
|
||||
void GuiContext::setLineSpacing(float lineSpacing) {
|
||||
textPainter()->setLineSpacing(lineSpacing);
|
||||
}
|
||||
|
||||
void GuiContext::setDefaultLineSpacing() {
|
||||
textPainter()->setLineSpacing(DefaultLineSpacing);
|
||||
}
|
||||
|
||||
int GuiContext::stringWidth(String const& s) {
|
||||
return textPainter()->stringWidth(s);
|
||||
}
|
||||
|
||||
int GuiContext::stringInterfaceWidth(String const& s) {
|
||||
if (interfaceScale()) {
|
||||
// font size is already adjusted UP by interfaceScale, so we have to adjust
|
||||
// it back down
|
||||
return stringWidth(s) / interfaceScale();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
StringList GuiContext::wrapText(String const& s, Maybe<unsigned> wrapWidth) {
|
||||
return textPainter()->wrapText(s, wrapWidth);
|
||||
}
|
||||
|
||||
StringList GuiContext::wrapInterfaceText(String const& s, Maybe<unsigned> wrapWidth) {
|
||||
if (wrapWidth)
|
||||
*wrapWidth *= interfaceScale();
|
||||
return wrapText(s, wrapWidth);
|
||||
}
|
||||
|
||||
bool GuiContext::shiftHeld() const {
|
||||
return m_shiftHeld;
|
||||
}
|
||||
|
||||
void GuiContext::setShiftHeld(bool held) {
|
||||
m_shiftHeld = held;
|
||||
}
|
||||
|
||||
void GuiContext::playAudio(AudioInstancePtr audioInstance) {
|
||||
m_mixer->play(audioInstance);
|
||||
}
|
||||
|
||||
void GuiContext::playAudio(String const& audioAsset, int loops, float volume) {
|
||||
auto assets = Root::singleton().assets();
|
||||
auto config = Root::singleton().configuration();
|
||||
auto audioInstance = make_shared<AudioInstance>(*assets->audio(audioAsset));
|
||||
audioInstance->setVolume(volume);
|
||||
audioInstance->setLoops(loops);
|
||||
m_mixer->play(move(audioInstance));
|
||||
}
|
||||
|
||||
String GuiContext::getClipboard() const {
|
||||
return m_applicationController->getClipboard().value();
|
||||
}
|
||||
|
||||
void GuiContext::setClipboard(String text) {
|
||||
m_applicationController->setClipboard(move(text));
|
||||
}
|
||||
|
||||
void GuiContext::cleanup() {
|
||||
int64_t textureTimeout = Root::singleton().assets()->json("/rendering.config:textureTimeout").toInt();
|
||||
if (m_textureCollection)
|
||||
m_textureCollection->cleanup(textureTimeout);
|
||||
if (m_textPainter)
|
||||
m_textPainter->cleanup(textureTimeout);
|
||||
}
|
||||
|
||||
}
|
145
source/windowing/StarGuiContext.hpp
Normal file
145
source/windowing/StarGuiContext.hpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
#ifndef STAR_GUI_CONTEXT_HPP
|
||||
#define STAR_GUI_CONTEXT_HPP
|
||||
|
||||
#include "StarApplicationController.hpp"
|
||||
#include "StarTextPainter.hpp"
|
||||
#include "StarDrawablePainter.hpp"
|
||||
#include "StarAssetTextureGroup.hpp"
|
||||
#include "StarInputEvent.hpp"
|
||||
#include "StarDrawable.hpp"
|
||||
#include "StarThread.hpp"
|
||||
#include "StarGuiTypes.hpp"
|
||||
#include "StarRenderer.hpp"
|
||||
#include "StarKeyBindings.hpp"
|
||||
#include "StarMixer.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_EXCEPTION(GuiContextException, StarException);
|
||||
|
||||
class GuiContext {
|
||||
public:
|
||||
// Get pointer to the singleton root instance, if it exists. Otherwise,
|
||||
// returns nullptr.
|
||||
static GuiContext* singletonPtr();
|
||||
|
||||
// Gets reference to GuiContext singleton, throws GuiContextException if root
|
||||
// is not initialized.
|
||||
static GuiContext& singleton();
|
||||
|
||||
GuiContext(MixerPtr mixer, ApplicationControllerPtr appController);
|
||||
~GuiContext();
|
||||
|
||||
GuiContext(GuiContext const&) = delete;
|
||||
GuiContext& operator=(GuiContext const&) = delete;
|
||||
|
||||
void renderInit(RendererPtr renderer);
|
||||
|
||||
MixerPtr const& mixer() const;
|
||||
ApplicationControllerPtr const& applicationController() const;
|
||||
RendererPtr const& renderer() const;
|
||||
AssetTextureGroupPtr const& assetTextureGroup() const;
|
||||
TextPainterPtr const& textPainter() const;
|
||||
|
||||
unsigned windowWidth() const;
|
||||
unsigned windowHeight() const;
|
||||
|
||||
Vec2U windowSize() const;
|
||||
Vec2U windowInterfaceSize() const;
|
||||
|
||||
int interfaceScale() const;
|
||||
void setInterfaceScale(int interfaceScale);
|
||||
|
||||
Maybe<Vec2I> mousePosition(InputEvent const& event) const;
|
||||
|
||||
Set<InterfaceAction> actions(InputEvent const& event) const;
|
||||
// used to cancel chorded inputs on KeyUp
|
||||
Set<InterfaceAction> actionsForKey(Key key) const;
|
||||
void refreshKeybindings();
|
||||
|
||||
// Drawing wrappers to internal renderers. Automatically loads textures before drawing.
|
||||
|
||||
void setInterfaceScissorRect(RectI const& scissor);
|
||||
void resetInterfaceScissorRect();
|
||||
|
||||
Vec2U textureSize(String const& texName);
|
||||
|
||||
void drawQuad(RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||
void drawQuad(String const& texName, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||
void drawQuad(String const& texName, Vec2F const& screenPos, int pixelRatio, Vec4B const& color = Vec4B::filled(255));
|
||||
void drawQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||
|
||||
void drawDrawable(Drawable drawable, Vec2F const& screenPos, int pixelRatio, Vec4B const& color = Vec4B::filled(255));
|
||||
|
||||
void drawLine(Vec2F const& begin, Vec2F const end, Vec4B const& color, float lineWidth = 1);
|
||||
void drawPolyLines(PolyF const& poly, Vec4B const& color, float lineWidth = 1);
|
||||
|
||||
void drawTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color);
|
||||
|
||||
void drawInterfaceDrawable(Drawable drawable, Vec2F const& screenPos, Vec4B const& color = Vec4B::filled(255));
|
||||
|
||||
void drawInterfaceLine(Vec2F const& begin, Vec2F const end, Vec4B const& color, float lineWidth = 1);
|
||||
void drawInterfacePolyLines(PolyF poly, Vec4B const& color, float lineWidth = 1);
|
||||
|
||||
void drawInterfaceTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color);
|
||||
|
||||
void drawInterfaceQuad(RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||
void drawInterfaceQuad(String const& texName, Vec2F const& screenPos, Vec4B const& color = Vec4B::filled(255));
|
||||
void drawInterfaceQuad(String const& texName, Vec2F const& screenPos, float scale, Vec4B const& color = Vec4B::filled(255));
|
||||
void drawInterfaceQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||
|
||||
void drawImageStretchSet(ImageStretchSet const& imageSet, RectF const& screenPos, GuiDirection direction = GuiDirection::Horizontal, Vec4B const& color = Vec4B::filled(255));
|
||||
|
||||
RectF renderText(String const& s, TextPositioning const& positioning);
|
||||
RectF renderInterfaceText(String const& s, TextPositioning const& positioning);
|
||||
|
||||
RectF determineTextSize(String const& s, TextPositioning const& positioning);
|
||||
RectF determineInterfaceTextSize(String const& s, TextPositioning const& positioning);
|
||||
|
||||
void setFontSize(unsigned size);
|
||||
void setFontSize(unsigned size, int pixelRatio);
|
||||
void setFontColor(Vec4B const& color);
|
||||
void setFontMode(FontMode mode);
|
||||
void setFontProcessingDirectives(String const& directives);
|
||||
|
||||
void setLineSpacing(float lineSpacing);
|
||||
void setDefaultLineSpacing();
|
||||
|
||||
int stringWidth(String const& s);
|
||||
int stringInterfaceWidth(String const& s);
|
||||
|
||||
StringList wrapText(String const& s, Maybe<unsigned> wrapWidth);
|
||||
StringList wrapInterfaceText(String const& s, Maybe<unsigned> wrapWidth);
|
||||
|
||||
void playAudio(AudioInstancePtr audioInstance);
|
||||
void playAudio(String const& audioAsset, int loops = 0, float volume = 1);
|
||||
|
||||
bool shiftHeld() const;
|
||||
void setShiftHeld(bool held);
|
||||
|
||||
String getClipboard() const;
|
||||
void setClipboard(String text);
|
||||
|
||||
void cleanup();
|
||||
|
||||
private:
|
||||
static GuiContext* s_singleton;
|
||||
|
||||
MixerPtr m_mixer;
|
||||
ApplicationControllerPtr m_applicationController;
|
||||
RendererPtr m_renderer;
|
||||
|
||||
AssetTextureGroupPtr m_textureCollection;
|
||||
TextPainterPtr m_textPainter;
|
||||
DrawablePainterPtr m_drawablePainter;
|
||||
|
||||
KeyBindings m_keyBindings;
|
||||
|
||||
int m_interfaceScale;
|
||||
|
||||
bool m_shiftHeld;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
88
source/windowing/StarGuiReader.cpp
Normal file
88
source/windowing/StarGuiReader.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
#include "StarGuiReader.hpp"
|
||||
#include "StarPane.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
GuiReader::GuiReader() {
|
||||
m_constructors["background"] = [=](
|
||||
String const& name, Json const& config) { return backgroundHandler(name, config); };
|
||||
m_constructors["button"] = [=](String const& name, Json const& config) { return buttonHandler(name, config); };
|
||||
m_constructors["itemslot"] = [=](String const& name, Json const& config) { return itemSlotHandler(name, config); };
|
||||
m_constructors["itemgrid"] = [=](String const& name, Json const& config) { return itemGridHandler(name, config); };
|
||||
m_constructors["list"] = [=](String const& name, Json const& config) { return listHandler(name, config); };
|
||||
m_constructors["panefeature"] = [=](
|
||||
String const& name, Json const& config) { return paneFeatureHandler(name, config); };
|
||||
m_constructors["radioGroup"] = [=](
|
||||
String const& name, Json const& config) { return radioGroupHandler(name, config); };
|
||||
m_constructors["spinner"] = [=](String const& name, Json const& config) { return spinnerHandler(name, config); };
|
||||
m_constructors["slider"] = [=](String const& name, Json const& config) { return sliderHandler(name, config); };
|
||||
m_constructors["textbox"] = [=](String const& name, Json const& config) { return textboxHandler(name, config); };
|
||||
m_constructors["title"] = [=](String const& name, Json const& config) { return titleHandler(name, config); };
|
||||
m_constructors["stack"] = [=](String const& name, Json const& config) { return stackHandler(name, config); };
|
||||
m_constructors["tabSet"] = [=](String const& name, Json const& config) { return tabSetHandler(name, config); };
|
||||
m_constructors["scrollArea"] = [=](
|
||||
String const& name, Json const& config) { return scrollAreaHandler(name, config); };
|
||||
}
|
||||
|
||||
WidgetConstructResult GuiReader::titleHandler(String const&, Json const& config) {
|
||||
if (m_pane) {
|
||||
String title = config.getString("title", "");
|
||||
String subtitle = config.getString("subtitle", "");
|
||||
Json iconConfig = config.get("icon", Json());
|
||||
|
||||
if (iconConfig.isNull()) {
|
||||
m_pane->setTitleString(title, subtitle);
|
||||
} else {
|
||||
try {
|
||||
String type = iconConfig.getString("type");
|
||||
auto icon = m_constructors.get(type)("icon", iconConfig);
|
||||
if (!icon.obj)
|
||||
throw WidgetParserException(strf("Title specified incompatible icon type: %s", type));
|
||||
m_pane->setTitle(icon.obj, title, subtitle);
|
||||
} catch (JsonException const& e) {
|
||||
throw WidgetParserException(strf("Malformed icon configuration data in title. %s", outputException(e, false)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw StarException("Only Pane controls support the 'title' command");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
WidgetConstructResult GuiReader::paneFeatureHandler(String const&, Json const& config) {
|
||||
if (m_pane) {
|
||||
m_pane->setAnchor(PaneAnchorNames.getLeft(config.getString("anchor", "None")));
|
||||
if (config.contains("offset"))
|
||||
m_pane->setAnchorOffset(jsonToVec2I(config.get("offset")));
|
||||
if (config.getBool("positionLocked", false))
|
||||
m_pane->lockPosition();
|
||||
} else {
|
||||
throw StarException("Only Pane controls support the 'panefeature' command");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
WidgetConstructResult GuiReader::backgroundHandler(String const&, Json const& config) {
|
||||
if (m_pane) {
|
||||
String header, body, footer;
|
||||
try {
|
||||
header = config.getString("fileHeader", "");
|
||||
body = config.getString("fileBody", "");
|
||||
footer = config.getString("fileFooter", "");
|
||||
} catch (MapException const& e) {
|
||||
throw WidgetParserException(
|
||||
strf("Malformed gui json, missing a required value in the map. %s", outputException(e, false)));
|
||||
}
|
||||
|
||||
m_pane->setBG(header, body, footer);
|
||||
} else {
|
||||
throw StarException("Only Pane controls support the 'background' command");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
22
source/windowing/StarGuiReader.hpp
Normal file
22
source/windowing/StarGuiReader.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef STAR_GUI_READER_HPP
|
||||
#define STAR_GUI_READER_HPP
|
||||
|
||||
#include "StarWidgetParsing.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_EXCEPTION(GUIBuilderException, StarException);
|
||||
|
||||
class GuiReader : public WidgetParser {
|
||||
public:
|
||||
GuiReader();
|
||||
|
||||
protected:
|
||||
WidgetConstructResult titleHandler(String const& _unused, Json const& config);
|
||||
WidgetConstructResult paneFeatureHandler(String const& _unused, Json const& config);
|
||||
WidgetConstructResult backgroundHandler(String const& _unused, Json const& config);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
29
source/windowing/StarGuiTypes.cpp
Normal file
29
source/windowing/StarGuiTypes.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "StarGuiTypes.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
bool ImageStretchSet::fullyPopulated() const {
|
||||
return inner.size() && begin.size() && end.size();
|
||||
}
|
||||
|
||||
GuiDirection otherDirection(GuiDirection direction) {
|
||||
switch (direction) {
|
||||
case GuiDirection::Horizontal:
|
||||
return GuiDirection::Vertical;
|
||||
case GuiDirection::Vertical:
|
||||
return GuiDirection::Horizontal;
|
||||
default:
|
||||
starAssert(false);
|
||||
return (GuiDirection)-1;
|
||||
}
|
||||
}
|
||||
|
||||
EnumMap<GuiDirection> const GuiDirectionNames{
|
||||
{GuiDirection::Horizontal, "horizontal"}, {GuiDirection::Vertical, "vertical"},
|
||||
};
|
||||
|
||||
String rarityBorder(Rarity rarity) {
|
||||
return strf("/interface/inventory/itemborder%s.png", RarityNames.getRight(rarity).toLower());
|
||||
}
|
||||
|
||||
}
|
51
source/windowing/StarGuiTypes.hpp
Normal file
51
source/windowing/StarGuiTypes.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#ifndef STAR_WIDGET_UTILITIES_HPP
|
||||
#define STAR_WIDGET_UTILITIES_HPP
|
||||
|
||||
#include "StarString.hpp"
|
||||
#include "StarVector.hpp"
|
||||
#include "StarBiMap.hpp"
|
||||
#include "StarGameTypes.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
struct ImageStretchSet {
|
||||
// how does inner fill up space?
|
||||
enum class ImageStretchType {
|
||||
Stretch,
|
||||
Repeat
|
||||
};
|
||||
|
||||
String begin;
|
||||
String inner;
|
||||
String end;
|
||||
ImageStretchType type;
|
||||
|
||||
bool fullyPopulated() const;
|
||||
};
|
||||
|
||||
enum class GuiDirection {
|
||||
Horizontal,
|
||||
Vertical
|
||||
};
|
||||
extern EnumMap<GuiDirection> const GuiDirectionNames;
|
||||
|
||||
GuiDirection otherDirection(GuiDirection direction);
|
||||
|
||||
template <typename T>
|
||||
T directionalValueFromVector(GuiDirection direction, Vector<T, 2> const& vec);
|
||||
|
||||
String rarityBorder(Rarity rarity);
|
||||
|
||||
template <typename T>
|
||||
T& directionalValueFromVector(GuiDirection direction, Vector<T, 2> const& vec) {
|
||||
switch (direction) {
|
||||
case GuiDirection::Horizontal:
|
||||
return vec[0];
|
||||
case GuiDirection::Vertical:
|
||||
return vec[1];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
14
source/windowing/StarImageStretchWidget.cpp
Normal file
14
source/windowing/StarImageStretchWidget.cpp
Normal file
|
@ -0,0 +1,14 @@
|
|||
#include "StarImageStretchWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ImageStretchWidget::ImageStretchWidget(ImageStretchSet const& imageStretchSet, GuiDirection direction)
|
||||
: m_imageStretchSet(imageStretchSet), m_direction(direction) {
|
||||
|
||||
}
|
||||
|
||||
void ImageStretchWidget::renderImpl() {
|
||||
context()->drawImageStretchSet(m_imageStretchSet, RectF(screenBoundRect()), m_direction);
|
||||
}
|
||||
|
||||
}
|
25
source/windowing/StarImageStretchWidget.hpp
Normal file
25
source/windowing/StarImageStretchWidget.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#ifndef STAR_IMAGE_STRETCH_WIDGET_HPP
|
||||
#define STAR_IMAGE_STRETCH_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ImageStretchWidget);
|
||||
|
||||
class ImageStretchWidget : public Widget {
|
||||
public:
|
||||
ImageStretchWidget(ImageStretchSet const& imageStretchSet, GuiDirection direction);
|
||||
virtual ~ImageStretchWidget() {}
|
||||
|
||||
protected:
|
||||
virtual void renderImpl();
|
||||
|
||||
private:
|
||||
ImageStretchSet m_imageStretchSet;
|
||||
GuiDirection m_direction;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
130
source/windowing/StarImageWidget.cpp
Normal file
130
source/windowing/StarImageWidget.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
#include "StarImageWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ImageWidget::ImageWidget(String const& image) {
|
||||
m_centered = false;
|
||||
m_trim = false;
|
||||
m_scale = 1;
|
||||
m_rotation = 0;
|
||||
m_maxSize = Vec2I{4096, 4096};
|
||||
m_minSize = Vec2I{0, 0};
|
||||
m_offset = Vec2I{0, 0};
|
||||
setImage(image);
|
||||
}
|
||||
|
||||
void ImageWidget::renderImpl() {
|
||||
auto screenPos = screenPosition();
|
||||
for (auto const& drawable : m_drawables)
|
||||
context()->drawDrawable(drawable, Vec2F(screenPos) * context()->interfaceScale() + Vec2F(m_offset), context()->interfaceScale(), Vec4B::filled(255));
|
||||
}
|
||||
|
||||
bool ImageWidget::interactive() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImageWidget::setImage(String const& image) {
|
||||
if (image.empty())
|
||||
setDrawables({});
|
||||
else
|
||||
setDrawables({Drawable::makeImage(image, 1.0f, false, Vec2F())});
|
||||
}
|
||||
|
||||
void ImageWidget::setScale(float scale) {
|
||||
m_scale = scale;
|
||||
transformDrawables();
|
||||
}
|
||||
|
||||
void ImageWidget::setRotation(float rotation) {
|
||||
m_rotation = rotation;
|
||||
transformDrawables();
|
||||
}
|
||||
|
||||
String ImageWidget::image() const {
|
||||
if (!m_drawables.size())
|
||||
return "";
|
||||
return m_drawables[0].imagePart().image;
|
||||
}
|
||||
|
||||
void ImageWidget::setDrawables(List<Drawable> drawables) {
|
||||
m_baseDrawables = move(drawables);
|
||||
|
||||
transformDrawables();
|
||||
}
|
||||
|
||||
Vec2I ImageWidget::offset() {
|
||||
return m_offset;
|
||||
}
|
||||
|
||||
void ImageWidget::setOffset(Vec2I const& offset) {
|
||||
m_offset = offset;
|
||||
}
|
||||
|
||||
bool ImageWidget::centered() {
|
||||
return m_centered;
|
||||
}
|
||||
|
||||
void ImageWidget::setCentered(bool centered) {
|
||||
m_centered = centered;
|
||||
transformDrawables();
|
||||
}
|
||||
|
||||
bool ImageWidget::trim() {
|
||||
return m_trim;
|
||||
}
|
||||
|
||||
void ImageWidget::setTrim(bool trim) {
|
||||
m_trim = trim;
|
||||
transformDrawables();
|
||||
}
|
||||
|
||||
void ImageWidget::setMaxSize(Vec2I const& size) {
|
||||
m_maxSize = size;
|
||||
transformDrawables();
|
||||
}
|
||||
|
||||
void ImageWidget::setMinSize(Vec2I const& size) {
|
||||
m_minSize = size;
|
||||
transformDrawables();
|
||||
}
|
||||
|
||||
RectI ImageWidget::screenBoundRect() const {
|
||||
RectI base = RectI::withSize(screenPosition(), size());
|
||||
if (m_centered) {
|
||||
base.translate(-Vec2I::floor(size() / 2));
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
void ImageWidget::transformDrawables() {
|
||||
m_drawables.clear();
|
||||
for (auto drawable : m_baseDrawables)
|
||||
m_drawables.append(Drawable(drawable));
|
||||
|
||||
if (m_rotation != 0)
|
||||
Drawable::rotateAll(m_drawables, m_rotation);
|
||||
|
||||
// When 'centered' is true, the drawables provided are pre-centered
|
||||
// around 0,0. Tooltips use this, as well as quest dialog portraits.
|
||||
if (m_centered) {
|
||||
auto boundBox = Drawable::boundBoxAll(m_drawables, m_trim);
|
||||
Drawable::translateAll(m_drawables, -boundBox.center());
|
||||
}
|
||||
|
||||
auto boundBox = Drawable::boundBoxAll(m_drawables, m_trim);
|
||||
auto size = boundBox.size().piecewiseMax({0, 0});
|
||||
if (size[0] && size[1]) {
|
||||
if ((size[0] > m_maxSize[0]) || (size[1] > m_maxSize[1])) {
|
||||
m_scale = std::min(m_maxSize[0] / size[0], m_maxSize[1] / size[1]);
|
||||
} else if ((size[0] < m_minSize[0]) || (size[1] < m_minSize[1])) {
|
||||
m_scale = std::min(m_minSize[0] / size[0], m_minSize[1] / size[1]);
|
||||
}
|
||||
}
|
||||
|
||||
Drawable::scaleAll(m_drawables, m_scale);
|
||||
|
||||
setSize(Vec2I((size * m_scale).ceil()));
|
||||
}
|
||||
|
||||
}
|
51
source/windowing/StarImageWidget.hpp
Normal file
51
source/windowing/StarImageWidget.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#ifndef STAR_IMAGE_WIDGET_HPP
|
||||
#define STAR_IMAGE_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ImageWidget);
|
||||
class ImageWidget : public Widget {
|
||||
public:
|
||||
ImageWidget(String const& image = {});
|
||||
|
||||
bool interactive() const override;
|
||||
void setImage(String const& image);
|
||||
void setScale(float scale);
|
||||
void setRotation(float rotation);
|
||||
String image() const;
|
||||
|
||||
void setDrawables(List<Drawable> drawables);
|
||||
Vec2I offset();
|
||||
void setOffset(Vec2I const& offset);
|
||||
bool centered();
|
||||
void setCentered(bool centered);
|
||||
bool trim();
|
||||
void setTrim(bool trim);
|
||||
|
||||
void setMaxSize(Vec2I const& size);
|
||||
void setMinSize(Vec2I const& size);
|
||||
|
||||
RectI screenBoundRect() const override;
|
||||
|
||||
protected:
|
||||
void renderImpl() override;
|
||||
|
||||
private:
|
||||
void transformDrawables();
|
||||
|
||||
List<Drawable> m_baseDrawables;
|
||||
List<Drawable> m_drawables;
|
||||
bool m_centered;
|
||||
bool m_trim;
|
||||
float m_scale;
|
||||
float m_rotation;
|
||||
Vec2I m_offset;
|
||||
Vec2I m_maxSize;
|
||||
Vec2I m_minSize;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
289
source/windowing/StarItemGridWidget.cpp
Normal file
289
source/windowing/StarItemGridWidget.cpp
Normal file
|
@ -0,0 +1,289 @@
|
|||
#include "StarItemGridWidget.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ItemGridWidget::ItemGridWidget(ItemBagConstPtr bag, Vec2I const& dimensions, Vec2I const& spacing, String const& backingImage, unsigned bagOffset)
|
||||
: ItemGridWidget(bag, dimensions, {spacing[0], 0}, {0, spacing[1]}, backingImage, bagOffset) {}
|
||||
|
||||
ItemGridWidget::ItemGridWidget(ItemBagConstPtr bag, Vec2I const& dimensions, Vec2I const& rowSpacing, Vec2I const& columnSpacing, String const& backingImage, unsigned bagOffset)
|
||||
: m_bagOffset(bagOffset), m_dimensions(dimensions), m_rowSpacing(rowSpacing), m_columnSpacing(columnSpacing), m_backingImage(backingImage) {
|
||||
m_selectedIndex = 0;
|
||||
m_progress = 1;
|
||||
|
||||
m_drawBackingImageWhenFull = false;
|
||||
m_drawBackingImageWhenEmpty = true;
|
||||
m_showDurability = false;
|
||||
m_highlightEmpty = false;
|
||||
|
||||
setItemBag(bag);
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
m_itemDraggableArea = jsonToRectI(assets->json("/interface.config:itemDraggableArea"));
|
||||
Vec2I calculatedSize = {
|
||||
m_dimensions[0] * m_rowSpacing[0] + m_dimensions[1] * m_columnSpacing[0],
|
||||
m_dimensions[0] * m_rowSpacing[1] + m_dimensions[1] * m_columnSpacing[1]
|
||||
};
|
||||
setSize(calculatedSize.piecewiseMax(m_itemDraggableArea.size()));
|
||||
|
||||
disableScissoring();
|
||||
markAsContainer();
|
||||
}
|
||||
|
||||
ItemBagConstPtr ItemGridWidget::bag() const {
|
||||
return m_bag;
|
||||
}
|
||||
|
||||
ItemPtr ItemGridWidget::itemAt(Vec2I const& position) const {
|
||||
auto pos = bagLocationAt(position);
|
||||
if (pos != NPos && m_bag)
|
||||
return m_bag->at(pos);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ItemPtr ItemGridWidget::itemAt(size_t index) const {
|
||||
if (index < m_bag->size())
|
||||
return m_bag->at(index);
|
||||
return {};
|
||||
}
|
||||
|
||||
ItemPtr ItemGridWidget::selectedItem() const {
|
||||
return itemAt(selectedIndex());
|
||||
}
|
||||
|
||||
ItemSlotWidgetPtr ItemGridWidget::itemWidgetAt(Vec2I const& position) const {
|
||||
auto pos = bagLocationAt(position);
|
||||
if (pos != NPos)
|
||||
return m_slots[pos];
|
||||
return {};
|
||||
}
|
||||
|
||||
ItemSlotWidgetPtr ItemGridWidget::itemWidgetAt(size_t index) const {
|
||||
if (index < m_slots.size())
|
||||
return m_slots[index];
|
||||
return {};
|
||||
}
|
||||
|
||||
Vec2I ItemGridWidget::dimensions() const {
|
||||
return m_dimensions;
|
||||
}
|
||||
|
||||
size_t ItemGridWidget::itemSlots() const {
|
||||
return m_dimensions.x() * m_dimensions.y();
|
||||
}
|
||||
|
||||
size_t ItemGridWidget::bagSize() const {
|
||||
if (!m_bag)
|
||||
return 0;
|
||||
|
||||
return m_bag->size();
|
||||
}
|
||||
|
||||
size_t ItemGridWidget::effectiveSize() const {
|
||||
return min(itemSlots(), bagSize());
|
||||
}
|
||||
|
||||
size_t ItemGridWidget::bagLocationAt(Vec2I const& position) const {
|
||||
if (m_bag) {
|
||||
for (size_t i = 0; i < (m_bag->size() - m_bagOffset) && i < unsigned(m_dimensions[0] * m_dimensions[1]); ++i) {
|
||||
Vec2I loc = locOfItemSlot(i);
|
||||
RectI bagItemArea = RectI(m_itemDraggableArea).translated(screenPosition() + loc);
|
||||
if (bagItemArea.contains(position))
|
||||
return i + m_bagOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return NPos;
|
||||
}
|
||||
|
||||
Vec2I ItemGridWidget::positionOfSlot(size_t slotNumber) {
|
||||
return m_slots.at(slotNumber)->position() + position();
|
||||
}
|
||||
|
||||
bool ItemGridWidget::sendEvent(InputEvent const& event) {
|
||||
if (m_visible) {
|
||||
if (auto mouseButton = event.ptr<MouseButtonDownEvent>()) {
|
||||
if (mouseButton->mouseButton == MouseButton::Left || (m_rightClickCallback && mouseButton->mouseButton == MouseButton::Right)) {
|
||||
Vec2I mousePos = *context()->mousePosition(event);
|
||||
for (size_t i = 0; i < (m_bag->size() - m_bagOffset) && i < unsigned(m_dimensions[0] * m_dimensions[1]); ++i) {
|
||||
Vec2I loc = locOfItemSlot(i);
|
||||
RectI bagItemArea = RectI(m_itemDraggableArea).translated(screenPosition() + loc);
|
||||
|
||||
if (bagItemArea.contains(mousePos)) {
|
||||
m_selectedIndex = i;
|
||||
if (mouseButton->mouseButton == MouseButton::Right)
|
||||
m_rightClickCallback(this);
|
||||
else
|
||||
m_callback(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Vec2I ItemGridWidget::locOfItemSlot(unsigned slot) const {
|
||||
Vec2I loc = {
|
||||
(int)slot % (int)m_dimensions[0] * m_rowSpacing[0] + // x contribution from row
|
||||
(int)slot / (int)m_dimensions[0] * m_columnSpacing[0], // x contribution from column
|
||||
(m_dimensions[0] - 1) * m_rowSpacing[1] - (int)slot % (int)m_dimensions[0] * m_rowSpacing[1] + // y contribution from row
|
||||
(m_dimensions[1] - 1) * m_columnSpacing[1] - (int)slot / (int)m_dimensions[0] * m_columnSpacing[1] // y contribution from column
|
||||
};
|
||||
return loc;
|
||||
}
|
||||
|
||||
void ItemGridWidget::setCallback(WidgetCallbackFunc callback) {
|
||||
m_callback = callback;
|
||||
}
|
||||
|
||||
void ItemGridWidget::setRightClickCallback(WidgetCallbackFunc callback) {
|
||||
m_rightClickCallback = callback;
|
||||
}
|
||||
|
||||
void ItemGridWidget::setItemBag(ItemBagConstPtr bag) {
|
||||
m_bag = bag;
|
||||
|
||||
if (!m_bag)
|
||||
return;
|
||||
|
||||
removeAllChildren();
|
||||
m_slots.clear();
|
||||
for (size_t i = 0; i < m_bag->size() - m_bagOffset && i < (unsigned)m_dimensions[0] * m_dimensions[1]; ++i) {
|
||||
auto itemSlot = make_shared<ItemSlotWidget>(m_bag->at(i), m_backingImage);
|
||||
addChild(strf("%d", i), itemSlot);
|
||||
m_slots.append(itemSlot);
|
||||
itemSlot->setBackingImageAffinity(m_drawBackingImageWhenFull, m_drawBackingImageWhenEmpty);
|
||||
itemSlot->setProgress(m_progress);
|
||||
itemSlot->setPosition(locOfItemSlot(i));
|
||||
itemSlot->showDurability(m_showDurability);
|
||||
}
|
||||
|
||||
m_itemNames = slotItemNames();
|
||||
}
|
||||
|
||||
void ItemGridWidget::setProgress(float progress) {
|
||||
m_progress = progress;
|
||||
|
||||
if (!m_bag)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < m_bag->size() - m_bagOffset && i < (unsigned)m_dimensions[0] * m_dimensions[1]; ++i) {
|
||||
auto itemSlot = m_slots.at(i);
|
||||
itemSlot->setProgress(m_progress);
|
||||
}
|
||||
}
|
||||
|
||||
size_t ItemGridWidget::selectedIndex() const {
|
||||
return m_selectedIndex + m_bagOffset;
|
||||
}
|
||||
|
||||
void ItemGridWidget::updateAllItemSlots() {
|
||||
if (!m_bag)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < m_bag->size() - m_bagOffset && i < (unsigned)m_dimensions[0] * m_dimensions[1]; ++i) {
|
||||
auto item = m_bag->at(i + m_bagOffset);
|
||||
auto slot = m_slots.at(i);
|
||||
slot->setItem(item);
|
||||
slot->setHighlightEnabled(!item && m_highlightEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemGridWidget::updateItemState() {
|
||||
updateAllItemSlots();
|
||||
auto newState = slotItemNames();
|
||||
for (size_t i = 0; i < newState.size(); ++i) {
|
||||
if (newState[i].empty()) {
|
||||
m_changedSlots.remove(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newState[i].compare(m_itemNames[i]) != 0)
|
||||
m_changedSlots.insert(i);
|
||||
}
|
||||
m_itemNames = newState;
|
||||
}
|
||||
|
||||
void ItemGridWidget::indicateChangedSlots() {
|
||||
for (auto i : m_changedSlots)
|
||||
m_slots[i]->indicateNew();
|
||||
}
|
||||
|
||||
void ItemGridWidget::setHighlightEmpty(bool highlight) {
|
||||
m_highlightEmpty = highlight;
|
||||
}
|
||||
|
||||
void ItemGridWidget::clearChangedSlots() {
|
||||
m_changedSlots.clear();
|
||||
}
|
||||
|
||||
bool ItemGridWidget::slotsChanged() {
|
||||
return m_changedSlots.size() > 0;
|
||||
}
|
||||
|
||||
HashSet<ItemDescriptor> ItemGridWidget::uniqueItemState() {
|
||||
HashSet<ItemDescriptor> state;
|
||||
if (!m_bag)
|
||||
return state;
|
||||
for (auto item : m_bag->items()) {
|
||||
if (item)
|
||||
state.add(item->descriptor().singular());
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
List<String> ItemGridWidget::slotItemNames() {
|
||||
List<String> itemNames;
|
||||
for (auto slot : m_slots) {
|
||||
if (slot->item())
|
||||
itemNames.append(slot->item()->name());
|
||||
else
|
||||
itemNames.append(String());
|
||||
}
|
||||
return itemNames;
|
||||
}
|
||||
|
||||
void ItemGridWidget::setBackingImageAffinity(bool full, bool empty) {
|
||||
m_drawBackingImageWhenFull = full;
|
||||
m_drawBackingImageWhenEmpty = empty;
|
||||
|
||||
if (!m_bag)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < m_bag->size() - m_bagOffset && i < (unsigned)m_dimensions[0] * m_dimensions[1]; ++i) {
|
||||
auto itemSlot = m_slots.at(i);
|
||||
itemSlot->setBackingImageAffinity(m_drawBackingImageWhenFull, m_drawBackingImageWhenEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemGridWidget::showDurability(bool show) {
|
||||
m_showDurability = show;
|
||||
|
||||
if (!m_bag)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < m_bag->size() - m_bagOffset && i < (unsigned)m_dimensions[0] * m_dimensions[1]; ++i) {
|
||||
auto itemSlot = m_slots.at(i);
|
||||
itemSlot->showDurability(m_showDurability);
|
||||
}
|
||||
}
|
||||
|
||||
RectI ItemGridWidget::getScissorRect() const {
|
||||
auto assets = Root::singleton().assets();
|
||||
auto durabilityOffset = jsonToVec2I(assets->json("/interface.config:itemIconDurabilityOffset"));
|
||||
auto itemCountRightAnchor = jsonToVec2I(assets->json("/interface.config:itemCountRightAnchor"));
|
||||
|
||||
Vec2I extra = Vec2I(durabilityOffset * -1).piecewiseMax(itemCountRightAnchor * -1).piecewiseMax(Vec2I());
|
||||
return RectI::withSize(screenPosition() - extra, size() + extra);
|
||||
}
|
||||
|
||||
void ItemGridWidget::renderImpl() {
|
||||
updateAllItemSlots();
|
||||
}
|
||||
|
||||
}
|
101
source/windowing/StarItemGridWidget.hpp
Normal file
101
source/windowing/StarItemGridWidget.hpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
#ifndef STAR_ITEMGRID_WIDGET_HPP
|
||||
#define STAR_ITEMGRID_WIDGET_HPP
|
||||
|
||||
#include "StarItemBag.hpp"
|
||||
#include "StarWidget.hpp"
|
||||
#include "StarItemSlotWidget.hpp"
|
||||
#include "StarItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ItemGridWidget);
|
||||
|
||||
class ItemGridWidget : public Widget {
|
||||
public:
|
||||
ItemGridWidget(ItemBagConstPtr bag, Vec2I const& dimensions, Vec2I const& spacing, String const& backingImage, unsigned bagOffset);
|
||||
ItemGridWidget(ItemBagConstPtr bag, Vec2I const& dimensions, Vec2I const& rowSpacing, Vec2I const& columnSpacing, String const& backingImage, unsigned bagOffset);
|
||||
|
||||
ItemBagConstPtr bag() const;
|
||||
|
||||
ItemPtr itemAt(Vec2I const& position) const;
|
||||
ItemPtr itemAt(size_t index) const;
|
||||
ItemPtr selectedItem() const;
|
||||
|
||||
ItemSlotWidgetPtr itemWidgetAt(Vec2I const& position) const;
|
||||
ItemSlotWidgetPtr itemWidgetAt(size_t index) const;
|
||||
|
||||
// Returns the dimensions of the item grid
|
||||
Vec2I dimensions() const;
|
||||
|
||||
// Returns the number of item slots in the grid (dimensions.x() * dimensions.y())
|
||||
size_t itemSlots() const;
|
||||
|
||||
// Returns the size of the underlying bag.
|
||||
size_t bagSize() const;
|
||||
|
||||
// Returns the min of bagSize() and itemSlots()
|
||||
size_t effectiveSize() const;
|
||||
|
||||
size_t bagLocationAt(Vec2I const& position) const;
|
||||
Vec2I positionOfSlot(size_t slotNumber);
|
||||
|
||||
bool sendEvent(InputEvent const& event) override;
|
||||
void setCallback(WidgetCallbackFunc callback);
|
||||
void setRightClickCallback(WidgetCallbackFunc callback);
|
||||
void setItemBag(ItemBagConstPtr bag);
|
||||
void setProgress(float progress);
|
||||
|
||||
size_t selectedIndex() const;
|
||||
|
||||
void updateAllItemSlots();
|
||||
|
||||
// Item states, keeping track of new items
|
||||
void updateItemState();
|
||||
void clearChangedSlots();
|
||||
bool slotsChanged();
|
||||
void indicateChangedSlots();
|
||||
|
||||
void setHighlightEmpty(bool highlight);
|
||||
|
||||
void setBackingImageAffinity(bool full, bool empty);
|
||||
void showDurability(bool show);
|
||||
|
||||
virtual RectI getScissorRect() const override;
|
||||
|
||||
protected:
|
||||
void renderImpl() override;
|
||||
HashSet<ItemDescriptor> uniqueItemState();
|
||||
List<String> slotItemNames();
|
||||
|
||||
private:
|
||||
Vec2I locOfItemSlot(unsigned slot) const;
|
||||
|
||||
ItemBagConstPtr m_bag;
|
||||
List<ItemSlotWidgetPtr> m_slots;
|
||||
unsigned m_bagOffset;
|
||||
Vec2I m_dimensions;
|
||||
Vec2I m_rowSpacing;
|
||||
Vec2I m_columnSpacing;
|
||||
|
||||
List<String> m_itemNames;
|
||||
Set<size_t> m_changedSlots;
|
||||
|
||||
RectI m_itemDraggableArea;
|
||||
|
||||
String m_backingImage;
|
||||
bool m_drawBackingImageWhenFull;
|
||||
bool m_drawBackingImageWhenEmpty;
|
||||
bool m_showDurability;
|
||||
|
||||
float m_progress;
|
||||
|
||||
bool m_highlightEmpty;
|
||||
|
||||
unsigned m_selectedIndex;
|
||||
WidgetCallbackFunc m_callback;
|
||||
WidgetCallbackFunc m_rightClickCallback;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
205
source/windowing/StarItemSlotWidget.cpp
Normal file
205
source/windowing/StarItemSlotWidget.cpp
Normal file
|
@ -0,0 +1,205 @@
|
|||
#include "StarItemSlotWidget.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarWidgetParsing.hpp"
|
||||
#include "StarImageMetadataDatabase.hpp"
|
||||
#include "StarItem.hpp"
|
||||
#include "StarDurabilityItem.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarGameTypes.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ItemSlotWidget::ItemSlotWidget(ItemPtr const& item, String const& backingImage)
|
||||
: m_item(item), m_backingImage(backingImage) {
|
||||
m_drawBackingImageWhenFull = false;
|
||||
m_drawBackingImageWhenEmpty = true;
|
||||
m_fontSize = 0;
|
||||
m_progress = 1;
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
auto interfaceConfig = assets->json("/interface.config");
|
||||
m_countPosition = TextPositioning(jsonToVec2F(interfaceConfig.get("itemCountRightAnchor")), HorizontalAnchor::RightAnchor);
|
||||
m_countFontMode = FontMode::Normal;
|
||||
m_fontSize = interfaceConfig.query("font.itemSize").toInt();
|
||||
m_fontColor = Color::rgb(jsonToVec3B(interfaceConfig.query("font.defaultColor")));
|
||||
m_itemDraggableArea = jsonToRectI(interfaceConfig.get("itemDraggableArea"));
|
||||
m_durabilityOffset = jsonToVec2I(interfaceConfig.get("itemIconDurabilityOffset"));
|
||||
|
||||
auto newItemIndicatorConfig = interfaceConfig.get("newItemAnimation");
|
||||
m_newItemIndicator = Animation(newItemIndicatorConfig);
|
||||
// End animation before it begins, only display when triggered
|
||||
m_newItemIndicator.update(newItemIndicatorConfig.getDouble("animationCycle") * newItemIndicatorConfig.getDouble("loops", 1.0f));
|
||||
|
||||
Json highlightAnimationConfig = interfaceConfig.get("highlightAnimation");
|
||||
m_highlightAnimation = Animation(highlightAnimationConfig);
|
||||
m_highlightEnabled = false;
|
||||
|
||||
Vec2I backingImageSize;
|
||||
if (m_backingImage.size()) {
|
||||
auto imgMetadata = Root::singleton().imageMetadataDatabase();
|
||||
backingImageSize = Vec2I(imgMetadata->imageSize(m_backingImage));
|
||||
}
|
||||
setSize(m_itemDraggableArea.max().piecewiseMax(backingImageSize));
|
||||
|
||||
WidgetParser parser;
|
||||
|
||||
parser.construct(assets->json("/interface/itemSlot.config").get("config"), this);
|
||||
m_durabilityBar = fetchChild<ProgressWidget>("durabilityBar");
|
||||
m_durabilityBar->hide();
|
||||
m_showDurability = false;
|
||||
m_showCount = true;
|
||||
m_showRarity = true;
|
||||
m_showLinkIndicator = false;
|
||||
|
||||
disableScissoring();
|
||||
}
|
||||
|
||||
void ItemSlotWidget::update() {
|
||||
Widget::update();
|
||||
}
|
||||
|
||||
bool ItemSlotWidget::sendEvent(InputEvent const& event) {
|
||||
if (m_visible) {
|
||||
if (auto mouseButton = event.ptr<MouseButtonDownEvent>()) {
|
||||
if (mouseButton->mouseButton == MouseButton::Left || (m_rightClickCallback && mouseButton->mouseButton == MouseButton::Right)) {
|
||||
Vec2I mousePos = *context()->mousePosition(event);
|
||||
RectI itemArea = m_itemDraggableArea.translated(screenPosition());
|
||||
if (itemArea.contains(mousePos)) {
|
||||
if (mouseButton->mouseButton == MouseButton::Right)
|
||||
m_rightClickCallback(this);
|
||||
else
|
||||
m_callback(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::setCallback(WidgetCallbackFunc callback) {
|
||||
m_callback = callback;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::setRightClickCallback(WidgetCallbackFunc callback) {
|
||||
m_rightClickCallback = callback;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::setItem(ItemPtr const& item) {
|
||||
m_item = item;
|
||||
}
|
||||
|
||||
ItemPtr ItemSlotWidget::item() const {
|
||||
return m_item;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::setProgress(float progress) {
|
||||
m_progress = progress;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::setBackingImageAffinity(bool full, bool empty) {
|
||||
m_drawBackingImageWhenFull = full;
|
||||
m_drawBackingImageWhenEmpty = empty;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::setCountPosition(TextPositioning textPositioning) {
|
||||
m_countPosition = textPositioning;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::setCountFontMode(FontMode fontMode) {
|
||||
m_countFontMode = fontMode;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::showDurability(bool show) {
|
||||
m_showDurability = show;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::showCount(bool show) {
|
||||
m_showCount = show;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::showRarity(bool showRarity) {
|
||||
m_showRarity = showRarity;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::showLinkIndicator(bool showLinkIndicator) {
|
||||
m_showLinkIndicator = showLinkIndicator;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::indicateNew() {
|
||||
m_newItemIndicator.reset();
|
||||
}
|
||||
|
||||
void ItemSlotWidget::setHighlightEnabled(bool highlight) {
|
||||
if (!m_highlightEnabled && highlight)
|
||||
m_highlightAnimation.reset();
|
||||
m_highlightEnabled = highlight;
|
||||
}
|
||||
|
||||
void ItemSlotWidget::renderImpl() {
|
||||
if (m_item) {
|
||||
if (m_drawBackingImageWhenFull && m_backingImage != "")
|
||||
context()->drawInterfaceQuad(m_backingImage, Vec2F(screenPosition()));
|
||||
|
||||
List<Drawable> iconDrawables = m_item->iconDrawables();
|
||||
|
||||
if (m_showRarity) {
|
||||
String border = rarityBorder(m_item->rarity());
|
||||
context()->drawInterfaceQuad(border, Vec2F(screenPosition()));
|
||||
}
|
||||
|
||||
if (m_showLinkIndicator) {
|
||||
// TODO: Hardcoded
|
||||
context()->drawInterfaceQuad("/interface/inventory/itemlinkindicator.png",
|
||||
Vec2F(screenPosition() - Vec2I(1, 1)));
|
||||
}
|
||||
|
||||
for (auto i : iconDrawables)
|
||||
context()->drawInterfaceDrawable(i, Vec2F(screenPosition() + size() / 2));
|
||||
|
||||
m_newItemIndicator.update(WorldTimestep);
|
||||
if (!m_newItemIndicator.isComplete())
|
||||
context()->drawInterfaceDrawable(m_newItemIndicator.drawable(1.0), Vec2F(screenPosition() + size() / 2), Color::White.toRgba());
|
||||
|
||||
if (m_showDurability) {
|
||||
if (auto durabilityItem = as<DurabilityItem>(m_item)) {
|
||||
float amount = durabilityItem->durabilityStatus();
|
||||
m_durabilityBar->setCurrentProgressLevel(amount);
|
||||
|
||||
if (amount < 1)
|
||||
m_durabilityBar->show();
|
||||
else
|
||||
m_durabilityBar->hide();
|
||||
} else {
|
||||
m_durabilityBar->hide();
|
||||
}
|
||||
}
|
||||
|
||||
int frame = (int)roundf(m_progress * 18); // TODO: Hardcoded lol
|
||||
context()->drawInterfaceQuad(strf("/interface/cooldown.png:%d", frame), Vec2F(screenPosition()));
|
||||
|
||||
if (m_item->count() > 1 && m_showCount) { // we don't need to tell people that there's only 1 of something
|
||||
context()->setFontSize(m_fontSize);
|
||||
context()->setFontColor(m_fontColor.toRgba());
|
||||
context()->setFontMode(m_countFontMode);
|
||||
context()->renderInterfaceText(strf("%d", m_item->count()), m_countPosition.translated(Vec2F(screenPosition())));
|
||||
}
|
||||
|
||||
} else if (m_drawBackingImageWhenEmpty && m_backingImage != "") {
|
||||
context()->drawInterfaceQuad(m_backingImage, Vec2F(screenPosition()));
|
||||
int frame = (int)roundf(m_progress * 18); // TODO: Hardcoded lol
|
||||
context()->drawInterfaceQuad(strf("/interface/cooldown.png:%d", frame), Vec2F(screenPosition()));
|
||||
}
|
||||
|
||||
if (m_highlightEnabled) {
|
||||
m_highlightAnimation.update(WorldTimestep);
|
||||
context()->drawInterfaceDrawable(m_highlightAnimation.drawable(1.0), Vec2F(screenPosition() + size() / 2), Color::White.toRgba());
|
||||
}
|
||||
|
||||
if (!m_item)
|
||||
m_durabilityBar->hide();
|
||||
}
|
||||
|
||||
}
|
76
source/windowing/StarItemSlotWidget.hpp
Normal file
76
source/windowing/StarItemSlotWidget.hpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#ifndef STAR_ITEMSLOT_WIDGET_HPP
|
||||
#define STAR_ITEMSLOT_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
#include "StarProgressWidget.hpp"
|
||||
#include "StarAnimation.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(Item);
|
||||
STAR_CLASS(ItemSlotWidget);
|
||||
|
||||
static float const ItemIndicateNewTime = 1.5f;
|
||||
|
||||
class ItemSlotWidget : public Widget {
|
||||
public:
|
||||
ItemSlotWidget(ItemPtr const& item, String const& backingImage);
|
||||
|
||||
virtual void update() override;
|
||||
bool sendEvent(InputEvent const& event) override;
|
||||
void setCallback(WidgetCallbackFunc callback);
|
||||
void setRightClickCallback(WidgetCallbackFunc callback);
|
||||
void setItem(ItemPtr const& item);
|
||||
ItemPtr item() const;
|
||||
void setProgress(float progress);
|
||||
void setBackingImageAffinity(bool full, bool empty);
|
||||
void setCountPosition(TextPositioning textPositioning);
|
||||
void setCountFontMode(FontMode fontMode);
|
||||
|
||||
void showDurability(bool show);
|
||||
void showCount(bool show);
|
||||
void showRarity(bool showRarity);
|
||||
void showLinkIndicator(bool showLinkIndicator);
|
||||
|
||||
void indicateNew();
|
||||
|
||||
void setHighlightEnabled(bool highlight);
|
||||
|
||||
protected:
|
||||
virtual void renderImpl() override;
|
||||
|
||||
private:
|
||||
ItemPtr m_item;
|
||||
|
||||
String m_backingImage;
|
||||
bool m_drawBackingImageWhenFull;
|
||||
bool m_drawBackingImageWhenEmpty;
|
||||
bool m_showDurability;
|
||||
bool m_showCount;
|
||||
bool m_showRarity;
|
||||
bool m_showLinkIndicator;
|
||||
|
||||
TextPositioning m_countPosition;
|
||||
FontMode m_countFontMode;
|
||||
|
||||
Vec2I m_durabilityOffset;
|
||||
RectI m_itemDraggableArea;
|
||||
|
||||
int m_fontSize;
|
||||
Color m_fontColor;
|
||||
|
||||
WidgetCallbackFunc m_callback;
|
||||
WidgetCallbackFunc m_rightClickCallback;
|
||||
float m_progress;
|
||||
|
||||
ProgressWidgetPtr m_durabilityBar;
|
||||
|
||||
Animation m_newItemIndicator;
|
||||
|
||||
bool m_highlightEnabled;
|
||||
Animation m_highlightAnimation;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
212
source/windowing/StarKeyBindings.cpp
Normal file
212
source/windowing/StarKeyBindings.cpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
#include "StarKeyBindings.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarConfiguration.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
namespace Star {
|
||||
|
||||
HashMap<Key, KeyMod> const KeyChordMods{
|
||||
{Key::LShift, KeyMod::LShift},
|
||||
{Key::RShift, KeyMod::RShift},
|
||||
{Key::LCtrl, KeyMod::LCtrl},
|
||||
{Key::RCtrl, KeyMod::RCtrl},
|
||||
{Key::LAlt, KeyMod::LAlt},
|
||||
{Key::RAlt, KeyMod::RAlt},
|
||||
{Key::LGui, KeyMod::LGui},
|
||||
{Key::RGui, KeyMod::RGui},
|
||||
{Key::AltGr, KeyMod::AltGr}
|
||||
};
|
||||
|
||||
EnumMap<InterfaceAction> const InterfaceActionNames{
|
||||
{InterfaceAction::None, "None"},
|
||||
{InterfaceAction::PlayerUp, "PlayerUp"},
|
||||
{InterfaceAction::PlayerDown, "PlayerDown"},
|
||||
{InterfaceAction::PlayerLeft, "PlayerLeft"},
|
||||
{InterfaceAction::PlayerRight, "PlayerRight"},
|
||||
{InterfaceAction::PlayerJump, "PlayerJump"},
|
||||
{InterfaceAction::PlayerMainItem, "PlayerMainItem"},
|
||||
{InterfaceAction::PlayerAltItem, "PlayerAltItem"},
|
||||
{InterfaceAction::PlayerDropItem, "PlayerDropItem"},
|
||||
{InterfaceAction::PlayerInteract, "PlayerInteract"},
|
||||
{InterfaceAction::PlayerShifting, "PlayerShifting"},
|
||||
{InterfaceAction::PlayerTechAction1, "PlayerTechAction1"},
|
||||
{InterfaceAction::PlayerTechAction2, "PlayerTechAction2"},
|
||||
{InterfaceAction::PlayerTechAction3, "PlayerTechAction3"},
|
||||
{InterfaceAction::EmoteBlabbering, "EmoteBlabbering"},
|
||||
{InterfaceAction::EmoteShouting, "EmoteShouting"},
|
||||
{InterfaceAction::EmoteHappy, "EmoteHappy"},
|
||||
{InterfaceAction::EmoteSad, "EmoteSad"},
|
||||
{InterfaceAction::EmoteNeutral, "EmoteNeutral"},
|
||||
{InterfaceAction::EmoteLaugh, "EmoteLaugh"},
|
||||
{InterfaceAction::EmoteAnnoyed, "EmoteAnnoyed"},
|
||||
{InterfaceAction::EmoteOh, "EmoteOh"},
|
||||
{InterfaceAction::EmoteOooh, "EmoteOooh"},
|
||||
{InterfaceAction::EmoteBlink, "EmoteBlink"},
|
||||
{InterfaceAction::EmoteWink, "EmoteWink"},
|
||||
{InterfaceAction::EmoteEat, "EmoteEat"},
|
||||
{InterfaceAction::EmoteSleep, "EmoteSleep"},
|
||||
{InterfaceAction::ShowLabels, "ShowLabels"},
|
||||
{InterfaceAction::CameraShift, "CameraShift"},
|
||||
{InterfaceAction::TitleBack, "TitleBack"},
|
||||
{InterfaceAction::CinematicSkip, "CinematicSkip"},
|
||||
{InterfaceAction::CinematicNext, "CinematicNext"},
|
||||
{InterfaceAction::GuiClose, "GuiClose"},
|
||||
{InterfaceAction::GuiShifting, "GuiShifting"},
|
||||
{InterfaceAction::KeybindingClear, "KeybindingClear"},
|
||||
{InterfaceAction::KeybindingCancel, "KeybindingCancel"},
|
||||
{InterfaceAction::ChatPageUp, "ChatPageUp"},
|
||||
{InterfaceAction::ChatPageDown, "ChatPageDown"},
|
||||
{InterfaceAction::ChatPreviousLine, "ChatPreviousLine"},
|
||||
{InterfaceAction::ChatNextLine, "ChatNextLine"},
|
||||
{InterfaceAction::ChatSendLine, "ChatSendLine"},
|
||||
{InterfaceAction::ChatBegin, "ChatBegin"},
|
||||
{InterfaceAction::ChatBeginCommand, "ChatBeginCommand"},
|
||||
{InterfaceAction::ChatStop, "ChatStop"},
|
||||
{InterfaceAction::InterfaceShowHelp, "InterfaceShowHelp"},
|
||||
{InterfaceAction::InterfaceHideHud, "InterfaceHideHud"},
|
||||
{InterfaceAction::InterfaceChangeBarGroup, "InterfaceChangeBarGroup"},
|
||||
{InterfaceAction::InterfaceDeselectHands, "InterfaceDeselectHands"},
|
||||
{InterfaceAction::InterfaceBar1, "InterfaceBar1"},
|
||||
{InterfaceAction::InterfaceBar2, "InterfaceBar2"},
|
||||
{InterfaceAction::InterfaceBar3, "InterfaceBar3"},
|
||||
{InterfaceAction::InterfaceBar4, "InterfaceBar4"},
|
||||
{InterfaceAction::InterfaceBar5, "InterfaceBar5"},
|
||||
{InterfaceAction::InterfaceBar6, "InterfaceBar6"},
|
||||
{InterfaceAction::InterfaceBar7, "InterfaceBar7"},
|
||||
{InterfaceAction::InterfaceBar8, "InterfaceBar8"},
|
||||
{InterfaceAction::InterfaceBar9, "InterfaceBar9"},
|
||||
{InterfaceAction::InterfaceBar10, "InterfaceBar10"},
|
||||
{InterfaceAction::EssentialBar1, "EssentialBar1"},
|
||||
{InterfaceAction::EssentialBar2, "EssentialBar2"},
|
||||
{InterfaceAction::EssentialBar3, "EssentialBar3"},
|
||||
{InterfaceAction::EssentialBar4, "EssentialBar4"},
|
||||
{InterfaceAction::InterfaceRepeatCommand, "InterfaceRepeatCommand"},
|
||||
{InterfaceAction::InterfaceToggleFullscreen, "InterfaceToggleFullscreen"},
|
||||
{InterfaceAction::InterfaceReload, "InterfaceReload"},
|
||||
{InterfaceAction::InterfaceEscapeMenu, "InterfaceEscapeMenu"},
|
||||
{InterfaceAction::InterfaceInventory, "InterfaceInventory"},
|
||||
{InterfaceAction::InterfaceCodex, "InterfaceCodex"},
|
||||
{InterfaceAction::InterfaceQuest, "InterfaceQuest"},
|
||||
{InterfaceAction::InterfaceCrafting, "InterfaceCrafting"},
|
||||
};
|
||||
|
||||
bool KeyChord::operator<(KeyChord const& rhs) const {
|
||||
return tie(key, mods) < tie(rhs.key, rhs.mods);
|
||||
}
|
||||
|
||||
KeyChord inputDescriptorFromJson(Json const& json) {
|
||||
Key key;
|
||||
auto type = json.getString("type");
|
||||
if (type == "key") {
|
||||
auto value = json.get("value");
|
||||
if (value.isType(Json::Type::String)) {
|
||||
key = KeyNames.getLeft(value.toString());
|
||||
} else if (value.canConvert(Json::Type::Int)) {
|
||||
key = (Key)value.toUInt();
|
||||
} else {
|
||||
throw StarException::format("Improper key value '%s'", value);
|
||||
}
|
||||
} else {
|
||||
throw StarException::format("Improper bindings type '%s'", type);
|
||||
}
|
||||
|
||||
KeyMod mods = KeyMod::NoMod;
|
||||
for (auto mod : json.get("mods").iterateArray())
|
||||
mods |= KeyModNames.getLeft(mod.toString());
|
||||
|
||||
return {key, mods};
|
||||
}
|
||||
|
||||
Json inputDescriptorToJson(KeyChord const& chord) {
|
||||
JsonArray modNames;
|
||||
for (auto const& p : KeyModNames) {
|
||||
if ((chord.mods & p.first) != KeyMod::NoMod)
|
||||
modNames.append(p.second);
|
||||
}
|
||||
return JsonObject{
|
||||
{"type", "key"},
|
||||
{"value", KeyNames.getRight(chord.key)},
|
||||
{"mods", modNames}
|
||||
};
|
||||
}
|
||||
|
||||
String printInputDescriptor(KeyChord chord) {
|
||||
StringList modNames;
|
||||
for (auto const& p : KeyModNames) {
|
||||
if ((chord.mods & p.first) != KeyMod::NoMod)
|
||||
modNames.append(p.second);
|
||||
}
|
||||
|
||||
return String::joinWith(" + ", modNames.join(" + "), KeyNames.getRight(chord.key));
|
||||
}
|
||||
|
||||
KeyBindings::KeyBindings() {}
|
||||
|
||||
KeyBindings::KeyBindings(Json const& json) {
|
||||
Map<Key, List<pair<KeyMod, InterfaceAction>>> actions;
|
||||
try {
|
||||
for (auto const& kvpair : json.iterateObject()) {
|
||||
InterfaceAction action = InterfaceActionNames.getLeft(kvpair.first);
|
||||
|
||||
for (auto const& input : kvpair.second.iterateArray()) {
|
||||
try {
|
||||
auto chord = inputDescriptorFromJson(input);
|
||||
actions[chord.key].append({chord.mods, action});
|
||||
} catch (StarException const& e) {
|
||||
Logger::warn("Could not load keybinding for %s: %s\n",
|
||||
InterfaceActionNames.getRight(action),
|
||||
outputException(e, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_actions = move(actions);
|
||||
} catch (StarException const& e) {
|
||||
throw StarException(strf("Could not set keybindings from configuration. %s", outputException(e, false)));
|
||||
}
|
||||
}
|
||||
|
||||
Set<InterfaceAction> KeyBindings::actions(Key key) const {
|
||||
return actions(KeyChord{key, KeyMod::NoMod});
|
||||
}
|
||||
|
||||
Set<InterfaceAction> KeyBindings::actions(InputEvent const& event) const {
|
||||
if (auto keyDown = event.ptr<KeyDownEvent>())
|
||||
return actions(KeyChord{keyDown->key, keyDown->mods});
|
||||
return {};
|
||||
}
|
||||
|
||||
Set<InterfaceAction> KeyBindings::actions(KeyChord chord) const {
|
||||
size_t mostMatchedMods = 0;
|
||||
Set<InterfaceAction> matching;
|
||||
for (auto const& pair : m_actions.value(chord.key)) {
|
||||
// first make sure that all required mods for the binding are held
|
||||
if ((pair.first & chord.mods) == pair.first) {
|
||||
// now count the number of mods in the binding
|
||||
size_t matchedMods = 0;
|
||||
for (auto modPair : KeyChordMods) {
|
||||
if ((modPair.second & pair.first) == modPair.second)
|
||||
++matchedMods;
|
||||
}
|
||||
|
||||
if (matchedMods > mostMatchedMods) {
|
||||
matching.clear();
|
||||
mostMatchedMods = matchedMods;
|
||||
}
|
||||
|
||||
// only activate the binding(s) with the most mods
|
||||
if (matchedMods == mostMatchedMods)
|
||||
matching.add(pair.second);
|
||||
}
|
||||
}
|
||||
return matching;
|
||||
}
|
||||
|
||||
Set<InterfaceAction> KeyBindings::actionsForKey(Key key) const {
|
||||
return Set<InterfaceAction>::from(m_actions.value(key).transformed([](auto p){ return p.second; }));
|
||||
}
|
||||
|
||||
}
|
119
source/windowing/StarKeyBindings.hpp
Normal file
119
source/windowing/StarKeyBindings.hpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
#ifndef STAR_KEY_BINDINGS_HPP
|
||||
#define STAR_KEY_BINDINGS_HPP
|
||||
|
||||
#include "StarInputEvent.hpp"
|
||||
#include "StarSet.hpp"
|
||||
#include "StarJson.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
enum class InterfaceAction {
|
||||
None,
|
||||
PlayerUp,
|
||||
PlayerDown,
|
||||
PlayerLeft,
|
||||
PlayerRight,
|
||||
PlayerJump,
|
||||
PlayerMainItem,
|
||||
PlayerAltItem,
|
||||
PlayerDropItem,
|
||||
PlayerInteract,
|
||||
PlayerShifting,
|
||||
PlayerTechAction1,
|
||||
PlayerTechAction2,
|
||||
PlayerTechAction3,
|
||||
EmoteBlabbering,
|
||||
EmoteShouting,
|
||||
EmoteHappy,
|
||||
EmoteSad,
|
||||
EmoteNeutral,
|
||||
EmoteLaugh,
|
||||
EmoteAnnoyed,
|
||||
EmoteOh,
|
||||
EmoteOooh,
|
||||
EmoteBlink,
|
||||
EmoteWink,
|
||||
EmoteEat,
|
||||
EmoteSleep,
|
||||
ShowLabels,
|
||||
CameraShift,
|
||||
TitleBack,
|
||||
CinematicSkip,
|
||||
CinematicNext,
|
||||
GuiClose,
|
||||
GuiShifting,
|
||||
KeybindingClear,
|
||||
KeybindingCancel,
|
||||
ChatPageUp,
|
||||
ChatPageDown,
|
||||
ChatPreviousLine,
|
||||
ChatNextLine,
|
||||
ChatSendLine,
|
||||
ChatBegin,
|
||||
ChatBeginCommand,
|
||||
ChatStop,
|
||||
InterfaceShowHelp,
|
||||
InterfaceHideHud,
|
||||
InterfaceChangeBarGroup,
|
||||
InterfaceDeselectHands,
|
||||
InterfaceBar1,
|
||||
InterfaceBar2,
|
||||
InterfaceBar3,
|
||||
InterfaceBar4,
|
||||
InterfaceBar5,
|
||||
InterfaceBar6,
|
||||
InterfaceBar7,
|
||||
InterfaceBar8,
|
||||
InterfaceBar9,
|
||||
InterfaceBar10,
|
||||
EssentialBar1,
|
||||
EssentialBar2,
|
||||
EssentialBar3,
|
||||
EssentialBar4,
|
||||
InterfaceRepeatCommand,
|
||||
InterfaceToggleFullscreen,
|
||||
InterfaceReload,
|
||||
InterfaceEscapeMenu,
|
||||
InterfaceInventory,
|
||||
InterfaceCodex,
|
||||
InterfaceQuest,
|
||||
InterfaceCrafting,
|
||||
};
|
||||
extern EnumMap<InterfaceAction> const InterfaceActionNames;
|
||||
|
||||
// Maps the mod keys that can used in key chords to its associated KeyMod.
|
||||
extern HashMap<Key, KeyMod> const KeyChordMods;
|
||||
|
||||
struct KeyChord {
|
||||
Key key;
|
||||
KeyMod mods;
|
||||
|
||||
bool operator<(KeyChord const& rhs) const;
|
||||
};
|
||||
|
||||
KeyChord inputDescriptorFromJson(Json const& json);
|
||||
Json inputDescriptorToJson(KeyChord const& chord);
|
||||
|
||||
String printInputDescriptor(KeyChord chord);
|
||||
|
||||
STAR_CLASS(KeyBindings);
|
||||
|
||||
class KeyBindings {
|
||||
public:
|
||||
KeyBindings();
|
||||
explicit KeyBindings(Json const& json);
|
||||
|
||||
Set<InterfaceAction> actions(Key key) const;
|
||||
Set<InterfaceAction> actions(InputEvent const& event) const;
|
||||
Set<InterfaceAction> actions(KeyChord chord) const;
|
||||
Set<InterfaceAction> actionsForKey(Key key) const;
|
||||
|
||||
private:
|
||||
// Maps the primary key to a list of InterfaceActions, and any mods that they
|
||||
// require to be held.
|
||||
Map<Key, List<pair<KeyMod, InterfaceAction>>> m_actions;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
112
source/windowing/StarLabelWidget.cpp
Normal file
112
source/windowing/StarLabelWidget.cpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
#include "StarLabelWidget.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
LabelWidget::LabelWidget(String text,
|
||||
Color const& color,
|
||||
HorizontalAnchor const& hAnchor,
|
||||
VerticalAnchor const& vAnchor,
|
||||
Maybe<unsigned> wrapWidth,
|
||||
Maybe<float> lineSpacing)
|
||||
: m_color(color),
|
||||
m_hAnchor(hAnchor),
|
||||
m_vAnchor(vAnchor),
|
||||
m_wrapWidth(move(wrapWidth)),
|
||||
m_lineSpacing(move(lineSpacing)) {
|
||||
auto assets = Root::singleton().assets();
|
||||
m_fontSize = assets->json("/interface.config:font").getInt("baseSize");
|
||||
setText(move(text));
|
||||
}
|
||||
|
||||
String const& LabelWidget::text() const {
|
||||
return m_text;
|
||||
}
|
||||
|
||||
Maybe<unsigned> LabelWidget::getTextCharLimit() const {
|
||||
return m_textCharLimit;
|
||||
}
|
||||
|
||||
void LabelWidget::setText(String newText) {
|
||||
m_text = move(newText);
|
||||
updateTextRegion();
|
||||
}
|
||||
|
||||
void LabelWidget::setFontSize(int fontSize) {
|
||||
m_fontSize = fontSize;
|
||||
updateTextRegion();
|
||||
}
|
||||
|
||||
void LabelWidget::setColor(Color newColor) {
|
||||
m_color = move(newColor);
|
||||
}
|
||||
|
||||
void LabelWidget::setAnchor(HorizontalAnchor hAnchor, VerticalAnchor vAnchor) {
|
||||
m_hAnchor = hAnchor;
|
||||
m_vAnchor = vAnchor;
|
||||
updateTextRegion();
|
||||
}
|
||||
|
||||
void LabelWidget::setWrapWidth(Maybe<unsigned> wrapWidth) {
|
||||
m_wrapWidth = move(wrapWidth);
|
||||
updateTextRegion();
|
||||
}
|
||||
|
||||
void LabelWidget::setLineSpacing(Maybe<float> lineSpacing) {
|
||||
m_lineSpacing = move(lineSpacing);
|
||||
updateTextRegion();
|
||||
}
|
||||
|
||||
void LabelWidget::setDirectives(String const& directives) {
|
||||
m_processingDirectives = directives;
|
||||
updateTextRegion();
|
||||
}
|
||||
|
||||
void LabelWidget::setTextCharLimit(Maybe<unsigned> charLimit) {
|
||||
m_textCharLimit = charLimit;
|
||||
updateTextRegion();
|
||||
}
|
||||
|
||||
RectI LabelWidget::relativeBoundRect() const {
|
||||
return RectI(m_textRegion).translated(relativePosition());
|
||||
}
|
||||
|
||||
RectI LabelWidget::getScissorRect() const {
|
||||
return noScissor();
|
||||
}
|
||||
|
||||
void LabelWidget::renderImpl() {
|
||||
context()->setFontSize(m_fontSize);
|
||||
context()->setFontColor(m_color.toRgba());
|
||||
context()->setFontProcessingDirectives(m_processingDirectives);
|
||||
|
||||
if (m_lineSpacing)
|
||||
context()->setLineSpacing(*m_lineSpacing);
|
||||
else
|
||||
context()->setDefaultLineSpacing();
|
||||
|
||||
context()->renderInterfaceText(m_text, {Vec2F(screenPosition()), m_hAnchor, m_vAnchor, m_wrapWidth, m_textCharLimit});
|
||||
|
||||
context()->setFontProcessingDirectives("");
|
||||
context()->setDefaultLineSpacing();
|
||||
}
|
||||
|
||||
void LabelWidget::updateTextRegion() {
|
||||
context()->setFontSize(m_fontSize);
|
||||
context()->setFontColor(m_color.toRgba());
|
||||
context()->setFontProcessingDirectives(m_processingDirectives);
|
||||
|
||||
if (m_lineSpacing)
|
||||
context()->setLineSpacing(*m_lineSpacing);
|
||||
else
|
||||
context()->setDefaultLineSpacing();
|
||||
|
||||
m_textRegion = RectI(context()->determineInterfaceTextSize(m_text, {Vec2F(), m_hAnchor, m_vAnchor, m_wrapWidth, m_textCharLimit}));
|
||||
setSize(m_textRegion.size());
|
||||
|
||||
context()->setFontProcessingDirectives("");
|
||||
context()->setDefaultLineSpacing();
|
||||
}
|
||||
|
||||
}
|
52
source/windowing/StarLabelWidget.hpp
Normal file
52
source/windowing/StarLabelWidget.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#ifndef STAR_LABEL_WIDGET_HPP
|
||||
#define STAR_LABEL_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(LabelWidget);
|
||||
class LabelWidget : public Widget {
|
||||
public:
|
||||
LabelWidget(String text = String(),
|
||||
Color const& color = Color::White,
|
||||
HorizontalAnchor const& hAnchor = HorizontalAnchor::LeftAnchor,
|
||||
VerticalAnchor const& vAnchor = VerticalAnchor::BottomAnchor,
|
||||
Maybe<unsigned> wrapWidth = {},
|
||||
Maybe<float> lineSpacing = {});
|
||||
|
||||
String const& text() const;
|
||||
Maybe<unsigned> getTextCharLimit() const;
|
||||
void setText(String newText);
|
||||
void setFontSize(int fontSize);
|
||||
void setColor(Color newColor);
|
||||
void setAnchor(HorizontalAnchor hAnchor, VerticalAnchor vAnchor);
|
||||
void setWrapWidth(Maybe<unsigned> wrapWidth);
|
||||
void setLineSpacing(Maybe<float> lineSpacing);
|
||||
void setDirectives(String const& directives);
|
||||
void setTextCharLimit(Maybe<unsigned> charLimit);
|
||||
|
||||
RectI relativeBoundRect() const override;
|
||||
|
||||
protected:
|
||||
virtual RectI getScissorRect() const override;
|
||||
virtual void renderImpl() override;
|
||||
|
||||
private:
|
||||
void updateTextRegion();
|
||||
|
||||
String m_text;
|
||||
int m_fontSize;
|
||||
Color m_color;
|
||||
HorizontalAnchor m_hAnchor;
|
||||
VerticalAnchor m_vAnchor;
|
||||
String m_processingDirectives;
|
||||
Maybe<unsigned> m_wrapWidth;
|
||||
Maybe<float> m_lineSpacing;
|
||||
Maybe<unsigned> m_textCharLimit;
|
||||
RectI m_textRegion;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
160
source/windowing/StarLargeCharPlateWidget.cpp
Normal file
160
source/windowing/StarLargeCharPlateWidget.cpp
Normal file
|
@ -0,0 +1,160 @@
|
|||
#include "StarLargeCharPlateWidget.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarPlayer.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
LargeCharPlateWidget::LargeCharPlateWidget(WidgetCallbackFunc mainCallback, PlayerPtr player) : m_player(player) {
|
||||
m_portraitScale = 0;
|
||||
|
||||
setSize(ButtonWidget::size());
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
auto charPlateImage = assets->json("/interface.config:largeCharPlate.backingImage").toString();
|
||||
|
||||
setCallback(mainCallback);
|
||||
setImages(charPlateImage);
|
||||
|
||||
m_playerPlate = charPlateImage;
|
||||
m_playerPlateHover = assets->json("/interface.config:largeCharPlate.playerHover").toString();
|
||||
m_noPlayerPlate = assets->json("/interface.config:largeCharPlate.noPlayer").toString();
|
||||
m_noPlayerPlateHover = assets->json("/interface.config:largeCharPlate.noPlayerHover").toString();
|
||||
m_portraitOffset = jsonToVec2I(assets->json("/interface.config:largeCharPlate.portraitOffset"));
|
||||
m_portraitScale = assets->json("/interface.config:largeCharPlate.portraitScale").toFloat();
|
||||
|
||||
String switchText = assets->json("/interface.config:largeCharPlate.switchText").toString();
|
||||
String createText = assets->json("/interface.config:largeCharPlate.createText").toString();
|
||||
|
||||
m_portrait = make_shared<PortraitWidget>();
|
||||
m_portrait->setScale(m_portraitScale);
|
||||
m_portrait->setPosition(m_portraitOffset);
|
||||
addChild("portrait", m_portrait);
|
||||
|
||||
String modeLabelText = assets->json("/interface.config:largeCharPlate.modeText").toString();
|
||||
m_regularTextColor = Color::rgb(jsonToVec3B(assets->json("/interface.config:largeCharPlate.textColor")));
|
||||
m_disabledTextColor = Color::rgb(jsonToVec3B(assets->json("/interface.config:largeCharPlate.textColorDisabled")));
|
||||
|
||||
m_modeNameOffset = jsonToVec2I(assets->json("/interface.config:largeCharPlate.modeNameOffset"));
|
||||
m_modeOffset = jsonToVec2I(assets->json("/interface.config:largeCharPlate.modeOffset"));
|
||||
|
||||
m_modeName = make_shared<LabelWidget>(modeLabelText, Color::White, HorizontalAnchor::HMidAnchor);
|
||||
addChild("modeName", m_modeName);
|
||||
m_modeName->setPosition(m_modeNameOffset);
|
||||
m_modeName->setAnchor(HorizontalAnchor::HMidAnchor, VerticalAnchor::BottomAnchor);
|
||||
|
||||
m_mode = make_shared<LabelWidget>();
|
||||
addChild("mode", m_mode);
|
||||
m_mode->setPosition(m_modeOffset);
|
||||
m_mode->setAnchor(HorizontalAnchor::LeftAnchor, VerticalAnchor::BottomAnchor);
|
||||
|
||||
m_createCharText = assets->json("/interface.config:largeCharPlate.noPlayerText").toString();
|
||||
m_createCharTextColor = Color::rgb(jsonToVec3B(assets->json("/interface.config:largeCharPlate.noPlayerTextColor")));
|
||||
m_playerNameOffset = jsonToVec2I(assets->json("/interface.config:largeCharPlate.playerNameOffset"));
|
||||
|
||||
m_playerName = make_shared<LabelWidget>();
|
||||
m_playerName->setColor(m_createCharTextColor);
|
||||
m_playerName->setPosition(m_playerNameOffset);
|
||||
m_playerName->setAnchor(HorizontalAnchor::HMidAnchor, VerticalAnchor::BottomAnchor);
|
||||
addChild("player", m_playerName);
|
||||
}
|
||||
|
||||
void LargeCharPlateWidget::renderImpl() {
|
||||
Vec2I pressedOffset = isPressed() ? ButtonWidget::pressedOffset() : Vec2I(0, 0);
|
||||
|
||||
m_portrait->setPosition(m_portraitOffset + pressedOffset);
|
||||
m_mode->setPosition(m_modeOffset + pressedOffset);
|
||||
m_modeName->setPosition(m_modeNameOffset + pressedOffset);
|
||||
m_playerName->setPosition(m_playerNameOffset + pressedOffset);
|
||||
if (m_delete) {
|
||||
m_delete->setPosition(m_deleteOffset + pressedOffset);
|
||||
}
|
||||
|
||||
if (m_player) {
|
||||
ButtonWidget::setImages(m_playerPlate, m_playerPlateHover);
|
||||
ButtonWidget::renderImpl();
|
||||
m_modeName->setColor(m_regularTextColor);
|
||||
m_playerName->setColor(m_regularTextColor);
|
||||
m_playerName->setText(m_player->name());
|
||||
} else {
|
||||
ButtonWidget::setImages(m_noPlayerPlate, m_noPlayerPlateHover);
|
||||
ButtonWidget::enable();
|
||||
ButtonWidget::renderImpl();
|
||||
m_modeName->setColor(m_disabledTextColor);
|
||||
m_playerName->setColor(m_createCharTextColor);
|
||||
m_playerName->setText(m_createCharText);
|
||||
}
|
||||
}
|
||||
|
||||
void LargeCharPlateWidget::mouseOut() {
|
||||
if (m_delete)
|
||||
m_delete->mouseOut();
|
||||
|
||||
ButtonWidget::mouseOut();
|
||||
}
|
||||
|
||||
void LargeCharPlateWidget::setPlayer(PlayerPtr player) {
|
||||
m_player = player;
|
||||
m_portrait->setEntity(m_player);
|
||||
|
||||
if (m_player) {
|
||||
m_playerName->setText(m_player->name());
|
||||
} else {
|
||||
m_playerName->setText(m_createCharText);
|
||||
}
|
||||
|
||||
auto modeTypeTextAndColor = Root::singleton().assets()->json("/interface.config:modeTypeTextAndColor").toArray();
|
||||
int modeType;
|
||||
if (m_player) {
|
||||
modeType = 1 + (int)m_player->modeType();
|
||||
} else {
|
||||
modeType = 0;
|
||||
}
|
||||
auto thisModeType = modeTypeTextAndColor[modeType].toArray();
|
||||
String modeTypeText = thisModeType[0].toString();
|
||||
Color modeTypeColor = Color::rgb(jsonToVec3B(thisModeType[1]));
|
||||
m_mode->setText(modeTypeText);
|
||||
m_mode->setColor(modeTypeColor);
|
||||
}
|
||||
|
||||
void LargeCharPlateWidget::enableDelete(WidgetCallbackFunc const& callback) {
|
||||
disableDelete();
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
auto baseImage = assets->json("/interface.config:largeCharPlate.trashButton.baseImage").toString();
|
||||
auto hoverImage = assets->json("/interface.config:largeCharPlate.trashButton.hoverImage").toString();
|
||||
auto pressedImage = assets->json("/interface.config:largeCharPlate.trashButton.pressedImage").toString();
|
||||
auto disabledImage = assets->json("/interface.config:largeCharPlate.trashButton.disabledImage").toString();
|
||||
auto offset = jsonToVec2I(assets->json("/interface.config:largeCharPlate.trashButton.offset"));
|
||||
|
||||
m_delete = make_shared<ButtonWidget>(callback, baseImage, hoverImage, pressedImage, disabledImage);
|
||||
addChild("trashButton", m_delete);
|
||||
m_delete->setPosition(offset);
|
||||
m_deleteOffset = offset;
|
||||
}
|
||||
|
||||
void LargeCharPlateWidget::disableDelete() {
|
||||
if (m_delete) {
|
||||
removeChild(m_delete.get());
|
||||
}
|
||||
|
||||
m_delete = {};
|
||||
}
|
||||
|
||||
bool LargeCharPlateWidget::sendEvent(InputEvent const& event) {
|
||||
if (event.is<MouseMoveEvent>() && m_delete) {
|
||||
if (m_delete->inMember(*m_context->mousePosition(event)))
|
||||
m_delete->mouseOver();
|
||||
else
|
||||
m_delete->mouseOut();
|
||||
}
|
||||
|
||||
if (Widget::sendEvent(event))
|
||||
return true;
|
||||
|
||||
return ButtonWidget::sendEvent(event);
|
||||
}
|
||||
|
||||
}
|
64
source/windowing/StarLargeCharPlateWidget.hpp
Normal file
64
source/windowing/StarLargeCharPlateWidget.hpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#ifndef STAR_LARGE_CHAR_PLATE_WIDGET_HPP
|
||||
#define STAR_LARGE_CHAR_PLATE_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
#include "StarButtonWidget.hpp"
|
||||
#include "StarPortraitWidget.hpp"
|
||||
#include "StarLabelWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(Player);
|
||||
|
||||
STAR_CLASS(LargeCharPlateWidget);
|
||||
class LargeCharPlateWidget : public ButtonWidget {
|
||||
public:
|
||||
LargeCharPlateWidget(WidgetCallbackFunc mainCallback, PlayerPtr player = PlayerPtr());
|
||||
|
||||
void mouseOut() override;
|
||||
|
||||
void setPlayer(PlayerPtr player = PlayerPtr());
|
||||
|
||||
void enableDelete(WidgetCallbackFunc const& callback);
|
||||
void disableDelete();
|
||||
|
||||
virtual bool sendEvent(InputEvent const& event) override;
|
||||
|
||||
protected:
|
||||
virtual void renderImpl() override;
|
||||
|
||||
private:
|
||||
PlayerPtr m_player;
|
||||
|
||||
PortraitWidgetPtr m_portrait;
|
||||
Vec2I m_portraitOffset;
|
||||
float m_portraitScale;
|
||||
|
||||
String m_playerPlateHover;
|
||||
String m_noPlayerPlate;
|
||||
String m_noPlayerPlateHover;
|
||||
String m_playerPlate;
|
||||
|
||||
LabelWidgetPtr m_playerName;
|
||||
LabelWidgetPtr m_playerPhrase;
|
||||
LabelWidgetPtr m_modeName;
|
||||
LabelWidgetPtr m_mode;
|
||||
|
||||
ButtonWidgetPtr m_delete;
|
||||
|
||||
Vec2I m_playerNameOffset;
|
||||
Vec2I m_playerPhraseOffset;
|
||||
Vec2I m_modeNameOffset;
|
||||
Vec2I m_modeOffset;
|
||||
Vec2I m_deleteOffset;
|
||||
|
||||
String m_createCharText;
|
||||
Color m_createCharTextColor;
|
||||
|
||||
Color m_regularTextColor;
|
||||
Color m_disabledTextColor;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
13
source/windowing/StarLayout.cpp
Normal file
13
source/windowing/StarLayout.cpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include "StarLayout.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
Layout::Layout() {
|
||||
markAsContainer();
|
||||
}
|
||||
|
||||
void Layout::update() {
|
||||
Widget::update();
|
||||
}
|
||||
|
||||
}
|
16
source/windowing/StarLayout.hpp
Normal file
16
source/windowing/StarLayout.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#ifndef STAR_LAYOUT_HPP
|
||||
#define STAR_LAYOUT_HPP
|
||||
#include "StarWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// VERY simple base class for a layout container object.
|
||||
class Layout : public Widget {
|
||||
public:
|
||||
Layout();
|
||||
virtual void update() override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
262
source/windowing/StarListWidget.cpp
Normal file
262
source/windowing/StarListWidget.cpp
Normal file
|
@ -0,0 +1,262 @@
|
|||
#include "StarListWidget.hpp"
|
||||
#include "StarGuiReader.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarRandom.hpp"
|
||||
#include "StarImageWidget.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ListWidget::ListWidget(Json const& schema) : m_schema(schema) {
|
||||
m_selectedItem = NPos;
|
||||
m_columns = 1;
|
||||
setSchema(m_schema);
|
||||
updateSizeAndPosition();
|
||||
}
|
||||
|
||||
ListWidget::ListWidget() {
|
||||
m_selectedItem = NPos;
|
||||
m_columns = 1;
|
||||
updateSizeAndPosition();
|
||||
}
|
||||
|
||||
RectI ListWidget::relativeBoundRect() const {
|
||||
if (m_fillDown)
|
||||
return RectI::withSize(relativePosition() - Vec2I(0, size()[1]), size());
|
||||
else
|
||||
return RectI::withSize(relativePosition(), size());
|
||||
}
|
||||
|
||||
void ListWidget::setCallback(WidgetCallbackFunc callback) {
|
||||
m_callback = move(callback);
|
||||
}
|
||||
|
||||
bool ListWidget::sendEvent(InputEvent const& event) {
|
||||
if (!m_visible)
|
||||
return false;
|
||||
|
||||
for (size_t i = m_members.size(); i != 0; --i) {
|
||||
auto child = m_members[i - 1];
|
||||
if (child->sendEvent(event)
|
||||
|| (event.is<MouseButtonDownEvent>() && child->inMember(*context()->mousePosition(event))
|
||||
&& event.get<MouseButtonDownEvent>().mouseButton == MouseButton::Left)) {
|
||||
setSelected(i - 1);
|
||||
return true;
|
||||
}
|
||||
setHovered(i - 1, event.is<MouseMoveEvent>() && child->inMember(*context()->mousePosition(event)));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ListWidget::setSchema(Json const& schema) {
|
||||
clear();
|
||||
m_schema = schema;
|
||||
try {
|
||||
m_selectedBG = schema.getString("selectedBG", "");
|
||||
m_unselectedBG = schema.getString("unselectedBG", "");
|
||||
m_hoverBG = schema.getString("hoverBG", "");
|
||||
m_disabledBG = schema.getString("disabledBG", "");
|
||||
if (m_disabledBG.empty() && !m_unselectedBG.empty())
|
||||
m_disabledBG = m_unselectedBG + Root::singleton().assets()->json("/interface.config:disabledButton").toString();
|
||||
m_spacing = jsonToVec2I(schema.get("spacing"));
|
||||
m_memberSize = jsonToVec2I(schema.get("memberSize"));
|
||||
} catch (JsonException const& e) {
|
||||
throw GuiException(strf("Missing required value in map: %s", outputException(e, false)));
|
||||
}
|
||||
updateSizeAndPosition();
|
||||
}
|
||||
|
||||
WidgetPtr ListWidget::addItem() {
|
||||
auto newItem = constructWidget();
|
||||
addChild(strf("%d", Random::randu64()), newItem);
|
||||
updateSizeAndPosition();
|
||||
|
||||
return newItem;
|
||||
}
|
||||
|
||||
WidgetPtr ListWidget::addItem(size_t at) {
|
||||
auto newItem = constructWidget();
|
||||
addChildAt(strf("%d", Random::randu64()), newItem, at);
|
||||
updateSizeAndPosition();
|
||||
|
||||
if (m_selectedItem != NPos && at <= m_selectedItem)
|
||||
setSelected(m_selectedItem + 1);
|
||||
|
||||
return newItem;
|
||||
}
|
||||
|
||||
WidgetPtr ListWidget::addItem(WidgetPtr existingItem) {
|
||||
addChild(strf("%d", Random::randu64()), existingItem);
|
||||
updateSizeAndPosition();
|
||||
|
||||
return existingItem;
|
||||
}
|
||||
|
||||
WidgetPtr ListWidget::constructWidget() {
|
||||
WidgetPtr newItem = make_shared<Widget>();
|
||||
m_reader.construct(m_schema.get("listTemplate"), newItem.get());
|
||||
newItem->setSize(m_memberSize);
|
||||
m_doScissor ? newItem->enableScissoring() : newItem->disableScissoring();
|
||||
return newItem;
|
||||
}
|
||||
|
||||
void ListWidget::updateSizeAndPosition() {
|
||||
int rows = m_members.size() % m_columns ? m_members.size() / m_columns + 1 : m_members.size() / m_columns;
|
||||
for (size_t i = 0; i < m_members.size(); i++) {
|
||||
Vec2I currentOffset;
|
||||
int col = i % m_columns;
|
||||
int row = rows - (i / m_columns) - 1;
|
||||
if (m_fillDown)
|
||||
row -= rows;
|
||||
currentOffset = Vec2I((m_memberSize[0] + m_spacing[0]) * col, (m_memberSize[1] + m_spacing[1]) * row);
|
||||
if (!m_fillDown)
|
||||
currentOffset[1] += m_spacing[1];
|
||||
m_members[i]->setPosition(currentOffset);
|
||||
}
|
||||
if (m_members.size()) {
|
||||
auto width = (m_memberSize[0] + m_spacing[0]) * m_columns;
|
||||
auto height = (m_memberSize[1] + m_spacing[1]) * rows;
|
||||
setSize(Vec2I(width, height));
|
||||
} else {
|
||||
setSize(Vec2I());
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::setEnabled(size_t pos, bool enabled) {
|
||||
if (pos != NPos && pos < listSize()) {
|
||||
if (enabled) {
|
||||
m_disabledItems.remove(pos);
|
||||
if (auto bgWidget = itemAt(pos)->fetchChild<ImageWidget>("background"))
|
||||
bgWidget->setImage(pos == m_selectedItem ? m_selectedBG : m_unselectedBG);
|
||||
} else {
|
||||
m_disabledItems.add(pos);
|
||||
if (m_selectedItem == pos)
|
||||
clearSelected();
|
||||
if (auto bgWidget = itemAt(pos)->fetchChild<ImageWidget>("background"))
|
||||
bgWidget->setImage(m_disabledBG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::setHovered(size_t pos, bool hovered) {
|
||||
if (m_hoverBG == "")
|
||||
return;
|
||||
|
||||
if (pos != m_selectedItem && pos < listSize() && !m_disabledItems.contains(pos)) {
|
||||
if (auto bgWidget = itemAt(pos)->fetchChild<ImageWidget>("background")) {
|
||||
if (hovered)
|
||||
bgWidget->setImage(m_hoverBG);
|
||||
else
|
||||
bgWidget->setImage(m_unselectedBG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::setSelected(size_t pos) {
|
||||
if ((m_selectedItem != NPos) && (m_selectedItem < listSize())) {
|
||||
if (auto bgWidget = selectedWidget()->fetchChild<ImageWidget>("background"))
|
||||
bgWidget->setImage(m_unselectedBG);
|
||||
}
|
||||
|
||||
if (!m_disabledItems.contains(pos) && m_selectedItem != pos) {
|
||||
m_selectedItem = pos;
|
||||
if (m_callback)
|
||||
m_callback(this);
|
||||
}
|
||||
|
||||
if (m_selectedItem != NPos) {
|
||||
if (auto bgWidget = selectedWidget()->fetchChild<ImageWidget>("background"))
|
||||
bgWidget->setImage(m_selectedBG);
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::clearSelected() {
|
||||
setSelected(NPos);
|
||||
}
|
||||
|
||||
void ListWidget::setSelectedWidget(WidgetPtr selected) {
|
||||
auto offset = itemPosition(selected);
|
||||
|
||||
if (offset == NPos) {
|
||||
throw GuiException("Attempted to select item not in list.");
|
||||
}
|
||||
|
||||
setSelected(offset);
|
||||
}
|
||||
|
||||
void ListWidget::registerMemberCallback(String const& name, WidgetCallbackFunc const& callback) {
|
||||
m_reader.registerCallback(name, callback);
|
||||
}
|
||||
|
||||
void ListWidget::setFillDown(bool fillDown) {
|
||||
m_fillDown = fillDown;
|
||||
}
|
||||
|
||||
void ListWidget::setColumns(uint64_t columns) {
|
||||
m_columns = columns;
|
||||
}
|
||||
|
||||
void ListWidget::removeItem(size_t at) {
|
||||
removeChildAt(at);
|
||||
if (m_selectedItem == at)
|
||||
setSelected(NPos);
|
||||
else if (m_selectedItem != NPos && m_selectedItem > at)
|
||||
setSelected(m_selectedItem - 1);
|
||||
|
||||
updateSizeAndPosition();
|
||||
}
|
||||
|
||||
void ListWidget::removeItem(WidgetPtr item) {
|
||||
auto offset = itemPosition(item);
|
||||
|
||||
if (offset == NPos) {
|
||||
throw GuiException("Attempted to remove item not in list.");
|
||||
}
|
||||
|
||||
removeItem(offset);
|
||||
}
|
||||
|
||||
void ListWidget::clear() {
|
||||
setSelected(NPos);
|
||||
removeAllChildren();
|
||||
updateSizeAndPosition();
|
||||
}
|
||||
|
||||
size_t ListWidget::selectedItem() const {
|
||||
return m_selectedItem;
|
||||
}
|
||||
|
||||
size_t ListWidget::itemPosition(WidgetPtr item) const {
|
||||
size_t offset = NPos;
|
||||
for (size_t i = 0; i < m_members.size(); ++i) {
|
||||
if (m_members[i] == item) {
|
||||
offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
WidgetPtr ListWidget::itemAt(size_t n) const {
|
||||
if (n < m_members.size()) {
|
||||
return m_members[n];
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
WidgetPtr ListWidget::selectedWidget() const {
|
||||
return itemAt(m_selectedItem);
|
||||
}
|
||||
|
||||
List<WidgetPtr> const& ListWidget::list() const {
|
||||
return m_members;
|
||||
}
|
||||
|
||||
size_t ListWidget::listSize() const {
|
||||
return numChildren();
|
||||
}
|
||||
|
||||
}
|
70
source/windowing/StarListWidget.hpp
Normal file
70
source/windowing/StarListWidget.hpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
#ifndef STAR_LIST_WIDGET_HPP
|
||||
#define STAR_LIST_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
#include "StarGuiReader.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(GuiReader);
|
||||
STAR_CLASS(ListWidget);
|
||||
|
||||
class ListWidget : public Widget {
|
||||
public:
|
||||
ListWidget(Json const& schema);
|
||||
ListWidget();
|
||||
|
||||
RectI relativeBoundRect() const override;
|
||||
|
||||
// Callback is called when the selection changes
|
||||
void setCallback(WidgetCallbackFunc callback);
|
||||
|
||||
bool sendEvent(InputEvent const& event) override;
|
||||
void setSchema(Json const& schema);
|
||||
WidgetPtr constructWidget();
|
||||
WidgetPtr addItem();
|
||||
WidgetPtr addItem(size_t at);
|
||||
WidgetPtr addItem(WidgetPtr existingItem);
|
||||
void removeItem(size_t at);
|
||||
void removeItem(WidgetPtr item);
|
||||
void clear();
|
||||
size_t selectedItem() const;
|
||||
size_t itemPosition(WidgetPtr item) const;
|
||||
WidgetPtr itemAt(size_t n) const;
|
||||
WidgetPtr selectedWidget() const;
|
||||
List<WidgetPtr> const& list() const;
|
||||
size_t listSize() const;
|
||||
void setEnabled(size_t pos, bool enabled);
|
||||
void setHovered(size_t pos, bool hovered);
|
||||
void setSelected(size_t pos);
|
||||
void clearSelected();
|
||||
void setSelectedWidget(WidgetPtr selected);
|
||||
|
||||
void registerMemberCallback(String const& name, WidgetCallbackFunc const& callback);
|
||||
|
||||
void setFillDown(bool fillDown);
|
||||
void setColumns(uint64_t columns);
|
||||
|
||||
private:
|
||||
void updateSizeAndPosition();
|
||||
|
||||
Json m_schema;
|
||||
GuiReader m_reader;
|
||||
|
||||
Set<size_t> m_disabledItems;
|
||||
size_t m_selectedItem;
|
||||
WidgetCallbackFunc m_callback;
|
||||
|
||||
String m_selectedBG;
|
||||
String m_unselectedBG;
|
||||
String m_hoverBG;
|
||||
String m_disabledBG;
|
||||
Vec2I m_spacing;
|
||||
|
||||
bool m_fillDown;
|
||||
uint64_t m_columns;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
369
source/windowing/StarPane.cpp
Normal file
369
source/windowing/StarPane.cpp
Normal file
|
@ -0,0 +1,369 @@
|
|||
#include "StarPane.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
EnumMap<PaneAnchor> const PaneAnchorNames{
|
||||
{PaneAnchor::None, "none"},
|
||||
{PaneAnchor::BottomLeft, "bottomLeft"},
|
||||
{PaneAnchor::BottomRight, "bottomRight"},
|
||||
{PaneAnchor::TopLeft, "topLeft"},
|
||||
{PaneAnchor::TopRight, "topRight"},
|
||||
{PaneAnchor::CenterBottom, "centerBottom"},
|
||||
{PaneAnchor::CenterTop, "centerTop"},
|
||||
{PaneAnchor::CenterLeft, "centerLeft"},
|
||||
{PaneAnchor::CenterRight, "centerRight"},
|
||||
{PaneAnchor::Center, "center"},
|
||||
};
|
||||
|
||||
Pane::Pane() {
|
||||
m_dragActive = m_lockPosition = false;
|
||||
m_dismissed = true;
|
||||
m_centerOffset = Vec2I();
|
||||
m_anchor = PaneAnchor::None;
|
||||
m_anchorOffset = Vec2I();
|
||||
m_visible = false;
|
||||
m_hasDisplayed = false;
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
m_fontSize = assets->json("/interface.config:font.baseSize").toInt();
|
||||
m_iconOffset = jsonToVec2I(assets->json("/interface.config:paneIconOffset"));
|
||||
m_titleOffset = jsonToVec2I(assets->json("/interface.config:paneTitleOffset"));
|
||||
m_subTitleOffset = jsonToVec2I(assets->json("/interface.config:paneSubTitleOffset"));
|
||||
m_titleColor = jsonToColor(assets->json("/interface.config:paneTitleColor"));
|
||||
m_subTitleColor = jsonToColor(assets->json("/interface.config:paneSubTitleColor"));
|
||||
}
|
||||
|
||||
void Pane::displayed() {
|
||||
m_dismissed = false;
|
||||
m_hasDisplayed = true;
|
||||
show();
|
||||
}
|
||||
|
||||
void Pane::dismissed() {
|
||||
if (m_clickDown)
|
||||
m_clickDown->mouseOut();
|
||||
m_clickDown.reset();
|
||||
if (m_mouseOver)
|
||||
m_mouseOver->mouseOut();
|
||||
m_mouseOver.reset();
|
||||
hide();
|
||||
m_dismissed = true;
|
||||
}
|
||||
|
||||
void Pane::dismiss() {
|
||||
m_dismissed = true;
|
||||
}
|
||||
|
||||
bool Pane::isDismissed() const {
|
||||
return m_dismissed;
|
||||
}
|
||||
|
||||
bool Pane::isDisplayed() const {
|
||||
return !m_dismissed;
|
||||
}
|
||||
|
||||
bool Pane::sendEvent(InputEvent const& event) {
|
||||
if (m_visible) {
|
||||
if (event.is<MouseButtonDownEvent>() || event.is<MouseButtonUpEvent>() || event.is<MouseMoveEvent>()
|
||||
|| event.is<MouseWheelEvent>()) {
|
||||
Vec2I mousePos = *m_context->mousePosition(event);
|
||||
// First, handle preliminary mouse out / click up events
|
||||
if (m_mouseOver) {
|
||||
if (!m_mouseOver->inMember(mousePos) || !m_mouseOver->active()) {
|
||||
m_mouseOver->mouseOut();
|
||||
m_mouseOver.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (event.is<MouseButtonUpEvent>())
|
||||
m_clickDown.reset();
|
||||
|
||||
WidgetPtr newClickDown;
|
||||
WidgetPtr newMouseOver;
|
||||
WidgetPtr newFocusWidget;
|
||||
|
||||
// Then, go through widgets in highest to lowest z-order and handle mouse
|
||||
// over, focus, and capture events.
|
||||
for (auto const& widget : reverseIterate(m_members)) {
|
||||
if (widget->inMember(mousePos) && widget->active() && widget->interactive()) {
|
||||
WidgetPtr topWidget = widget;
|
||||
WidgetPtr child = getChildAt(mousePos);
|
||||
if (child->active() && child->interactive()) {
|
||||
if (event.is<MouseButtonDownEvent>()
|
||||
&& (event.get<MouseButtonDownEvent>().mouseButton == MouseButton::Left
|
||||
|| event.get<MouseButtonDownEvent>().mouseButton == MouseButton::Right)) {
|
||||
if (!newClickDown)
|
||||
newClickDown = child;
|
||||
|
||||
if (!newFocusWidget)
|
||||
newFocusWidget = child;
|
||||
}
|
||||
|
||||
if (!newMouseOver)
|
||||
newMouseOver = child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_clickDown != newClickDown)
|
||||
m_clickDown = newClickDown;
|
||||
|
||||
if (m_mouseOver != newMouseOver) {
|
||||
if (m_mouseOver)
|
||||
m_mouseOver->mouseOut();
|
||||
m_mouseOver = newMouseOver;
|
||||
if (m_mouseOver) {
|
||||
if (m_clickDown == m_mouseOver)
|
||||
m_mouseOver->mouseReturnStillDown();
|
||||
else
|
||||
m_mouseOver->mouseOver();
|
||||
}
|
||||
}
|
||||
|
||||
if (newFocusWidget && m_focusWidget != newFocusWidget) {
|
||||
if (auto focusWidget = m_focusWidget)
|
||||
focusWidget->blur();
|
||||
m_focusWidget = newFocusWidget;
|
||||
m_focusWidget->focus();
|
||||
}
|
||||
|
||||
// Finally go through widgets in highest to lowest z-order and send the
|
||||
// raw event, stopping further processing if the widget consumes it.
|
||||
for (auto const& widget : reverseIterate(m_members)) {
|
||||
if (widget->inMember(mousePos) && widget->active() && widget->interactive()) {
|
||||
if (widget->sendEvent(event))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.is<MouseButtonDownEvent>()) {
|
||||
Vec2I mousePos = *m_context->mousePosition(event);
|
||||
if (inDragArea(mousePos) && !m_lockPosition) {
|
||||
setDragActive(true, mousePos);
|
||||
return true;
|
||||
}
|
||||
if (inWindow(mousePos))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_focusWidget) {
|
||||
if (m_focusWidget->sendEvent(event))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Pane::setFocus(Widget const* focus) {
|
||||
if (m_focusWidget.get() == focus)
|
||||
return;
|
||||
if (m_focusWidget)
|
||||
m_focusWidget->blur();
|
||||
if (auto c = childPtr(focus))
|
||||
m_focusWidget = move(c);
|
||||
else
|
||||
throw GuiException("Cannot set focus on a widget which is not a child of this pane");
|
||||
}
|
||||
|
||||
void Pane::removeFocus(Widget const* focus) {
|
||||
if (m_focusWidget.get() == focus)
|
||||
m_focusWidget.reset();
|
||||
}
|
||||
|
||||
void Pane::removeFocus() {
|
||||
m_focusWidget.reset();
|
||||
}
|
||||
|
||||
Pane const* Pane::window() const {
|
||||
return this;
|
||||
}
|
||||
|
||||
Pane* Pane::window() {
|
||||
return this;
|
||||
}
|
||||
|
||||
void Pane::renderImpl() {
|
||||
if (m_bgFooter != "")
|
||||
m_context->drawInterfaceQuad(m_bgFooter, Vec2F(position()));
|
||||
|
||||
if (m_bgBody != "")
|
||||
m_context->drawInterfaceQuad(m_bgBody, Vec2F(position()) + Vec2F(0, m_footerSize[1]));
|
||||
|
||||
if (m_bgHeader != "") {
|
||||
auto headerPos = Vec2F(position()) + Vec2F(0, m_footerSize[1] + m_bodySize[1]);
|
||||
m_context->drawInterfaceQuad(m_bgHeader, headerPos);
|
||||
|
||||
if (m_icon) {
|
||||
m_icon->setPosition(Vec2I(0, m_footerSize[1] + m_bodySize[1]) + m_iconOffset);
|
||||
m_icon->render(m_drawingArea);
|
||||
m_context->resetInterfaceScissorRect();
|
||||
}
|
||||
|
||||
m_context->setFontSize(m_fontSize);
|
||||
m_context->setFontColor(m_titleColor.toRgba());
|
||||
m_context->setFontMode(FontMode::Shadow);
|
||||
m_context->renderInterfaceText(m_title, {headerPos + Vec2F(m_titleOffset)});
|
||||
m_context->setFontColor(m_subTitleColor.toRgba());
|
||||
m_context->renderInterfaceText(m_subTitle, {headerPos + Vec2F(m_subTitleOffset)});
|
||||
m_context->setFontMode(FontMode::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
void Pane::update() {
|
||||
if (m_visible) {
|
||||
for (auto const& widget : m_members) {
|
||||
widget->update();
|
||||
if ((m_focusWidget == widget) != widget->hasFocus()) {
|
||||
m_focusWidget.reset();
|
||||
widget->blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Pane::tick() {}
|
||||
|
||||
bool Pane::dragActive() const {
|
||||
return m_dragActive;
|
||||
}
|
||||
|
||||
Vec2I Pane::dragMouseOrigin() const {
|
||||
return m_dragMouseOrigin;
|
||||
}
|
||||
|
||||
void Pane::setDragActive(bool dragActive, Vec2I dragMouseOrigin) {
|
||||
m_dragActive = dragActive;
|
||||
m_dragMouseOrigin = dragMouseOrigin;
|
||||
}
|
||||
|
||||
void Pane::drag(Vec2I mousePosition) {
|
||||
Vec2I delta = mousePosition - m_dragMouseOrigin;
|
||||
setPosition(position() + delta);
|
||||
m_dragMouseOrigin += delta;
|
||||
}
|
||||
|
||||
bool Pane::inWindow(Vec2I const& position) const {
|
||||
return screenBoundRect().contains(position);
|
||||
}
|
||||
|
||||
bool Pane::inDragArea(Vec2I const& position) const {
|
||||
return inWindow(position) && (position[1] < (this->position()[1] + m_footerSize[1])
|
||||
|| position[1] > (this->position()[1] + (m_footerSize[1] + m_bodySize[1])));
|
||||
}
|
||||
|
||||
Vec2I Pane::cursorRelativeToPane(Vec2I const& position) const {
|
||||
return position - this->position();
|
||||
}
|
||||
|
||||
Vec2I Pane::centerOffset() const {
|
||||
return m_centerOffset;
|
||||
}
|
||||
|
||||
void Pane::setBG(BGResult const& res) {
|
||||
setBG(res.header, res.body, res.footer);
|
||||
}
|
||||
|
||||
void Pane::setBG(String const& header, String const& body, String const& footer) {
|
||||
m_bgHeader = header;
|
||||
m_bgBody = body;
|
||||
m_bgFooter = footer;
|
||||
if (m_bgHeader != "") {
|
||||
m_headerSize = Vec2I(m_context->textureSize(m_bgHeader));
|
||||
} else {
|
||||
m_headerSize = {};
|
||||
}
|
||||
if (m_bgBody != "") {
|
||||
m_bodySize = Vec2I(m_context->textureSize(m_bgBody));
|
||||
} else {
|
||||
m_bodySize = {};
|
||||
}
|
||||
if (m_bgFooter != "") {
|
||||
m_footerSize = Vec2I(m_context->textureSize(m_bgFooter));
|
||||
} else {
|
||||
m_footerSize = {};
|
||||
}
|
||||
|
||||
setSize(Vec2I(std::max(std::max(m_headerSize[0], m_bodySize[0]), m_footerSize[0]),
|
||||
m_headerSize[1] + m_bodySize[1] + m_footerSize[1]));
|
||||
}
|
||||
|
||||
Pane::BGResult Pane::getBG() const {
|
||||
return {m_bgHeader, m_bgBody, m_bgFooter};
|
||||
}
|
||||
|
||||
void Pane::lockPosition() {
|
||||
m_lockPosition = true;
|
||||
}
|
||||
|
||||
void Pane::unlockPosition() {
|
||||
m_lockPosition = false;
|
||||
}
|
||||
|
||||
void Pane::setTitle(WidgetPtr icon, String const& title, String const& subTitle) {
|
||||
m_icon = icon;
|
||||
m_title = title;
|
||||
m_subTitle = subTitle;
|
||||
if (m_icon) {
|
||||
m_icon->setParent(this);
|
||||
m_icon->show();
|
||||
}
|
||||
}
|
||||
|
||||
void Pane::setTitleString(String const& title, String const& subTitle) {
|
||||
m_title = title;
|
||||
m_subTitle = subTitle;
|
||||
}
|
||||
|
||||
void Pane::setTitleIcon(WidgetPtr icon) {
|
||||
m_icon = icon;
|
||||
if (m_icon) {
|
||||
m_icon->setParent(this);
|
||||
m_icon->show();
|
||||
}
|
||||
}
|
||||
|
||||
String Pane::title() const {
|
||||
return m_title;
|
||||
}
|
||||
|
||||
String Pane::subTitle() const {
|
||||
return m_subTitle;
|
||||
}
|
||||
|
||||
WidgetPtr Pane::titleIcon() const {
|
||||
return m_icon;
|
||||
}
|
||||
|
||||
PaneAnchor Pane::anchor() {
|
||||
return m_anchor;
|
||||
}
|
||||
|
||||
void Pane::setAnchor(PaneAnchor anchor) {
|
||||
m_anchor = anchor;
|
||||
}
|
||||
|
||||
Vec2I Pane::anchorOffset() const {
|
||||
return m_anchorOffset;
|
||||
}
|
||||
|
||||
void Pane::setAnchorOffset(Vec2I anchorOffset) {
|
||||
m_anchorOffset = anchorOffset;
|
||||
}
|
||||
|
||||
bool Pane::hasDisplayed() const {
|
||||
return m_hasDisplayed;
|
||||
}
|
||||
|
||||
PanePtr Pane::createTooltip(Vec2I const&) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<String> Pane::cursorOverride(Vec2I const&) {
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
130
source/windowing/StarPane.hpp
Normal file
130
source/windowing/StarPane.hpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
#ifndef STAR_PANE_HPP
|
||||
#define STAR_PANE_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
#include "StarBiMap.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(Pane);
|
||||
|
||||
enum class PaneAnchor {
|
||||
None,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
CenterBottom,
|
||||
CenterTop,
|
||||
CenterLeft,
|
||||
CenterRight,
|
||||
Center
|
||||
};
|
||||
extern EnumMap<PaneAnchor> const PaneAnchorNames;
|
||||
|
||||
class Pane : public Widget {
|
||||
public:
|
||||
Pane();
|
||||
|
||||
struct BGResult {
|
||||
String header;
|
||||
String body;
|
||||
String footer;
|
||||
};
|
||||
|
||||
virtual void displayed();
|
||||
virtual void dismissed();
|
||||
|
||||
void dismiss();
|
||||
bool isDismissed() const;
|
||||
bool isDisplayed() const;
|
||||
|
||||
Vec2I centerOffset() const;
|
||||
|
||||
// members are drawn strictly in the order they are added,
|
||||
// so add them in the correct order.
|
||||
|
||||
virtual bool sendEvent(InputEvent const& event);
|
||||
virtual void setFocus(Widget const* focus);
|
||||
virtual void removeFocus(Widget const* focus);
|
||||
virtual void removeFocus();
|
||||
|
||||
virtual void update();
|
||||
virtual void tick();
|
||||
|
||||
bool dragActive() const;
|
||||
Vec2I dragMouseOrigin() const;
|
||||
void setDragActive(bool dragActive, Vec2I dragMouseOrigin);
|
||||
void drag(Vec2I mousePosition);
|
||||
|
||||
bool inWindow(Vec2I const& position) const;
|
||||
bool inDragArea(Vec2I const& position) const;
|
||||
Vec2I cursorRelativeToPane(Vec2I const& position) const;
|
||||
|
||||
void setBG(BGResult const& res);
|
||||
void setBG(String const& header, String const& body = "", String const& footer = "");
|
||||
BGResult getBG() const;
|
||||
|
||||
void lockPosition();
|
||||
void unlockPosition();
|
||||
|
||||
void setTitle(WidgetPtr icon, String const& title, String const& subTitle);
|
||||
void setTitleString(String const& title, String const& subTitle);
|
||||
void setTitleIcon(WidgetPtr icon);
|
||||
String title() const;
|
||||
String subTitle() const;
|
||||
WidgetPtr titleIcon() const;
|
||||
|
||||
virtual Pane* window();
|
||||
virtual Pane const* window() const;
|
||||
|
||||
PaneAnchor anchor();
|
||||
void setAnchor(PaneAnchor anchor);
|
||||
Vec2I anchorOffset() const;
|
||||
void setAnchorOffset(Vec2I anchorOffset);
|
||||
bool hasDisplayed() const;
|
||||
|
||||
// If a tooltip popup should be created at the given mouse position, return a
|
||||
// new pane to be used as the tooltip.
|
||||
virtual PanePtr createTooltip(Vec2I const& screenPosition);
|
||||
virtual Maybe<String> cursorOverride(Vec2I const& screenPosition);
|
||||
|
||||
protected:
|
||||
virtual void renderImpl();
|
||||
|
||||
String m_bgHeader;
|
||||
String m_bgBody;
|
||||
String m_bgFooter;
|
||||
|
||||
Vec2I m_footerSize;
|
||||
Vec2I m_bodySize;
|
||||
Vec2I m_headerSize;
|
||||
|
||||
bool m_dismissed;
|
||||
bool m_dragActive;
|
||||
Vec2I m_dragMouseOrigin;
|
||||
bool m_lockPosition;
|
||||
Vec2I m_centerOffset;
|
||||
|
||||
WidgetPtr m_mouseOver;
|
||||
WidgetPtr m_clickDown;
|
||||
WidgetPtr m_focusWidget;
|
||||
|
||||
WidgetPtr m_icon;
|
||||
String m_title;
|
||||
String m_subTitle;
|
||||
unsigned m_fontSize;
|
||||
Vec2I m_iconOffset;
|
||||
Vec2I m_titleOffset;
|
||||
Vec2I m_subTitleOffset;
|
||||
Color m_titleColor;
|
||||
Color m_subTitleColor;
|
||||
|
||||
PaneAnchor m_anchor;
|
||||
Vec2I m_anchorOffset;
|
||||
bool m_hasDisplayed;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
374
source/windowing/StarPaneManager.cpp
Normal file
374
source/windowing/StarPaneManager.cpp
Normal file
|
@ -0,0 +1,374 @@
|
|||
#include "StarPaneManager.hpp"
|
||||
#include "StarGameTypes.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
PaneManager::PaneManager()
|
||||
: m_context(GuiContext::singletonPtr()), m_prevInterfaceScale(1) {
|
||||
auto assets = Root::singleton().assets();
|
||||
m_tooltipMouseoverTime = assets->json("/panes.config:tooltipMouseoverTime").toFloat();
|
||||
m_tooltipMouseoverRadius = assets->json("/panes.config:tooltipMouseoverRadius").toFloat();
|
||||
m_tooltipMouseOffset = jsonToVec2I(assets->json("/panes.config:tooltipMouseoverOffset"));
|
||||
|
||||
m_tooltipShowTimer = m_tooltipMouseoverTime;
|
||||
}
|
||||
|
||||
void PaneManager::displayPane(PaneLayer paneLayer, PanePtr const& pane, DismissCallback onDismiss) {
|
||||
if (!m_displayedPanes[paneLayer].insertFront(move(pane), move(onDismiss)).second)
|
||||
throw GuiException("Pane displayed twice in PaneManager::displayPane");
|
||||
|
||||
if (!pane->hasDisplayed() && pane->anchor() == PaneAnchor::None)
|
||||
pane->setPosition(Vec2I((windowSize() - pane->size()) / 2) + pane->centerOffset()); // center it
|
||||
|
||||
pane->displayed();
|
||||
}
|
||||
|
||||
bool PaneManager::isDisplayed(PanePtr const& pane) const {
|
||||
for (auto const& layerPair : m_displayedPanes) {
|
||||
if (layerPair.second.contains(pane))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PaneManager::dismissPane(PanePtr const& pane) {
|
||||
if (!dismiss(pane))
|
||||
throw GuiException("No such pane in PaneManager::dismissPane");
|
||||
}
|
||||
|
||||
void PaneManager::dismissAllPanes(Set<PaneLayer> const& paneLayers) {
|
||||
for (auto const& paneLayer : paneLayers) {
|
||||
for (auto const& panePair : copy(m_displayedPanes[paneLayer]))
|
||||
dismiss(panePair.first);
|
||||
}
|
||||
}
|
||||
|
||||
void PaneManager::dismissAllPanes() {
|
||||
for (auto layerPair : copy(m_displayedPanes)) {
|
||||
for (auto const& panePair : layerPair.second)
|
||||
dismiss(panePair.first);
|
||||
}
|
||||
}
|
||||
|
||||
PanePtr PaneManager::topPane(Set<PaneLayer> const& paneLayers) const {
|
||||
for (auto const& layerPair : m_displayedPanes) {
|
||||
if (paneLayers.contains(layerPair.first) && !layerPair.second.empty())
|
||||
return layerPair.second.firstKey();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
PanePtr PaneManager::topPane() const {
|
||||
for (auto const& layerPair : m_displayedPanes) {
|
||||
if (!layerPair.second.empty())
|
||||
return layerPair.second.firstKey();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void PaneManager::bringToTop(PanePtr const& pane) {
|
||||
for (auto& layerPair : m_displayedPanes) {
|
||||
if (layerPair.second.contains(pane)) {
|
||||
layerPair.second.toFront(pane);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw GuiException("Pane was not displayed in PaneManager::bringToTop");
|
||||
}
|
||||
|
||||
void PaneManager::bringPaneAdjacent(PanePtr const& anchor, PanePtr const& adjacent, int gap) {
|
||||
Vec2I centerAdjacent = anchor->position() + (anchor->size() / 2) - (adjacent->size() / 2);
|
||||
centerAdjacent = centerAdjacent.piecewiseClamp(Vec2I(), windowSize() - adjacent->size()); // keeps pane inside window
|
||||
|
||||
if (anchor->position()[0] + anchor->size()[0] + gap + adjacent->size()[0] <= windowSize()[0])
|
||||
adjacent->setPosition(Vec2I(anchor->position()[0] + anchor->size()[0] + gap, centerAdjacent[1])); // place to the right
|
||||
else if (anchor->position()[0] - gap - adjacent->size()[0] >= 0)
|
||||
adjacent->setPosition(Vec2I(anchor->position()[0] - gap - adjacent->size()[0], centerAdjacent[1])); // place to the left
|
||||
else if (anchor->position()[1] + anchor->size()[1] + gap + adjacent->size()[1] <= windowSize()[1])
|
||||
adjacent->setPosition(Vec2I(centerAdjacent[0], anchor->position()[1] + anchor->size()[1] + gap)); // place above
|
||||
else if (anchor->position()[1] - gap - adjacent->size()[1] >= 0)
|
||||
adjacent->setPosition(Vec2I(centerAdjacent[0], anchor->position()[1] - gap - adjacent->size()[1])); // place below
|
||||
else
|
||||
adjacent->setPosition(centerAdjacent);
|
||||
|
||||
bringToTop(adjacent);
|
||||
}
|
||||
|
||||
PanePtr PaneManager::getPaneAt(Set<PaneLayer> const& paneLayers, Vec2I const& position) const {
|
||||
for (auto const& layerPair : m_displayedPanes) {
|
||||
if (!paneLayers.contains(layerPair.first))
|
||||
continue;
|
||||
|
||||
for (auto const& panePair : layerPair.second) {
|
||||
if (panePair.first->inWindow(position) && panePair.first->active())
|
||||
return panePair.first;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
PanePtr PaneManager::getPaneAt(Vec2I const& position) const {
|
||||
for (auto const& layerPair : m_displayedPanes) {
|
||||
for (auto const& panePair : layerPair.second) {
|
||||
if (panePair.first->inWindow(position) && panePair.first->active())
|
||||
return panePair.first;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void PaneManager::setBackgroundWidget(WidgetPtr bg) {
|
||||
m_backgroundWidget = bg;
|
||||
}
|
||||
|
||||
PanePtr PaneManager::keyboardCapturedPane() const {
|
||||
for (auto const& layerPair : m_displayedPanes) {
|
||||
for (auto const& panePair : layerPair.second) {
|
||||
if (panePair.first->active() && panePair.first->keyboardCaptured() != KeyboardCaptureMode::None)
|
||||
return panePair.first;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool PaneManager::keyboardCapturedForTextInput() const {
|
||||
if (auto pane = keyboardCapturedPane())
|
||||
return pane->keyboardCaptured() == KeyboardCaptureMode::TextInput;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PaneManager::sendInputEvent(InputEvent const& event) {
|
||||
if (event.is<MouseMoveEvent>()) {
|
||||
m_tooltipLastMousePos = *m_context->mousePosition(event);
|
||||
|
||||
for (auto const& layerPair : m_displayedPanes) {
|
||||
for (auto const& panePair : layerPair.second) {
|
||||
if (panePair.first->dragActive()) {
|
||||
panePair.first->drag(*m_context->mousePosition(event));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.is<MouseButtonDownEvent>() || vmag(m_tooltipInitialPosition - m_tooltipLastMousePos) > m_tooltipMouseoverRadius) {
|
||||
m_tooltipShowTimer = m_tooltipMouseoverTime;
|
||||
if (m_activeTooltip) {
|
||||
dismiss(m_activeTooltip);
|
||||
m_activeTooltip.reset();
|
||||
m_tooltipParentPane.reset();
|
||||
m_tooltipShowTimer = m_tooltipMouseoverTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.is<MouseButtonUpEvent>()) {
|
||||
for (auto const& layerPair : m_displayedPanes) {
|
||||
for (auto const& panePair : layerPair.second) {
|
||||
if (panePair.first->dragActive()) {
|
||||
panePair.first->setDragActive(false, {});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The gui close event can only be intercepted by a pane that has captured
|
||||
// the keyboard otherwise it will always be used to close first before being
|
||||
// a normal event. This is so a window can control its own closing if it
|
||||
// really needs to (like the keybindings window).
|
||||
if (event.is<KeyDownEvent>() && m_context->actions(event).contains(InterfaceAction::GuiClose)) {
|
||||
if (auto top = topPane({PaneLayer::ModalWindow, PaneLayer::Window})) {
|
||||
dismiss(top);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a pane that has captured the keyboard, keyboard events will
|
||||
// ONLY be sent to it.
|
||||
auto keyCapturePane = keyboardCapturedPane();
|
||||
if (keyCapturePane && (event.is<KeyDownEvent>() || event.is<KeyUpEvent>() || event.is<TextInputEvent>()))
|
||||
return keyCapturePane->sendEvent(event);
|
||||
|
||||
bool foundModal = false;
|
||||
for (auto& layerPair : m_displayedPanes) {
|
||||
for (auto const& panePair : copy(layerPair.second)) {
|
||||
if (panePair.first->sendEvent(event)) {
|
||||
if (event.is<MouseButtonDownEvent>())
|
||||
layerPair.second.toFront(panePair.first);
|
||||
return true;
|
||||
}
|
||||
// If any modal windows are shown, Only the first modal window should
|
||||
// have a chance to consume the input event and all other panes below it
|
||||
// including different layers should ignore it.
|
||||
if (layerPair.first == PaneLayer::ModalWindow) {
|
||||
foundModal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundModal)
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PaneManager::render() {
|
||||
if (m_backgroundWidget) {
|
||||
auto size = m_backgroundWidget->size();
|
||||
m_backgroundWidget->setPosition(Vec2I((windowSize()[0] - size[0]) / 2, (windowSize()[1] - size[1]) / 2));
|
||||
m_backgroundWidget->render(RectI(Vec2I(), windowSize()));
|
||||
}
|
||||
|
||||
for (auto const& layerPair : reverseIterate(m_displayedPanes)) {
|
||||
for (auto const& panePair : reverseIterate(layerPair.second)) {
|
||||
if (panePair.first->active()) {
|
||||
if (m_prevInterfaceScale != m_context->interfaceScale())
|
||||
panePair.first->setPosition(
|
||||
calculateNewInterfacePosition(panePair.first, (float)m_context->interfaceScale() / m_prevInterfaceScale));
|
||||
|
||||
panePair.first->setDrawingOffset(calculatePaneOffset(panePair.first));
|
||||
panePair.first->render(RectI(Vec2I(), windowSize()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_context->resetInterfaceScissorRect();
|
||||
m_prevInterfaceScale = m_context->interfaceScale();
|
||||
}
|
||||
|
||||
void PaneManager::update() {
|
||||
m_tooltipShowTimer -= WorldTimestep;
|
||||
if (m_tooltipShowTimer < 0 && !m_activeTooltip) {
|
||||
if (auto parentPane = getPaneAt(m_tooltipLastMousePos)) {
|
||||
if (auto tooltip = parentPane->createTooltip(m_tooltipLastMousePos)) {
|
||||
m_activeTooltip = move(tooltip);
|
||||
m_tooltipParentPane = move(parentPane);
|
||||
m_tooltipInitialPosition = m_tooltipLastMousePos;
|
||||
displayPane(PaneLayer::Tooltip, m_activeTooltip);
|
||||
|
||||
Vec2I offsetDirection = Vec2I::filled(1);
|
||||
Vec2I offsetAdjust = Vec2I();
|
||||
|
||||
if (m_tooltipLastMousePos[0] + m_tooltipMouseOffset[0] + m_activeTooltip->size()[0] > (int)m_context->windowWidth() / m_context->interfaceScale()) {
|
||||
offsetDirection[0] = -1;
|
||||
offsetAdjust[0] = -m_activeTooltip->size()[0];
|
||||
}
|
||||
|
||||
if (m_tooltipLastMousePos[1] + m_tooltipMouseOffset[1] - m_activeTooltip->size()[1] < 0)
|
||||
offsetDirection[1] = -1;
|
||||
else
|
||||
offsetAdjust[1] = -m_activeTooltip->size()[1];
|
||||
|
||||
m_activeTooltip->setPosition(m_tooltipLastMousePos + (offsetAdjust + m_tooltipMouseOffset.piecewiseMultiply(offsetDirection)));
|
||||
} else {
|
||||
m_tooltipShowTimer = m_tooltipMouseoverTime;
|
||||
}
|
||||
}
|
||||
} else if (m_activeTooltip && !m_tooltipParentPane->isDisplayed()) {
|
||||
dismiss(m_activeTooltip);
|
||||
m_activeTooltip.reset();
|
||||
m_tooltipParentPane.reset();
|
||||
}
|
||||
|
||||
for (auto const& layerPair : m_displayedPanes) {
|
||||
for (auto const& panePair : copy(layerPair.second)) {
|
||||
if (panePair.first->isDismissed())
|
||||
dismiss(panePair.first);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& layerPair : reverseIterate(m_displayedPanes)) {
|
||||
for (auto const& panePair : reverseIterate(layerPair.second)) {
|
||||
panePair.first->tick();
|
||||
if (panePair.first->active())
|
||||
panePair.first->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vec2I PaneManager::windowSize() const {
|
||||
return Vec2I(m_context->windowInterfaceSize());
|
||||
}
|
||||
|
||||
Vec2I PaneManager::calculatePaneOffset(PanePtr const& pane) const {
|
||||
Vec2I size = pane->size();
|
||||
switch (pane->anchor()) {
|
||||
case PaneAnchor::None:
|
||||
return pane->anchorOffset();
|
||||
case PaneAnchor::BottomLeft:
|
||||
return pane->anchorOffset();
|
||||
case PaneAnchor::BottomRight:
|
||||
return pane->anchorOffset() + Vec2I{windowSize()[0] - size[0], 0};
|
||||
case PaneAnchor::TopLeft:
|
||||
return pane->anchorOffset() + Vec2I{0, windowSize()[1] - size[1]};
|
||||
case PaneAnchor::TopRight:
|
||||
return pane->anchorOffset() + (windowSize() - size);
|
||||
case PaneAnchor::CenterTop:
|
||||
return pane->anchorOffset() + Vec2I{(windowSize()[0] - size[0]) / 2, windowSize()[1] - size[1]};
|
||||
case PaneAnchor::CenterBottom:
|
||||
return pane->anchorOffset() + Vec2I{(windowSize()[0] - size[0]) / 2, 0};
|
||||
case PaneAnchor::CenterLeft:
|
||||
return pane->anchorOffset() + Vec2I{0, (windowSize()[1] - size[1]) / 2};
|
||||
case PaneAnchor::CenterRight:
|
||||
return pane->anchorOffset() + Vec2I{windowSize()[0] - size[0], (windowSize()[1] - size[1]) / 2};
|
||||
case PaneAnchor::Center:
|
||||
return pane->anchorOffset() + ((windowSize() - size) / 2);
|
||||
default:
|
||||
return pane->anchorOffset();
|
||||
}
|
||||
}
|
||||
|
||||
Vec2I PaneManager::calculateNewInterfacePosition(PanePtr const& pane, float interfaceScaleRatio) const {
|
||||
Vec2F position(pane->relativePosition());
|
||||
Vec2F size(pane->size());
|
||||
Mat3F scale;
|
||||
switch (pane->anchor()) {
|
||||
case PaneAnchor::None:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio, Vec2F(windowSize()) / 2);
|
||||
case PaneAnchor::BottomLeft:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio);
|
||||
case PaneAnchor::BottomRight:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio, {size[0], 0});
|
||||
case PaneAnchor::TopLeft:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio, {0, size[1]});
|
||||
case PaneAnchor::TopRight:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio, size);
|
||||
case PaneAnchor::CenterTop:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio, {size[0] / 2, size[1]});
|
||||
case PaneAnchor::CenterBottom:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio, {size[0] / 2, 0});
|
||||
case PaneAnchor::CenterLeft:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio, {0, size[1] / 2});
|
||||
case PaneAnchor::CenterRight:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio, {size[0], size[1] / 2});
|
||||
case PaneAnchor::Center:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio, size / 2);
|
||||
default:
|
||||
scale = Mat3F::scaling(interfaceScaleRatio, Vec2F(windowSize()) / 2);
|
||||
}
|
||||
return Vec2I::round((scale * Vec3F(position, 0)).vec2());
|
||||
}
|
||||
|
||||
bool PaneManager::dismiss(PanePtr const& pane) {
|
||||
bool dismissed = false;
|
||||
for (auto& layerPair : m_displayedPanes) {
|
||||
if (auto panePair = layerPair.second.maybeTake(pane)) {
|
||||
dismissed = true;
|
||||
panePair->first->dismissed();
|
||||
if (panePair->second)
|
||||
panePair->second(pane);
|
||||
}
|
||||
}
|
||||
|
||||
return dismissed;
|
||||
}
|
||||
|
||||
}
|
104
source/windowing/StarPaneManager.hpp
Normal file
104
source/windowing/StarPaneManager.hpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
#ifndef STAR_PANE_MANAGER_HPP
|
||||
#define STAR_PANE_MANAGER_HPP
|
||||
|
||||
#include "StarPane.hpp"
|
||||
#include "StarOrderedMap.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(PaneManager);
|
||||
|
||||
enum class PaneLayer {
|
||||
// A special class of window only meant to be used by PaneManager to display
|
||||
// tooltips given by Pane::createTooltip
|
||||
Tooltip,
|
||||
// A special class of window that is displayed above all other windows and
|
||||
// turns off input to other windows and the hud until it is dismissed.
|
||||
ModalWindow,
|
||||
// Window layer for regular windows that are regularly displayed and
|
||||
// dismissed and dragged around.
|
||||
Window,
|
||||
// The bottom GUI layer, for persistent hud elements that are always or almost
|
||||
// always shown. Not key dismissable.
|
||||
Hud,
|
||||
// Layer for interface elements which are logically part of the world but
|
||||
// handled by GUI panes (such as wires)
|
||||
World
|
||||
};
|
||||
|
||||
// This class handles a set of panes to be drawn as a collective windowing
|
||||
// interface. It is a set of panes on separate distinct layers, where each
|
||||
// layer contains a z-ordered list of panes to display.
|
||||
class PaneManager {
|
||||
public:
|
||||
typedef function<void(PanePtr const&)> DismissCallback;
|
||||
|
||||
PaneManager();
|
||||
|
||||
// Display a pane on any given layer. The pane lifetime in this class is
|
||||
// only during display, once dismissed, the pane is forgotten completely.
|
||||
void displayPane(PaneLayer paneLayer, PanePtr const& pane, DismissCallback onDismiss = {});
|
||||
|
||||
bool isDisplayed(PanePtr const& pane) const;
|
||||
|
||||
// Dismiss a given displayed pane. Pane must already be displayed.
|
||||
void dismissPane(PanePtr const& pane);
|
||||
|
||||
// Dismisses all panes in the given layers.
|
||||
void dismissAllPanes(Set<PaneLayer> const& paneLayers);
|
||||
void dismissAllPanes();
|
||||
|
||||
PanePtr topPane(Set<PaneLayer> const& paneLayers) const;
|
||||
PanePtr topPane() const;
|
||||
|
||||
// Brign an already displayed pane to the top of its layer.
|
||||
void bringToTop(PanePtr const& pane);
|
||||
|
||||
// Position a pane adjacent to an anchor pane in a direction where
|
||||
// it will fit on the screen
|
||||
void bringPaneAdjacent(PanePtr const& anchor, PanePtr const& adjacent, int gap);
|
||||
|
||||
PanePtr getPaneAt(Set<PaneLayer> const& paneLayers, Vec2I const& position) const;
|
||||
PanePtr getPaneAt(Vec2I const& position) const;
|
||||
|
||||
void setBackgroundWidget(WidgetPtr bg);
|
||||
|
||||
// Returns the pane that has captured the keyboard, if any.
|
||||
PanePtr keyboardCapturedPane() const;
|
||||
// Returns true if the current pane that has captured the keyboard is
|
||||
// accepting text input.
|
||||
bool keyboardCapturedForTextInput() const;
|
||||
|
||||
bool sendInputEvent(InputEvent const& event);
|
||||
|
||||
void render();
|
||||
void update();
|
||||
|
||||
private:
|
||||
Vec2I windowSize() const;
|
||||
Vec2I calculatePaneOffset(PanePtr const& pane) const;
|
||||
Vec2I calculateNewInterfacePosition(PanePtr const& pane, float interfaceScaleRatio) const;
|
||||
bool dismiss(PanePtr const& pane);
|
||||
|
||||
GuiContext* m_context;
|
||||
int m_prevInterfaceScale;
|
||||
|
||||
// Map of each pane layer, where the 0th pane is the topmost pane in each layer.
|
||||
Map<PaneLayer, OrderedMap<PanePtr, DismissCallback>> m_displayedPanes;
|
||||
|
||||
WidgetPtr m_backgroundWidget;
|
||||
|
||||
float m_tooltipMouseoverTime;
|
||||
float m_tooltipMouseoverRadius;
|
||||
Vec2I m_tooltipMouseOffset;
|
||||
|
||||
float m_tooltipShowTimer;
|
||||
Vec2I m_tooltipLastMousePos;
|
||||
Vec2I m_tooltipInitialPosition;
|
||||
PanePtr m_activeTooltip;
|
||||
PanePtr m_tooltipParentPane;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
117
source/windowing/StarPortraitWidget.cpp
Normal file
117
source/windowing/StarPortraitWidget.cpp
Normal file
|
@ -0,0 +1,117 @@
|
|||
#include "StarPortraitWidget.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarImageMetadataDatabase.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
PortraitWidget::PortraitWidget(PortraitEntityPtr entity, PortraitMode mode) : m_entity(entity), m_portraitMode(mode) {
|
||||
m_scale = 1;
|
||||
m_iconMode = false;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
PortraitWidget::PortraitWidget() {
|
||||
m_entity = {};
|
||||
m_portraitMode = PortraitMode::Full;
|
||||
m_scale = 1;
|
||||
m_iconMode = false;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
RectI PortraitWidget::getScissorRect() const {
|
||||
return noScissor();
|
||||
}
|
||||
|
||||
void PortraitWidget::renderImpl() {
|
||||
auto imgMetadata = Root::singleton().imageMetadataDatabase();
|
||||
|
||||
Vec2I offset = Vec2I();
|
||||
if (m_iconMode) {
|
||||
auto imgSize = Vec2F(imgMetadata->imageSize(m_iconImage));
|
||||
offset = Vec2I(m_scale * imgSize / 2);
|
||||
offset += m_iconOffset;
|
||||
context()->drawInterfaceQuad(m_iconImage, Vec2F(screenPosition()), m_scale);
|
||||
}
|
||||
if (m_entity) {
|
||||
List<Drawable> portrait = m_entity->portrait(m_portraitMode);
|
||||
for (auto i : portrait) {
|
||||
i.scale(m_scale);
|
||||
context()->drawInterfaceDrawable(i, Vec2F(screenPosition() + offset));
|
||||
}
|
||||
} else {
|
||||
if (m_portraitMode == PortraitMode::Bust || m_portraitMode == PortraitMode::Head) {
|
||||
Vec2I pos = offset;
|
||||
auto imgSize = Vec2F(imgMetadata->imageSize(m_noEntityImagePart));
|
||||
pos -= Vec2I(m_scale * imgSize * 0.5);
|
||||
context()->drawInterfaceQuad(m_noEntityImagePart, Vec2F(screenPosition() + pos), m_scale);
|
||||
} else {
|
||||
Vec2I pos = offset;
|
||||
auto imgSize = Vec2F(imgMetadata->imageSize(m_noEntityImageFull));
|
||||
pos -= Vec2I(m_scale * imgSize * 0.5);
|
||||
context()->drawInterfaceQuad(m_noEntityImageFull, Vec2F(screenPosition() + pos), m_scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PortraitWidget::init() {
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
m_noEntityImageFull = assets->json("/interface.config:portraitNullPlayerImageFull").toString();
|
||||
m_noEntityImagePart = assets->json("/interface.config:portraitNullPlayerImagePart").toString();
|
||||
m_iconImage = assets->json("/interface.config:portraitIconImage").toString();
|
||||
m_iconOffset = jsonToVec2I(assets->json("/interface.config:portraitIconOffset"));
|
||||
|
||||
updateSize();
|
||||
}
|
||||
|
||||
void PortraitWidget::setEntity(PortraitEntityPtr entity) {
|
||||
m_entity = entity;
|
||||
updateSize();
|
||||
}
|
||||
|
||||
void PortraitWidget::setMode(PortraitMode mode) {
|
||||
m_portraitMode = mode;
|
||||
updateSize();
|
||||
}
|
||||
|
||||
void PortraitWidget::setScale(float scale) {
|
||||
m_scale = scale;
|
||||
updateSize();
|
||||
}
|
||||
|
||||
void PortraitWidget::setIconMode() {
|
||||
m_iconMode = true;
|
||||
updateSize();
|
||||
}
|
||||
|
||||
bool PortraitWidget::sendEvent(InputEvent const&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void PortraitWidget::updateSize() {
|
||||
auto imgMetadata = Root::singleton().imageMetadataDatabase();
|
||||
|
||||
if (m_iconMode) {
|
||||
setSize(Vec2I(imgMetadata->imageSize(m_iconImage) * m_scale));
|
||||
} else {
|
||||
if (m_entity) {
|
||||
setSize(Vec2I(
|
||||
(Drawable::boundBoxAll(m_entity->portrait(m_portraitMode), false)
|
||||
.size()
|
||||
* TilePixels
|
||||
* m_scale)
|
||||
.ceil()));
|
||||
} else {
|
||||
if (m_portraitMode == PortraitMode::Bust || m_portraitMode == PortraitMode::Head)
|
||||
setSize(Vec2I(imgMetadata->imageSize(m_iconImage) * m_scale));
|
||||
else
|
||||
setSize(Vec2I(imgMetadata->imageSize(m_noEntityImageFull) * m_scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
44
source/windowing/StarPortraitWidget.hpp
Normal file
44
source/windowing/StarPortraitWidget.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef STAR_PORTRAIT_WIDGET_HPP
|
||||
#define STAR_PORTRAIT_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
#include "StarPlayer.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(Player);
|
||||
STAR_CLASS(PortraitWidget);
|
||||
|
||||
class PortraitWidget : public Widget {
|
||||
public:
|
||||
PortraitWidget(PortraitEntityPtr entity, PortraitMode mode = PortraitMode::Full);
|
||||
PortraitWidget();
|
||||
virtual ~PortraitWidget() {}
|
||||
|
||||
void setEntity(PortraitEntityPtr entity);
|
||||
void setMode(PortraitMode mode);
|
||||
void setScale(float scale);
|
||||
void setIconMode();
|
||||
bool sendEvent(InputEvent const& event);
|
||||
|
||||
protected:
|
||||
virtual RectI getScissorRect() const;
|
||||
virtual void renderImpl();
|
||||
|
||||
private:
|
||||
void init();
|
||||
void updateSize();
|
||||
|
||||
PortraitEntityPtr m_entity;
|
||||
PortraitMode m_portraitMode;
|
||||
String m_noEntityImageFull;
|
||||
String m_noEntityImagePart;
|
||||
float m_scale;
|
||||
|
||||
bool m_iconMode;
|
||||
String m_iconImage;
|
||||
Vec2I m_iconOffset;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
71
source/windowing/StarProgressWidget.cpp
Normal file
71
source/windowing/StarProgressWidget.cpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
#include "StarProgressWidget.hpp"
|
||||
#include "StarLexicalCast.hpp"
|
||||
#include "StarInterpolation.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ProgressWidget::ProgressWidget(String const& background,
|
||||
String const& overlay,
|
||||
ImageStretchSet const& progressSet,
|
||||
GuiDirection direction)
|
||||
: m_background(background),
|
||||
m_overlay(overlay),
|
||||
m_bar(progressSet),
|
||||
m_direction(direction) {
|
||||
|
||||
m_progressLevel = 0;
|
||||
m_maxLevel = 1;
|
||||
|
||||
if (!m_background.empty())
|
||||
setSize(Vec2I(context()->textureSize(m_background)));
|
||||
else if (!m_overlay.empty())
|
||||
setSize(Vec2I(context()->textureSize(m_overlay)));
|
||||
|
||||
m_color = Color::White;
|
||||
}
|
||||
|
||||
void ProgressWidget::renderImpl() {
|
||||
float progress = 1;
|
||||
if (m_maxLevel > 0)
|
||||
progress = m_progressLevel / m_maxLevel;
|
||||
|
||||
auto shift = [&](float begin, float end, RectF templ) {
|
||||
RectF result = templ;
|
||||
|
||||
if (m_direction == GuiDirection::Horizontal) {
|
||||
result.min()[0] = lerp(begin, templ.min()[0], templ.max()[0]);
|
||||
result.max()[0] = lerp(end, templ.min()[0], templ.max()[0]);
|
||||
} else {
|
||||
result.min()[1] = lerp(begin, templ.min()[1], templ.max()[1]);
|
||||
result.max()[1] = lerp(end, templ.min()[1], templ.max()[1]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
if (!m_background.empty())
|
||||
context()->drawInterfaceQuad(m_background, shift(0, 1, RectF(Vec2F(), Vec2F(size()))), shift(0, 1, RectF(screenBoundRect())));
|
||||
|
||||
context()->drawImageStretchSet(m_bar, shift(0, progress, RectF(screenBoundRect())), m_direction, m_color.toRgba());
|
||||
|
||||
if (!m_overlay.empty())
|
||||
context()->drawInterfaceQuad(m_overlay, shift(0, 1, RectF({}, Vec2F(size()))), shift(0, 1, RectF(screenBoundRect())));
|
||||
}
|
||||
|
||||
void ProgressWidget::setCurrentProgressLevel(float amount) {
|
||||
m_progressLevel = amount;
|
||||
}
|
||||
|
||||
void ProgressWidget::setMaxProgressLevel(float amount) {
|
||||
m_maxLevel = amount;
|
||||
}
|
||||
|
||||
void ProgressWidget::setColor(Color const& color) {
|
||||
m_color = color;
|
||||
}
|
||||
|
||||
void ProgressWidget::setOverlay(String const& overlay) {
|
||||
m_overlay = overlay;
|
||||
}
|
||||
|
||||
}
|
42
source/windowing/StarProgressWidget.hpp
Normal file
42
source/windowing/StarProgressWidget.hpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
#ifndef STAR_PROGRESS_WIDGET_HPP
|
||||
#define STAR_PROGRESS_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ProgressWidget);
|
||||
class ProgressWidget : public Widget {
|
||||
public:
|
||||
ProgressWidget(String const& background,
|
||||
String const& overlay,
|
||||
ImageStretchSet const& progressSet,
|
||||
GuiDirection direction);
|
||||
virtual ~ProgressWidget() {}
|
||||
|
||||
void setCurrentProgressLevel(float amount);
|
||||
void setMaxProgressLevel(float amount);
|
||||
|
||||
void setColor(Color const& color);
|
||||
void setOverlay(String const& overlay);
|
||||
|
||||
protected:
|
||||
virtual void renderImpl();
|
||||
RectI shift(float begin, float end, RectI templ);
|
||||
|
||||
float m_progressLevel;
|
||||
float m_maxLevel;
|
||||
|
||||
Color m_color;
|
||||
|
||||
String m_background;
|
||||
String m_overlay;
|
||||
ImageStretchSet m_bar;
|
||||
GuiDirection m_direction;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
126
source/windowing/StarRegisteredPaneManager.hpp
Normal file
126
source/windowing/StarRegisteredPaneManager.hpp
Normal file
|
@ -0,0 +1,126 @@
|
|||
#ifndef STAR_REGISTERED_PANE_MANAGER_HPP
|
||||
#define STAR_REGISTERED_PANE_MANAGER_HPP
|
||||
|
||||
#include "StarPaneManager.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// This class inherits PaneManager to allow for registered panes that are kept
|
||||
// internally by the class even when dismissed. They can be displayed,
|
||||
// dismissed, and toggled between the two without being lost.
|
||||
template <typename KeyT>
|
||||
class RegisteredPaneManager : public PaneManager {
|
||||
public:
|
||||
typedef KeyT Key;
|
||||
|
||||
void registerPane(KeyT paneId, PaneLayer paneLayer, PanePtr pane, DismissCallback onDismiss = {});
|
||||
PanePtr deregisterPane(KeyT const& paneId);
|
||||
void deregisterAllPanes();
|
||||
|
||||
template <typename T = Pane>
|
||||
shared_ptr<T> registeredPane(KeyT const& paneId) const;
|
||||
|
||||
// Displays a registred pane if it is not already displayed. Returns true
|
||||
// if it is newly displayed.
|
||||
bool displayRegisteredPane(KeyT const& paneId);
|
||||
bool registeredPaneIsDisplayed(KeyT const& paneId) const;
|
||||
|
||||
// Dismisses a registred pane if it is displayed. Returns true if it
|
||||
// has been dismissed.
|
||||
bool dismissRegisteredPane(KeyT const& paneId);
|
||||
|
||||
// Returns whether the pane is now displayed.
|
||||
bool toggleRegisteredPane(KeyT const& paneId);
|
||||
|
||||
private:
|
||||
struct PaneInfo {
|
||||
PaneLayer layer;
|
||||
PanePtr pane;
|
||||
DismissCallback dismissCallback;
|
||||
};
|
||||
|
||||
PaneInfo const& getRegisteredPaneInfo(KeyT const& paneId) const;
|
||||
|
||||
// Map of registered panes by name.
|
||||
HashMap<KeyT, PaneInfo> m_registeredPanes;
|
||||
};
|
||||
|
||||
template <typename KeyT>
|
||||
template <typename T>
|
||||
shared_ptr<T> RegisteredPaneManager<KeyT>::registeredPane(KeyT const& paneId) const {
|
||||
if (auto v = m_registeredPanes.ptr(paneId))
|
||||
return convert<T>(v->pane);
|
||||
throw GuiException(strf("No pane named '%s' found in RegisteredPaneManager", outputAny(paneId)));
|
||||
}
|
||||
|
||||
template <typename KeyT>
|
||||
void RegisteredPaneManager<KeyT>::registerPane(
|
||||
KeyT paneId, PaneLayer paneLayer, PanePtr pane, DismissCallback onDismiss) {
|
||||
if (!m_registeredPanes.insert(move(paneId), {move(paneLayer), move(pane), move(onDismiss)}).second)
|
||||
throw GuiException(
|
||||
strf("Registered pane with name '%s' registered a second time in RegisteredPaneManager::registerPane",
|
||||
outputAny(paneId)));
|
||||
}
|
||||
|
||||
template <typename KeyT>
|
||||
PanePtr RegisteredPaneManager<KeyT>::deregisterPane(KeyT const& paneId) {
|
||||
if (auto v = m_registeredPanes.maybeTake(paneId)) {
|
||||
if (isDisplayed(v->pane))
|
||||
dismissPane(v->pane);
|
||||
return v->pane;
|
||||
}
|
||||
throw GuiException(strf("No pane named '%s' found in RegisteredPaneManager::deregisterPane", outputAny(paneId)));
|
||||
}
|
||||
|
||||
template <typename KeyT>
|
||||
void RegisteredPaneManager<KeyT>::deregisterAllPanes() {
|
||||
for (auto const& k : m_registeredPanes.keys())
|
||||
deregisterPane(k);
|
||||
}
|
||||
|
||||
template <typename KeyT>
|
||||
bool RegisteredPaneManager<KeyT>::displayRegisteredPane(KeyT const& paneId) {
|
||||
auto const& paneInfo = getRegisteredPaneInfo(paneId);
|
||||
if (!isDisplayed(paneInfo.pane)) {
|
||||
displayPane(paneInfo.layer, paneInfo.pane, paneInfo.dismissCallback);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename KeyT>
|
||||
bool RegisteredPaneManager<KeyT>::registeredPaneIsDisplayed(KeyT const& paneId) const {
|
||||
return isDisplayed(getRegisteredPaneInfo(paneId).pane);
|
||||
}
|
||||
|
||||
template <typename KeyT>
|
||||
bool RegisteredPaneManager<KeyT>::dismissRegisteredPane(KeyT const& paneId) {
|
||||
auto const& paneInfo = getRegisteredPaneInfo(paneId);
|
||||
if (isDisplayed(paneInfo.pane)) {
|
||||
dismissPane(paneInfo.pane);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename KeyT>
|
||||
bool RegisteredPaneManager<KeyT>::toggleRegisteredPane(KeyT const& paneId) {
|
||||
if (registeredPaneIsDisplayed(paneId)) {
|
||||
dismissRegisteredPane(paneId);
|
||||
return false;
|
||||
} else {
|
||||
displayRegisteredPane(paneId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename KeyT>
|
||||
typename RegisteredPaneManager<KeyT>::PaneInfo const& RegisteredPaneManager<KeyT>::getRegisteredPaneInfo(
|
||||
KeyT const& paneId) const {
|
||||
if (auto p = m_registeredPanes.ptr(paneId))
|
||||
return *p;
|
||||
throw GuiException(strf("No registered pane with name '%s' found in RegisteredPaneManager", outputAny(paneId)));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
445
source/windowing/StarScrollArea.cpp
Normal file
445
source/windowing/StarScrollArea.cpp
Normal file
|
@ -0,0 +1,445 @@
|
|||
#include "StarScrollArea.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// TODO: I hate these hardcoded values. Please smite with fire.
|
||||
static int const ScrollAreaBorder = 9;
|
||||
static int const ScrollButtonStackSize = 6;
|
||||
static int const ScrollThumbSize = 3;
|
||||
static int const ScrollThumbOverhead = ScrollThumbSize + ScrollThumbSize;
|
||||
static int const ScrollBarTrackOverhead = ScrollButtonStackSize + ScrollButtonStackSize + ScrollThumbOverhead;
|
||||
static int64_t const ScrollAdvanceTimer = 100;
|
||||
|
||||
ScrollThumb::ScrollThumb(GuiDirection direction) {
|
||||
m_hovered = false;
|
||||
m_pressed = false;
|
||||
m_direction = direction;
|
||||
auto assets = Root::singleton().assets();
|
||||
setImages(assets->json("/interface.config:scrollArea.thumbs"));
|
||||
}
|
||||
|
||||
void ScrollThumb::setImages(ImageStretchSet const& base, ImageStretchSet const& hover, ImageStretchSet const& pressed) {
|
||||
m_baseThumb = base;
|
||||
m_hoverThumb = hover;
|
||||
m_pressedThumb = pressed;
|
||||
}
|
||||
|
||||
void ScrollThumb::setImages(Json const& images) {
|
||||
String directionString;
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
directionString = "vertical";
|
||||
} else {
|
||||
directionString = "horizontal";
|
||||
}
|
||||
|
||||
m_baseThumb.begin = images.get(directionString).get("base").getString("begin");
|
||||
m_baseThumb.end = images.get(directionString).get("base").getString("end");
|
||||
m_baseThumb.inner = images.get(directionString).get("base").getString("inner");
|
||||
|
||||
m_hoverThumb.begin = images.get(directionString).get("hover").getString("begin");
|
||||
m_hoverThumb.end = images.get(directionString).get("hover").getString("end");
|
||||
m_hoverThumb.inner = images.get(directionString).get("hover").getString("inner");
|
||||
|
||||
m_pressedThumb.begin = images.get(directionString).get("pressed").getString("begin");
|
||||
m_pressedThumb.end = images.get(directionString).get("pressed").getString("end");
|
||||
m_pressedThumb.inner = images.get(directionString).get("pressed").getString("inner");
|
||||
}
|
||||
|
||||
bool ScrollThumb::isHovered() const {
|
||||
return m_hovered;
|
||||
}
|
||||
|
||||
bool ScrollThumb::isPressed() const {
|
||||
return m_pressed;
|
||||
}
|
||||
|
||||
void ScrollThumb::setHovered(bool hovered) {
|
||||
m_hovered = hovered;
|
||||
}
|
||||
|
||||
void ScrollThumb::setPressed(bool pressed) {
|
||||
m_pressed = pressed;
|
||||
}
|
||||
|
||||
void ScrollThumb::mouseOver() {
|
||||
setHovered(true);
|
||||
}
|
||||
|
||||
void ScrollThumb::mouseOut() {
|
||||
setHovered(false);
|
||||
}
|
||||
|
||||
void ScrollThumb::renderImpl() {
|
||||
ImageStretchSet workingSet = m_baseThumb;
|
||||
if (isHovered())
|
||||
workingSet = m_hoverThumb;
|
||||
if (isPressed())
|
||||
workingSet = m_pressedThumb;
|
||||
|
||||
if (workingSet.fullyPopulated()) {
|
||||
context()->drawImageStretchSet(workingSet,
|
||||
RectF::withSize(Vec2F(m_parent->screenPosition() + position()), Vec2F(size())),
|
||||
m_direction);
|
||||
}
|
||||
}
|
||||
|
||||
Vec2U ScrollThumb::baseSize() const {
|
||||
return context()->textureSize(m_baseThumb.begin);
|
||||
}
|
||||
|
||||
ScrollBar::ScrollBar(GuiDirection direction, WidgetCallbackFunc forwardFunc, WidgetCallbackFunc backwardFunc)
|
||||
: m_direction(direction) {
|
||||
m_forward = make_shared<ButtonWidget>();
|
||||
m_forward->setCallback(forwardFunc);
|
||||
m_forward->setSustainCallbackOnDownHold(true);
|
||||
m_forward->setPressedOffset({0, 0});
|
||||
|
||||
m_backward = make_shared<ButtonWidget>();
|
||||
m_backward->setCallback(backwardFunc);
|
||||
m_backward->setSustainCallbackOnDownHold(true);
|
||||
m_backward->setPressedOffset({0, 0});
|
||||
|
||||
m_thumb = make_shared<ScrollThumb>(m_direction);
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
setButtonImages(assets->json("/interface.config:scrollArea.buttons"));
|
||||
|
||||
addChild("thumb", m_thumb);
|
||||
addChild("forward", m_forward);
|
||||
addChild("backward", m_backward);
|
||||
}
|
||||
|
||||
void ScrollBar::setButtonImages(Json const& images) {
|
||||
String directionString;
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
directionString = "vertical";
|
||||
} else {
|
||||
directionString = "horizontal";
|
||||
}
|
||||
|
||||
m_forward->setImages(images.get(directionString).get("forward").getString("base"),
|
||||
images.get(directionString).get("forward").getString("hover"),
|
||||
images.get(directionString).get("forward").getString("pressed"));
|
||||
|
||||
m_backward->setImages(images.get(directionString).get("backward").getString("base"),
|
||||
images.get(directionString).get("backward").getString("hover"),
|
||||
images.get(directionString).get("backward").getString("pressed"));
|
||||
}
|
||||
|
||||
Vec2I ScrollBar::size() const {
|
||||
if (m_parent)
|
||||
return m_parent->size();
|
||||
return {};
|
||||
}
|
||||
|
||||
int ScrollBar::trackSize() const {
|
||||
if (m_parent) {
|
||||
auto scrollArea = convert<ScrollArea>(m_parent);
|
||||
auto size = scrollArea->size();
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
if (scrollArea->horizontalScroll()) {
|
||||
return size[1] - (ScrollBarTrackOverhead + ScrollAreaBorder);
|
||||
} else {
|
||||
return size[1] - ScrollBarTrackOverhead;
|
||||
}
|
||||
} else {
|
||||
return size[0] - ScrollBarTrackOverhead;
|
||||
}
|
||||
}
|
||||
|
||||
throw GuiException("Somehow have a Scroll Bar without a parent.");
|
||||
}
|
||||
|
||||
float ScrollBar::sizeRatio() const {
|
||||
if (m_parent) {
|
||||
auto scrollArea = convert<ScrollArea>(m_parent);
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
return scrollArea->contentSize()[1] / (float)(scrollArea->areaSize()[1]);
|
||||
} else {
|
||||
return scrollArea->contentSize()[0] / (float)(scrollArea->areaSize()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
throw GuiException("Somehow have a Scroll Bar without a parent.");
|
||||
}
|
||||
|
||||
float ScrollBar::scrollRatio() const {
|
||||
if (m_parent) {
|
||||
auto scrollArea = convert<ScrollArea>(m_parent);
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
if (scrollArea->maxScrollPosition()[1] == 0)
|
||||
return 0;
|
||||
return scrollArea->scrollOffset()[1] / (float)(scrollArea->maxScrollPosition()[1]);
|
||||
} else {
|
||||
if (scrollArea->maxScrollPosition()[0] == 0)
|
||||
return 0;
|
||||
return scrollArea->scrollOffset()[0] / (float)(scrollArea->maxScrollPosition()[0]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ButtonWidgetPtr ScrollBar::forwardButton() const {
|
||||
return m_forward;
|
||||
}
|
||||
|
||||
ButtonWidgetPtr ScrollBar::backwardButton() const {
|
||||
return m_backward;
|
||||
}
|
||||
|
||||
ScrollThumbPtr ScrollBar::thumb() const {
|
||||
return m_thumb;
|
||||
}
|
||||
|
||||
void ScrollBar::drawChildren() {
|
||||
if (m_parent) {
|
||||
auto scrollArea = convert<ScrollArea>(m_parent);
|
||||
|
||||
float ratio = sizeRatio();
|
||||
if (ratio < 1)
|
||||
ratio = 1;
|
||||
|
||||
int innerSize = (int)max(0.0f, ceil(trackSize() / ratio));
|
||||
int offsetBegin = (int)ceil((trackSize() - innerSize) * scrollRatio());
|
||||
innerSize += ScrollThumbOverhead;
|
||||
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
if (scrollArea->horizontalScroll()) {
|
||||
m_forward->setPosition(m_parent->size() - Vec2I(ScrollAreaBorder, ScrollButtonStackSize));
|
||||
m_backward->setPosition({m_parent->size()[0] - ScrollAreaBorder, ScrollAreaBorder});
|
||||
m_thumb->setPosition({m_parent->size()[0] - ScrollAreaBorder, ScrollAreaBorder + ScrollButtonStackSize + offsetBegin});
|
||||
} else {
|
||||
m_forward->setPosition(m_parent->size() - Vec2I(ScrollAreaBorder, ScrollButtonStackSize));
|
||||
m_backward->setPosition({m_parent->size()[0] - ScrollAreaBorder, 0});
|
||||
m_thumb->setPosition({m_parent->size()[0] - ScrollAreaBorder, ScrollButtonStackSize + offsetBegin});
|
||||
}
|
||||
m_thumb->setSize(Vec2I(m_thumb->baseSize()[0], innerSize));
|
||||
} else {
|
||||
m_forward->setPosition({m_parent->size()[0] - ScrollButtonStackSize, 0});
|
||||
m_backward->setPosition({0, 0});
|
||||
m_thumb->setPosition({ScrollButtonStackSize + offsetBegin, 0});
|
||||
m_thumb->setSize(Vec2I(innerSize, m_thumb->baseSize()[1]));
|
||||
}
|
||||
|
||||
for (auto child : m_members) {
|
||||
child->render(m_drawingArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vec2I ScrollBar::offsetFromThumbPosition(Vec2I const& thumbPosition) const {
|
||||
auto scrollArea = convert<ScrollArea>(m_parent);
|
||||
if (m_direction == GuiDirection::Vertical) {
|
||||
int scrollSpan = trackSize() - m_thumb->size()[1];
|
||||
int scrollOffset = clamp((thumbPosition[1] - ScrollButtonStackSize), 0, scrollSpan);
|
||||
float scrollRatio = (float)scrollOffset / (float)scrollSpan;
|
||||
return {scrollArea->scrollOffset()[0], ceil(scrollArea->maxScrollPosition()[1] * scrollRatio)};
|
||||
} else {
|
||||
int scrollSpan = trackSize() - m_thumb->size()[0];
|
||||
int scrollOffset = clamp((thumbPosition[0] - ScrollButtonStackSize), 0, scrollSpan);
|
||||
float scrollRatio = (float)scrollOffset / (float)scrollSpan;
|
||||
return {(int)ceil(scrollArea->maxScrollPosition()[0] * scrollRatio), scrollArea->scrollOffset()[1]};
|
||||
}
|
||||
}
|
||||
|
||||
ScrollArea::ScrollArea() {
|
||||
auto assets = Root::singleton().assets();
|
||||
m_buttonAdvance = assets->json("/interface.config:scrollArea.buttonAdvance").toInt();
|
||||
m_advanceLimiter = Time::monotonicMilliseconds();
|
||||
|
||||
WidgetCallbackFunc vAdvance = [this](Widget*) { scrollAreaBy({0, advanceFactorHelper()}); };
|
||||
WidgetCallbackFunc vRetreat = [this](Widget*) { scrollAreaBy({0, -advanceFactorHelper()}); };
|
||||
WidgetCallbackFunc hAdvance = [this](Widget*) { scrollAreaBy({advanceFactorHelper(), 0}); };
|
||||
WidgetCallbackFunc hRetreat = [this](Widget*) { scrollAreaBy({-advanceFactorHelper(), 0}); };
|
||||
|
||||
m_vBar = make_shared<ScrollBar>(GuiDirection::Vertical, vAdvance, vRetreat);
|
||||
m_hBar = make_shared<ScrollBar>(GuiDirection::Horizontal, hAdvance, hRetreat);
|
||||
|
||||
m_dragActive = false;
|
||||
|
||||
addChild("vScrollBar", m_vBar);
|
||||
addChild("hScrollBar", m_hBar);
|
||||
|
||||
m_horizontalScroll = false;
|
||||
m_verticalScroll = true;
|
||||
}
|
||||
|
||||
void ScrollArea::setButtonImages(Json const& images) {
|
||||
m_vBar->setButtonImages(images);
|
||||
m_hBar->setButtonImages(images);
|
||||
}
|
||||
|
||||
void ScrollArea::setThumbImages(Json const& images) {
|
||||
m_vBar->thumb()->setImages(images);
|
||||
m_hBar->thumb()->setImages(images);
|
||||
}
|
||||
|
||||
RectI ScrollArea::contentBoundRect() const {
|
||||
RectI res = RectI::null();
|
||||
for (auto child : m_members) {
|
||||
if (child == m_vBar || child == m_hBar) // scroll bars don't count
|
||||
continue;
|
||||
if (!child->active()) // neither do hidden members
|
||||
continue;
|
||||
res.combine(child->relativeBoundRect());
|
||||
}
|
||||
res.setMax({res.max()[0], res.max()[1] + 1});
|
||||
return res;
|
||||
}
|
||||
|
||||
Vec2I ScrollArea::contentSize() const {
|
||||
return contentBoundRect().size();
|
||||
}
|
||||
|
||||
Vec2I ScrollArea::areaSize() const {
|
||||
Vec2I s = size();
|
||||
if (horizontalScroll())
|
||||
s[1] -= ScrollAreaBorder;
|
||||
if (verticalScroll())
|
||||
s[0] -= ScrollAreaBorder;
|
||||
return s;
|
||||
}
|
||||
|
||||
void ScrollArea::scrollAreaBy(Vec2I const& offset) {
|
||||
m_scrollOffset += offset;
|
||||
}
|
||||
|
||||
Vec2I ScrollArea::scrollOffset() const {
|
||||
return m_scrollOffset;
|
||||
}
|
||||
|
||||
bool ScrollArea::horizontalScroll() const {
|
||||
return m_horizontalScroll;
|
||||
}
|
||||
|
||||
void ScrollArea::setHorizontalScroll(bool horizontal) {
|
||||
m_horizontalScroll = horizontal;
|
||||
}
|
||||
|
||||
bool ScrollArea::verticalScroll() const {
|
||||
return m_verticalScroll;
|
||||
}
|
||||
|
||||
void ScrollArea::setVerticalScroll(bool vertical) {
|
||||
m_verticalScroll = vertical;
|
||||
}
|
||||
|
||||
bool ScrollArea::sendEvent(InputEvent const& event) {
|
||||
if (!m_visible)
|
||||
return false;
|
||||
|
||||
if (m_dragActive) {
|
||||
if (event.is<MouseButtonUpEvent>()) {
|
||||
blur();
|
||||
m_dragActive = false;
|
||||
m_vBar->thumb()->setPressed(false);
|
||||
m_hBar->thumb()->setPressed(false);
|
||||
return true;
|
||||
} else if (event.is<MouseMoveEvent>()) {
|
||||
auto thumbDragPosition = *context()->mousePosition(event) - screenPosition() - m_dragOffset;
|
||||
if (m_dragDirection == GuiDirection::Vertical) {
|
||||
m_scrollOffset = m_vBar->offsetFromThumbPosition(thumbDragPosition);
|
||||
} else {
|
||||
m_scrollOffset = m_hBar->offsetFromThumbPosition(thumbDragPosition);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto mousePos = context()->mousePosition(event);
|
||||
if (mousePos && !inMember(*mousePos))
|
||||
return false;
|
||||
|
||||
if (event.is<MouseButtonDownEvent>()) {
|
||||
if (m_vBar->thumb()->inMember(*mousePos)) {
|
||||
focus();
|
||||
m_dragOffset = *mousePos - screenPosition() - m_vBar->thumb()->position();
|
||||
m_dragDirection = GuiDirection::Vertical;
|
||||
m_dragActive = true;
|
||||
m_vBar->thumb()->setPressed(true);
|
||||
return true;
|
||||
} else if (m_hBar->thumb()->inMember(*mousePos)) {
|
||||
focus();
|
||||
m_dragOffset = *mousePos - screenPosition() - m_hBar->thumb()->position();
|
||||
m_dragDirection = GuiDirection::Horizontal;
|
||||
m_dragActive = true;
|
||||
m_hBar->thumb()->setPressed(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Widget::sendEvent(event))
|
||||
return true;
|
||||
|
||||
if (auto mouseWheel = event.ptr<MouseWheelEvent>()) {
|
||||
if (mouseWheel->mouseWheel == MouseWheel::Up)
|
||||
scrollAreaBy({0, m_buttonAdvance * 3});
|
||||
else
|
||||
scrollAreaBy({0, -m_buttonAdvance * 3});
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScrollArea::update() {
|
||||
if (!m_visible)
|
||||
return;
|
||||
|
||||
auto maxScroll = maxScrollPosition();
|
||||
|
||||
// keep vertical scroll bars same distance from the *top* on resize
|
||||
if (m_verticalScroll && maxScroll != m_lastMaxScroll)
|
||||
m_scrollOffset += (maxScroll - m_lastMaxScroll);
|
||||
|
||||
m_scrollOffset = m_scrollOffset.piecewiseClamp(Vec2I(), maxScroll);
|
||||
m_lastMaxScroll = maxScroll;
|
||||
}
|
||||
|
||||
Vec2I ScrollArea::maxScrollPosition() const {
|
||||
return Vec2I().piecewiseMax(contentSize() - size());
|
||||
}
|
||||
|
||||
void ScrollArea::drawChildren() {
|
||||
RectI innerDrawingArea = m_drawingArea;
|
||||
|
||||
if (horizontalScroll())
|
||||
innerDrawingArea.setYMin(ScrollAreaBorder);
|
||||
if (verticalScroll())
|
||||
innerDrawingArea.setXMax(innerDrawingArea.xMax() - ScrollAreaBorder);
|
||||
|
||||
auto contentBoundRect = ScrollArea::contentBoundRect();
|
||||
auto contentOffset = contentBoundRect.min();
|
||||
auto contentSize = contentBoundRect.size();
|
||||
|
||||
auto offset = contentOffset + scrollOffset();
|
||||
|
||||
auto areaSize = ScrollArea::areaSize();
|
||||
|
||||
if (contentSize[1] < areaSize[1])
|
||||
offset[1] = offset[1] - (areaSize[1] - contentSize[1]);
|
||||
|
||||
for (auto child : m_members) {
|
||||
if (child == m_vBar || child == m_hBar)
|
||||
continue;
|
||||
child->setDrawingOffset(-offset);
|
||||
child->render(innerDrawingArea);
|
||||
}
|
||||
|
||||
if (m_horizontalScroll)
|
||||
m_hBar->render(m_drawingArea);
|
||||
if (m_verticalScroll)
|
||||
m_vBar->render(m_drawingArea);
|
||||
}
|
||||
|
||||
int ScrollArea::advanceFactorHelper() {
|
||||
auto t = Time::monotonicMilliseconds() - m_advanceLimiter;
|
||||
m_advanceLimiter = Time::monotonicMilliseconds();
|
||||
if ((t > ScrollAdvanceTimer) || (t < 0))
|
||||
t = ScrollAdvanceTimer;
|
||||
return (int)std::ceil((m_buttonAdvance * t) / (float)ScrollAdvanceTimer);
|
||||
}
|
||||
|
||||
}
|
132
source/windowing/StarScrollArea.hpp
Normal file
132
source/windowing/StarScrollArea.hpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
#ifndef _STAR_SCROLL_AREA_HPP_
|
||||
#define _STAR_SCROLL_AREA_HPP_
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
#include "StarButtonWidget.hpp"
|
||||
#include "StarImageWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
class ScrollThumb : public Widget {
|
||||
public:
|
||||
ScrollThumb(GuiDirection direction);
|
||||
virtual ~ScrollThumb() {}
|
||||
|
||||
void setDirection(GuiDirection direction);
|
||||
void setImages(ImageStretchSet const& base,
|
||||
ImageStretchSet const& hover = ImageStretchSet(),
|
||||
ImageStretchSet const& pressed = ImageStretchSet());
|
||||
void setImages(Json const& images);
|
||||
|
||||
bool isHovered() const;
|
||||
bool isPressed() const;
|
||||
|
||||
void setHovered(bool hovered);
|
||||
void setPressed(bool pressed);
|
||||
|
||||
void mouseOver() override;
|
||||
void mouseOut() override;
|
||||
|
||||
Vec2U baseSize() const;
|
||||
|
||||
protected:
|
||||
virtual void renderImpl() override;
|
||||
|
||||
private:
|
||||
void readDefaults();
|
||||
|
||||
GuiDirection m_direction;
|
||||
|
||||
ImageStretchSet m_baseThumb;
|
||||
ImageStretchSet m_hoverThumb;
|
||||
ImageStretchSet m_pressedThumb;
|
||||
|
||||
bool m_hovered;
|
||||
bool m_pressed;
|
||||
};
|
||||
typedef shared_ptr<ScrollThumb> ScrollThumbPtr;
|
||||
|
||||
class ScrollBar : public Widget {
|
||||
public:
|
||||
ScrollBar(GuiDirection direction, WidgetCallbackFunc forwardFunc, WidgetCallbackFunc backwardFunc);
|
||||
|
||||
void setButtonImages(Json const& images);
|
||||
|
||||
int trackSize() const;
|
||||
float sizeRatio() const;
|
||||
virtual Vec2I size() const override;
|
||||
float scrollRatio() const;
|
||||
Vec2I offsetFromThumbPosition(Vec2I const& thumbPosition) const;
|
||||
|
||||
ButtonWidgetPtr forwardButton() const;
|
||||
ButtonWidgetPtr backwardButton() const;
|
||||
ScrollThumbPtr thumb() const;
|
||||
|
||||
protected:
|
||||
void drawChildren() override;
|
||||
|
||||
private:
|
||||
GuiDirection m_direction;
|
||||
|
||||
ButtonWidgetPtr m_forward; // up or right, makes the offset higher
|
||||
ButtonWidgetPtr m_backward; // down or left, makes the offset lower
|
||||
ScrollThumbPtr m_thumb;
|
||||
|
||||
ImageStretchSet m_track;
|
||||
};
|
||||
typedef shared_ptr<ScrollBar> ScrollBarPtr;
|
||||
|
||||
class ScrollArea : public Widget {
|
||||
public:
|
||||
ScrollArea();
|
||||
|
||||
void setButtonImages(Json const& images);
|
||||
void setThumbImages(Json const& images);
|
||||
|
||||
RectI contentBoundRect() const;
|
||||
Vec2I contentSize() const;
|
||||
|
||||
Vec2I areaSize() const;
|
||||
|
||||
void scrollAreaBy(Vec2I const& offset);
|
||||
|
||||
Vec2I scrollOffset() const;
|
||||
Vec2I maxScrollPosition() const;
|
||||
|
||||
bool horizontalScroll() const;
|
||||
void setHorizontalScroll(bool horizontal);
|
||||
|
||||
bool verticalScroll() const;
|
||||
void setVerticalScroll(bool vertical);
|
||||
|
||||
virtual bool sendEvent(InputEvent const& event) override;
|
||||
virtual void update() override;
|
||||
|
||||
protected:
|
||||
void drawChildren() override;
|
||||
|
||||
private:
|
||||
int advanceFactorHelper();
|
||||
|
||||
int m_buttonAdvance;
|
||||
int64_t m_advanceLimiter;
|
||||
|
||||
Vec2I m_scrollOffset;
|
||||
Vec2I m_lastMaxScroll;
|
||||
Vec2I m_contentSize;
|
||||
|
||||
bool m_dragActive;
|
||||
GuiDirection m_dragDirection;
|
||||
Vec2I m_dragOffset;
|
||||
|
||||
ScrollBarPtr m_vBar;
|
||||
ScrollBarPtr m_hBar;
|
||||
ImageWidgetPtr m_cornerBlock;
|
||||
|
||||
bool m_horizontalScroll;
|
||||
bool m_verticalScroll;
|
||||
};
|
||||
typedef shared_ptr<ScrollArea> ScrollAreaPtr;
|
||||
}
|
||||
|
||||
#endif
|
183
source/windowing/StarSliderBar.cpp
Normal file
183
source/windowing/StarSliderBar.cpp
Normal file
|
@ -0,0 +1,183 @@
|
|||
#include "StarSliderBar.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarGuiReader.hpp"
|
||||
#include "StarImageMetadataDatabase.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
SliderBarWidget::SliderBarWidget(String const& grid, bool showSpinner)
|
||||
: m_grid(make_shared<ImageWidget>(grid)),
|
||||
m_low(0),
|
||||
m_high(1),
|
||||
m_delta(1),
|
||||
m_val(0),
|
||||
m_updateJog(true),
|
||||
m_jogDragActive(false),
|
||||
m_enabled(true) {
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
auto imgMetadata = Root::singleton().imageMetadataDatabase();
|
||||
|
||||
m_jog = make_shared<ButtonWidget>();
|
||||
m_jog->setImages(assets->json("/interface.config:slider.jog").toString());
|
||||
m_jog->setPressedOffset({0, 0});
|
||||
|
||||
if (showSpinner) {
|
||||
GuiReader guiReader;
|
||||
guiReader.registerCallback("spinner.down", bind(&SliderBarWidget::leftCallback, this));
|
||||
guiReader.registerCallback("spinner.up", bind(&SliderBarWidget::rightCallback, this));
|
||||
|
||||
float rightButtonOffset = imgMetadata->imageSize(assets->json("/interface.config:slider.leftBase").toString())[0];
|
||||
rightButtonOffset += assets->json("/interface.config:slider.defaultPadding").toInt();
|
||||
|
||||
float gridOffset = rightButtonOffset;
|
||||
|
||||
rightButtonOffset += imgMetadata->imageSize(grid)[0];
|
||||
rightButtonOffset += assets->json("/interface.config:slider.defaultPadding").toInt();
|
||||
|
||||
// TODO: Make spinners a thing already
|
||||
Json config = Json::parse(strf(
|
||||
R"SPINNER(
|
||||
{
|
||||
"spinner" : {
|
||||
"type" : "spinner",
|
||||
"leftBase" : "%s",
|
||||
"leftHover" : "%s",
|
||||
"rightBase" : "%s",
|
||||
"rightHover" : "%s",
|
||||
"position" : [0, 0],
|
||||
"upOffset" : %s
|
||||
}
|
||||
}
|
||||
)SPINNER",
|
||||
assets->json("/interface.config:slider.leftBase").toString(),
|
||||
assets->json("/interface.config:slider.leftHover").toString(),
|
||||
assets->json("/interface.config:slider.rightBase").toString(),
|
||||
assets->json("/interface.config:slider.rightHover").toString(),
|
||||
rightButtonOffset));
|
||||
|
||||
guiReader.construct(config, this);
|
||||
|
||||
m_grid->setPosition(Vec2I(gridOffset, 0));
|
||||
|
||||
m_leftButton = fetchChild<ButtonWidget>("spinner.down");
|
||||
m_rightButton = fetchChild<ButtonWidget>("spinner.up");
|
||||
}
|
||||
|
||||
addChild("grid", m_grid);
|
||||
addChild("jog", m_jog);
|
||||
|
||||
markAsContainer();
|
||||
disableScissoring();
|
||||
}
|
||||
|
||||
void SliderBarWidget::setJogImages(String const& baseImage, String const& hoverImage, String const& pressedImage, String const& disabledImage) {
|
||||
m_jog->setImages(baseImage, hoverImage, pressedImage, disabledImage);
|
||||
}
|
||||
|
||||
void SliderBarWidget::setRange(int low, int high, int delta) {
|
||||
m_low = low;
|
||||
m_high = high;
|
||||
m_high = std::max(m_low + 1, m_high);
|
||||
if (delta <= 0)
|
||||
m_delta = 1;
|
||||
else
|
||||
m_delta = delta;
|
||||
setVal(m_val);
|
||||
}
|
||||
|
||||
void SliderBarWidget::setRange(Vec2I const& range, int delta) {
|
||||
setRange(range[0], range[1], delta);
|
||||
}
|
||||
|
||||
void SliderBarWidget::setVal(int val, bool callbackIfChanged) {
|
||||
bool doCallback = false;
|
||||
if (m_callback && callbackIfChanged && m_val != val)
|
||||
doCallback = true;
|
||||
|
||||
if (m_val != val)
|
||||
m_updateJog = true;
|
||||
|
||||
m_val = val;
|
||||
m_val = clamp(m_val, m_low, m_high);
|
||||
|
||||
if (doCallback)
|
||||
m_callback(this);
|
||||
}
|
||||
|
||||
int SliderBarWidget::val() const {
|
||||
return m_val;
|
||||
}
|
||||
|
||||
void SliderBarWidget::setEnabled(bool enabled) {
|
||||
if (m_enabled != enabled) {
|
||||
m_enabled = enabled;
|
||||
m_jogDragActive = false;
|
||||
m_jog->setEnabled(enabled);
|
||||
if (m_rightButton)
|
||||
m_rightButton->setEnabled(enabled);
|
||||
if (m_leftButton)
|
||||
m_leftButton->setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void SliderBarWidget::setCallback(WidgetCallbackFunc callback) {
|
||||
m_callback = callback;
|
||||
}
|
||||
|
||||
void SliderBarWidget::update() {
|
||||
float gridLow = m_grid->position()[0];
|
||||
float gridHigh = gridLow + m_grid->size()[0];
|
||||
|
||||
if (m_jogDragActive) {
|
||||
auto clampedPos = clamp<float>(m_jogDragPos[0], gridLow, gridHigh);
|
||||
float clampedPercent = (clampedPos - gridLow) / (gridHigh - gridLow);
|
||||
setVal(clampedPercent * (m_high - m_low));
|
||||
}
|
||||
|
||||
if (m_updateJog) {
|
||||
float percent = (float)m_val / (m_high - m_low);
|
||||
|
||||
float jogPos = (gridHigh - gridLow) * percent + gridLow - m_jog->size()[0] * 0.5;
|
||||
|
||||
m_jog->setPosition(Vec2I(jogPos, 0));
|
||||
m_updateJog = false;
|
||||
}
|
||||
|
||||
Widget::update();
|
||||
}
|
||||
|
||||
bool SliderBarWidget::sendEvent(InputEvent const& event) {
|
||||
if (event.is<MouseButtonUpEvent>()) {
|
||||
blur();
|
||||
m_jogDragActive = false;
|
||||
}
|
||||
|
||||
if (m_enabled) {
|
||||
if (event.is<MouseButtonDownEvent>()) {
|
||||
if (m_jog->inMember(*context()->mousePosition(event))) {
|
||||
focus();
|
||||
m_jogDragPos = *context()->mousePosition(event) - screenPosition();
|
||||
m_jogDragActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.is<MouseMoveEvent>()) {
|
||||
if (m_jogDragActive)
|
||||
m_jogDragPos = *context()->mousePosition(event) - screenPosition();
|
||||
}
|
||||
}
|
||||
|
||||
return Widget::sendEvent(event);
|
||||
}
|
||||
|
||||
void SliderBarWidget::leftCallback() {
|
||||
setVal(std::max(m_low, m_val - m_delta));
|
||||
}
|
||||
|
||||
void SliderBarWidget::rightCallback() {
|
||||
setVal(std::min(m_high, m_val + m_delta));
|
||||
}
|
||||
|
||||
}
|
57
source/windowing/StarSliderBar.hpp
Normal file
57
source/windowing/StarSliderBar.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#ifndef STAR_SLIDER_BAR_HPP
|
||||
#define STAR_SLIDER_BAR_HPP
|
||||
#include "StarWidget.hpp"
|
||||
#include "StarButtonWidget.hpp"
|
||||
#include "StarImageWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// This class only does left right right now. If you need advanced
|
||||
// multiorientation physics please
|
||||
// implement it in.
|
||||
class SliderBarWidget : public Widget {
|
||||
public:
|
||||
SliderBarWidget(String const& grid, bool showSpinner = true);
|
||||
|
||||
void setJogImages(String const& baseImage, String const& hoverImage = "", String const& pressedImage = "", String const& disabledImage = "");
|
||||
|
||||
void setRange(int low, int high, int delta);
|
||||
void setRange(Vec2I const& range, int delta);
|
||||
void setVal(int val, bool callbackIfChanged = true);
|
||||
int val() const;
|
||||
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
void setCallback(WidgetCallbackFunc callback);
|
||||
|
||||
virtual void update() override;
|
||||
|
||||
virtual bool sendEvent(InputEvent const& event) override;
|
||||
|
||||
private:
|
||||
void leftCallback();
|
||||
void rightCallback();
|
||||
|
||||
ButtonWidgetPtr m_leftButton;
|
||||
ButtonWidgetPtr m_rightButton;
|
||||
ImageWidgetPtr m_grid;
|
||||
ButtonWidgetPtr m_jog;
|
||||
int m_low;
|
||||
int m_high;
|
||||
int m_delta;
|
||||
int m_val;
|
||||
|
||||
bool m_updateJog;
|
||||
|
||||
Vec2I m_savedJogPos;
|
||||
Vec2I m_jogDragPos;
|
||||
bool m_jogDragActive;
|
||||
|
||||
bool m_enabled;
|
||||
|
||||
WidgetCallbackFunc m_callback;
|
||||
};
|
||||
typedef shared_ptr<SliderBarWidget> SliderBarWidgetPtr;
|
||||
}
|
||||
|
||||
#endif
|
35
source/windowing/StarStackWidget.cpp
Normal file
35
source/windowing/StarStackWidget.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include "StarStackWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
void StackWidget::showPage(size_t page) {
|
||||
if (m_shownPage)
|
||||
m_shownPage->hide();
|
||||
m_shownPage = m_members[page];
|
||||
m_page = makeLeft(page);
|
||||
if (m_shownPage)
|
||||
m_shownPage->show();
|
||||
}
|
||||
|
||||
void StackWidget::showPage(String const& name) {
|
||||
if (m_shownPage)
|
||||
m_shownPage->hide();
|
||||
m_shownPage = m_memberHash.get(name);
|
||||
m_page = makeRight(name);
|
||||
if (m_shownPage)
|
||||
m_shownPage->show();
|
||||
}
|
||||
|
||||
Either<size_t, String> StackWidget::currentPage() const {
|
||||
return m_page;
|
||||
}
|
||||
|
||||
void StackWidget::addChild(String const& name, WidgetPtr member) {
|
||||
Widget::addChild(name, member);
|
||||
if (m_members.size() != 1)
|
||||
member->hide();
|
||||
else
|
||||
showPage(0);
|
||||
}
|
||||
|
||||
}
|
25
source/windowing/StarStackWidget.hpp
Normal file
25
source/windowing/StarStackWidget.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#ifndef STAR_STACK_WIDGET_HPP
|
||||
#define STAR_STACK_WIDGET_HPP
|
||||
#include "StarWidget.hpp"
|
||||
#include "StarEither.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(StackWidget);
|
||||
class StackWidget : public Widget {
|
||||
public:
|
||||
void showPage(size_t page);
|
||||
void showPage(String const& name);
|
||||
|
||||
Either<size_t, String> currentPage() const;
|
||||
|
||||
virtual void addChild(String const& name, WidgetPtr member) override;
|
||||
|
||||
private:
|
||||
WidgetPtr m_shownPage;
|
||||
Either<size_t, String> m_page;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
92
source/windowing/StarTabSet.cpp
Normal file
92
source/windowing/StarTabSet.cpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
#include "StarTabSet.hpp"
|
||||
#include "StarButtonWidget.hpp"
|
||||
#include "StarStackWidget.hpp"
|
||||
#include "StarFlowLayout.hpp"
|
||||
#include "StarGuiReader.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarLexicalCast.hpp"
|
||||
#include "StarImageMetadataDatabase.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
TabSetWidget::TabSetWidget(TabSetConfig const& tabSetConfig) {
|
||||
m_tabSetConfig = tabSetConfig;
|
||||
|
||||
m_tabBar = make_shared<FlowLayout>();
|
||||
m_tabBar->setSpacing(m_tabSetConfig.tabButtonSpacing);
|
||||
Widget::addChild("tabBar", m_tabBar);
|
||||
|
||||
m_stack = make_shared<StackWidget>();
|
||||
addChild("tabs", m_stack);
|
||||
|
||||
markAsContainer();
|
||||
}
|
||||
|
||||
void TabSetWidget::setSize(Vec2I const& size) {
|
||||
auto imgMetadata = Root::singleton().imageMetadataDatabase();
|
||||
auto tabHeight = max({imgMetadata->imageSize(m_tabSetConfig.tabButtonBaseImage).y(),
|
||||
imgMetadata->imageSize(m_tabSetConfig.tabButtonHoverImage).y(),
|
||||
imgMetadata->imageSize(m_tabSetConfig.tabButtonPressedImage).y(),
|
||||
imgMetadata->imageSize(m_tabSetConfig.tabButtonBaseImageSelected).y(),
|
||||
imgMetadata->imageSize(m_tabSetConfig.tabButtonHoverImageSelected).y(),
|
||||
imgMetadata->imageSize(m_tabSetConfig.tabButtonPressedImageSelected).y()});
|
||||
|
||||
Widget::setSize(Vec2I(size.x(), max<int>(size.y(), tabHeight)));
|
||||
|
||||
m_tabBar->setSize({size.x(), tabHeight});
|
||||
m_tabBar->setPosition({0, size.y() - tabHeight});
|
||||
m_stack->setSize({size.x(), size.y() - tabHeight});
|
||||
}
|
||||
|
||||
void TabSetWidget::addTab(String const& widgetName, WidgetPtr widget, String const& title) {
|
||||
auto newButton = make_shared<ButtonWidget>();
|
||||
newButton->setImages(
|
||||
m_tabSetConfig.tabButtonBaseImage, m_tabSetConfig.tabButtonHoverImage, m_tabSetConfig.tabButtonPressedImage);
|
||||
newButton->setCheckedImages(m_tabSetConfig.tabButtonBaseImageSelected,
|
||||
m_tabSetConfig.tabButtonHoverImageSelected,
|
||||
m_tabSetConfig.tabButtonPressedImageSelected);
|
||||
newButton->setCheckable(true);
|
||||
newButton->setText(title);
|
||||
newButton->setTextOffset(m_tabSetConfig.tabButtonTextOffset);
|
||||
newButton->setPressedOffset(m_tabSetConfig.tabButtonPressedOffset);
|
||||
|
||||
size_t pageForButton = m_tabBar->numChildren();
|
||||
newButton->setCallback([this, pageForButton](Widget*) { tabSelect(pageForButton); });
|
||||
|
||||
m_tabBar->addChild(toString(pageForButton), newButton);
|
||||
m_stack->addChild(widgetName, widget);
|
||||
|
||||
if (!m_lastSelected)
|
||||
tabSelect(0);
|
||||
}
|
||||
|
||||
size_t TabSetWidget::tabCount() const {
|
||||
return m_tabBar->numChildren();
|
||||
}
|
||||
|
||||
void TabSetWidget::tabSelect(size_t page) {
|
||||
if (m_lastSelected != page) {
|
||||
m_lastSelected = page;
|
||||
m_stack->showPage(page);
|
||||
for (size_t i = 0; i < m_tabBar->numChildren(); ++i) {
|
||||
if (i == page)
|
||||
m_tabBar->getChildNum<ButtonWidget>(i)->setChecked(true);
|
||||
else
|
||||
m_tabBar->getChildNum<ButtonWidget>(i)->setChecked(false);
|
||||
}
|
||||
if (m_callback)
|
||||
m_callback(this);
|
||||
} else {
|
||||
m_tabBar->getChildNum<ButtonWidget>(page)->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
size_t TabSetWidget::selectedTab() const {
|
||||
return m_lastSelected.value(NPos);
|
||||
}
|
||||
|
||||
void TabSetWidget::setCallback(WidgetCallbackFunc callback) {
|
||||
m_callback = move(callback);
|
||||
}
|
||||
|
||||
}
|
51
source/windowing/StarTabSet.hpp
Normal file
51
source/windowing/StarTabSet.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#ifndef STAR_TAB_SET_HPP
|
||||
#define STAR_TAB_SET_HPP
|
||||
|
||||
#include "StarButtonGroup.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(FlowLayout);
|
||||
STAR_CLASS(StackWidget);
|
||||
|
||||
STAR_CLASS(TabBarWidget);
|
||||
STAR_CLASS(TabSetWidget);
|
||||
|
||||
struct TabSetConfig {
|
||||
String tabButtonBaseImage;
|
||||
String tabButtonHoverImage;
|
||||
String tabButtonPressedImage;
|
||||
String tabButtonBaseImageSelected;
|
||||
String tabButtonHoverImageSelected;
|
||||
String tabButtonPressedImageSelected;
|
||||
Vec2I tabButtonPressedOffset;
|
||||
Vec2I tabButtonTextOffset;
|
||||
Vec2I tabButtonSpacing;
|
||||
};
|
||||
|
||||
class TabSetWidget : public Widget {
|
||||
public:
|
||||
TabSetWidget(TabSetConfig const& tabSetconfig);
|
||||
|
||||
virtual void setSize(Vec2I const& size) override;
|
||||
|
||||
void addTab(String const& widgetName, WidgetPtr widget, String const& title);
|
||||
|
||||
size_t tabCount() const;
|
||||
void tabSelect(size_t page);
|
||||
size_t selectedTab() const;
|
||||
|
||||
// Callback is called when the tab changes
|
||||
void setCallback(WidgetCallbackFunc callback);
|
||||
|
||||
private:
|
||||
TabSetConfig m_tabSetConfig;
|
||||
FlowLayoutPtr m_tabBar;
|
||||
StackWidgetPtr m_stack;
|
||||
WidgetCallbackFunc m_callback;
|
||||
Maybe<size_t> m_lastSelected;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
393
source/windowing/StarTextBoxWidget.cpp
Normal file
393
source/windowing/StarTextBoxWidget.cpp
Normal file
|
@ -0,0 +1,393 @@
|
|||
#include "StarTextBoxWidget.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
TextBoxWidget::TextBoxWidget(String const& startingText, String const& hint, WidgetCallbackFunc callback)
|
||||
: m_text(startingText), m_hint(hint), m_callback(callback) {
|
||||
m_regex = ".*";
|
||||
m_repeatKeyThreshold = 0;
|
||||
m_repeatCode = SpecialRepeatKeyCodes::None;
|
||||
m_isPressed = false;
|
||||
m_isHover = false;
|
||||
m_cursorOffset = startingText.size();
|
||||
m_hAnchor = HorizontalAnchor::LeftAnchor;
|
||||
m_vAnchor = VerticalAnchor::BottomAnchor;
|
||||
m_color = Color::rgbf(1, 1, 1);
|
||||
m_drawBorder = false;
|
||||
m_cursorHoriz = Vec2I();
|
||||
m_cursorVert = Vec2I();
|
||||
m_overfillMode = true;
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
m_maxWidth = assets->json("/interface.config:textBoxDefaultWidth").toInt();
|
||||
m_fontSize = assets->json("/interface.config:font.baseSize").toInt();
|
||||
|
||||
// Meh, padding is hard-coded here
|
||||
setSize({m_maxWidth + 6, m_fontSize + 2});
|
||||
m_cursorHoriz = jsonToVec2I(assets->json("/interface.config:textboxCursorHorizontal"));
|
||||
m_cursorVert = jsonToVec2I(assets->json("/interface.config:textboxCursorVertical"));
|
||||
}
|
||||
|
||||
void TextBoxWidget::renderImpl() {
|
||||
float blueRate = 0;
|
||||
if (m_isHover && !m_isPressed)
|
||||
blueRate = 0.2f;
|
||||
else
|
||||
blueRate = 0.0f;
|
||||
|
||||
Vec2F pos(screenPosition());
|
||||
if (m_hAnchor == HorizontalAnchor::HMidAnchor)
|
||||
pos += Vec2F(size()[0] / 2.0f, 0);
|
||||
else if (m_hAnchor == HorizontalAnchor::RightAnchor)
|
||||
pos += Vec2F(size()[0], 0);
|
||||
|
||||
if ((m_maxWidth != -1) && m_overfillMode) {
|
||||
context()->setFontSize(m_fontSize);
|
||||
int shift = std::max(0, getCursorOffset() - m_maxWidth);
|
||||
pos += Vec2F(-shift, 0);
|
||||
}
|
||||
|
||||
context()->setFontSize(m_fontSize);
|
||||
if (m_text.empty()) {
|
||||
context()->setFontColor(m_color.mix(Color::rgbf(0.3f, 0.3f, 0.3f), 0.8f).mix(Color::rgbf(0.0f, 0.0f, 1.0f), blueRate).toRgba());
|
||||
context()->renderInterfaceText(m_hint, {pos, m_hAnchor, m_vAnchor});
|
||||
} else {
|
||||
context()->setFontColor(m_color.mix(Color::rgbf(0, 0, 1), blueRate).toRgba());
|
||||
context()->renderInterfaceText(m_text, {pos, m_hAnchor, m_vAnchor});
|
||||
}
|
||||
context()->setFontColor(Vec4B::filled(255));
|
||||
|
||||
if (hasFocus()) {
|
||||
// render cursor
|
||||
float cc = 0.6f + 0.4f * std::sin((double)Time::monotonicMilliseconds() / 300.0);
|
||||
Color cursorColor = Color::rgbf(cc, cc, cc);
|
||||
|
||||
context()->drawInterfaceLine(
|
||||
pos + Vec2F(getCursorOffset(), m_fontSize * m_cursorVert[0]),
|
||||
pos + Vec2F(getCursorOffset(), m_fontSize * m_cursorVert[1]),
|
||||
cursorColor.toRgba());
|
||||
context()->drawInterfaceLine(
|
||||
pos + Vec2F(getCursorOffset() + m_fontSize * m_cursorHoriz[0], m_fontSize * m_cursorVert[0]),
|
||||
pos + Vec2F(getCursorOffset() + m_fontSize * m_cursorHoriz[1], m_fontSize * m_cursorVert[0]),
|
||||
cursorColor.toRgba());
|
||||
}
|
||||
|
||||
if (m_drawBorder)
|
||||
context()->drawInterfacePolyLines(PolyF(screenBoundRect()), Color(Color::White).toRgba());
|
||||
}
|
||||
|
||||
int TextBoxWidget::getCursorOffset() { // horizontal only
|
||||
float scale;
|
||||
context()->setFontSize(m_fontSize);
|
||||
if (m_hAnchor == HorizontalAnchor::LeftAnchor) {
|
||||
scale = 1.0;
|
||||
} else if (m_hAnchor == HorizontalAnchor::HMidAnchor) {
|
||||
scale = 0.5;
|
||||
} else if (m_hAnchor == HorizontalAnchor::RightAnchor) {
|
||||
scale = -1.0;
|
||||
return context()->stringInterfaceWidth(m_text) * scale
|
||||
+ context()->stringInterfaceWidth(m_text.substr(m_cursorOffset, m_text.size()));
|
||||
} else {
|
||||
throw GuiException("Somehow, the value of m_hAnchor became bad");
|
||||
}
|
||||
|
||||
return (int)std::ceil(context()->stringInterfaceWidth(m_text) * scale
|
||||
- context()->stringInterfaceWidth(m_text.substr(m_cursorOffset, m_text.size())));
|
||||
}
|
||||
|
||||
void TextBoxWidget::update() {
|
||||
Widget::update();
|
||||
if (m_repeatCode != SpecialRepeatKeyCodes::None) {
|
||||
if (Time::monotonicMilliseconds() >= m_repeatKeyThreshold) {
|
||||
m_repeatKeyThreshold += 50;
|
||||
if (m_repeatCode == SpecialRepeatKeyCodes::Delete) {
|
||||
if (m_cursorOffset < (int)m_text.size())
|
||||
modText(m_text.substr(0, m_cursorOffset) + m_text.substr(m_cursorOffset + 1));
|
||||
} else if (m_repeatCode == SpecialRepeatKeyCodes::Backspace) {
|
||||
if (m_cursorOffset > 0) {
|
||||
if (modText(m_text.substr(0, m_cursorOffset - 1) + m_text.substr(m_cursorOffset)))
|
||||
m_cursorOffset -= 1;
|
||||
}
|
||||
} else if (m_repeatCode == SpecialRepeatKeyCodes::Left) {
|
||||
if (m_cursorOffset > 0) {
|
||||
m_cursorOffset--;
|
||||
if (m_cursorOffset < 0)
|
||||
m_cursorOffset = 0;
|
||||
}
|
||||
} else if (m_repeatCode == SpecialRepeatKeyCodes::Right) {
|
||||
if (m_cursorOffset < (int)m_text.size()) {
|
||||
m_cursorOffset++;
|
||||
if (m_cursorOffset > (int)m_text.size())
|
||||
m_cursorOffset = m_text.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String TextBoxWidget::getText() {
|
||||
return m_text;
|
||||
}
|
||||
|
||||
bool TextBoxWidget::setText(String const& text, bool callback) {
|
||||
if (m_text == text)
|
||||
return true;
|
||||
|
||||
if (!newTextValid(text))
|
||||
return false;
|
||||
|
||||
m_text = text;
|
||||
m_cursorOffset = m_text.size();
|
||||
m_repeatCode = SpecialRepeatKeyCodes::None;
|
||||
if (callback)
|
||||
m_callback(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
String TextBoxWidget::getRegex() {
|
||||
return m_regex;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setRegex(String const& regex) {
|
||||
m_regex = regex;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setColor(Color const& color) {
|
||||
m_color = color;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setFontSize(int fontSize) {
|
||||
m_fontSize = fontSize;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setMaxWidth(int maxWidth) {
|
||||
m_maxWidth = maxWidth;
|
||||
setSize({m_maxWidth + 6, m_fontSize + 2});
|
||||
}
|
||||
|
||||
void TextBoxWidget::setOverfillMode(bool overtype) {
|
||||
m_overfillMode = overtype;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setOnBlurCallback(WidgetCallbackFunc onBlur) {
|
||||
m_onBlur = onBlur;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setOnEnterKeyCallback(WidgetCallbackFunc onEnterKey) {
|
||||
m_onEnterKey = onEnterKey;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setOnEscapeKeyCallback(WidgetCallbackFunc onEscapeKey) {
|
||||
m_onEscapeKey = onEscapeKey;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setNextFocus(Maybe<String> nextFocus) {
|
||||
m_nextFocus = nextFocus;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setPrevFocus(Maybe<String> prevFocus) {
|
||||
m_prevFocus = prevFocus;
|
||||
}
|
||||
|
||||
void TextBoxWidget::mouseOver() {
|
||||
Widget::mouseOver();
|
||||
m_isHover = true;
|
||||
}
|
||||
|
||||
void TextBoxWidget::mouseOut() {
|
||||
Widget::mouseOut();
|
||||
m_isHover = false;
|
||||
m_isPressed = false;
|
||||
}
|
||||
|
||||
void TextBoxWidget::mouseReturnStillDown() {
|
||||
Widget::mouseReturnStillDown();
|
||||
m_isHover = true;
|
||||
m_isPressed = true;
|
||||
}
|
||||
|
||||
void TextBoxWidget::blur() {
|
||||
Widget::blur();
|
||||
if (m_onBlur)
|
||||
m_onBlur(this);
|
||||
m_repeatCode = SpecialRepeatKeyCodes::None;
|
||||
}
|
||||
|
||||
KeyboardCaptureMode TextBoxWidget::keyboardCaptured() const {
|
||||
if (active() && hasFocus())
|
||||
return KeyboardCaptureMode::TextInput;
|
||||
return KeyboardCaptureMode::None;
|
||||
}
|
||||
|
||||
bool TextBoxWidget::sendEvent(InputEvent const& event) {
|
||||
if (!hasFocus())
|
||||
return false;
|
||||
|
||||
if (event.is<KeyUpEvent>()) {
|
||||
m_repeatCode = SpecialRepeatKeyCodes::None;
|
||||
m_callback(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (innerSendEvent(event)) {
|
||||
m_callback(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextBoxWidget::innerSendEvent(InputEvent const& event) {
|
||||
if (auto keyDown = event.ptr<KeyDownEvent>()) {
|
||||
m_repeatKeyThreshold = Time::monotonicMilliseconds() + 300LL;
|
||||
|
||||
if (keyDown->key == Key::Escape) {
|
||||
if (m_onEscapeKey) {
|
||||
m_onEscapeKey(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (keyDown->key == Key::Return || keyDown->key == Key::Kp_enter) {
|
||||
if (m_onEnterKey) {
|
||||
m_onEnterKey(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (keyDown->key == Key::Tab) {
|
||||
if ((KeyMod::LShift & keyDown->mods) == KeyMod::LShift || (KeyMod::RShift & keyDown->mods) == KeyMod::RShift) {
|
||||
if (m_prevFocus) {
|
||||
if (auto prevWidget = parent()->fetchChild(*m_prevFocus)) {
|
||||
prevWidget->focus();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (m_nextFocus) {
|
||||
if (auto nextWidget = parent()->fetchChild(*m_nextFocus)) {
|
||||
nextWidget->focus();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ((keyDown->mods & (KeyMod::LCtrl | KeyMod::RCtrl)) != KeyMod::NoMod) {
|
||||
if (keyDown->key == Key::C) {
|
||||
context()->setClipboard(m_text);
|
||||
return true;
|
||||
}
|
||||
if (keyDown->key == Key::X) {
|
||||
context()->setClipboard(m_text);
|
||||
if (modText(""))
|
||||
m_cursorOffset = 0;
|
||||
return true;
|
||||
}
|
||||
if (keyDown->key == Key::V) {
|
||||
String clipboard = context()->getClipboard();
|
||||
if (modText(m_text.substr(0, m_cursorOffset) + clipboard + m_text.substr(m_cursorOffset)))
|
||||
m_cursorOffset += clipboard.size();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int rightSteps = 1;
|
||||
int leftSteps = 1;
|
||||
if ((keyDown->mods & (KeyMod::LCtrl | KeyMod::RCtrl)) != KeyMod::NoMod || (keyDown->mods & (KeyMod::LAlt | KeyMod::RAlt)) != KeyMod::NoMod) {
|
||||
rightSteps = m_text.findNextBoundary(m_cursorOffset) - m_cursorOffset;
|
||||
leftSteps = m_cursorOffset - m_text.findNextBoundary(m_cursorOffset, true);
|
||||
|
||||
if (rightSteps < 1)
|
||||
rightSteps = 1;
|
||||
if (leftSteps < 1)
|
||||
leftSteps = 1;
|
||||
}
|
||||
|
||||
if (keyDown->key == Key::Backspace) {
|
||||
m_repeatCode = SpecialRepeatKeyCodes::Backspace;
|
||||
for (int i = 0; i < leftSteps; i++) {
|
||||
if (m_cursorOffset > 0) {
|
||||
if (modText(m_text.substr(0, m_cursorOffset - 1) + m_text.substr(m_cursorOffset)))
|
||||
m_cursorOffset -= 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (keyDown->key == Key::Delete) {
|
||||
m_repeatCode = SpecialRepeatKeyCodes::Delete;
|
||||
for (int i = 0; i < rightSteps; i++) {
|
||||
if (m_cursorOffset < (int)m_text.size())
|
||||
modText(m_text.substr(0, m_cursorOffset) + m_text.substr(m_cursorOffset + 1));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (keyDown->key == Key::Left) {
|
||||
m_repeatCode = SpecialRepeatKeyCodes::Left;
|
||||
for (int i = 0; i < leftSteps; i++) {
|
||||
m_cursorOffset--;
|
||||
if (m_cursorOffset < 0)
|
||||
m_cursorOffset = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (keyDown->key == Key::Right) {
|
||||
m_repeatCode = SpecialRepeatKeyCodes::Right;
|
||||
for (int i = 0; i < rightSteps; i++) {
|
||||
m_cursorOffset++;
|
||||
if (m_cursorOffset > (int)m_text.size())
|
||||
m_cursorOffset = m_text.size();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (keyDown->key == Key::Home) {
|
||||
m_cursorOffset = 0;
|
||||
return true;
|
||||
}
|
||||
if (keyDown->key == Key::End) {
|
||||
m_cursorOffset = m_text.size();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto textInput = event.ptr<TextInputEvent>()) {
|
||||
if (modText(m_text.substr(0, m_cursorOffset) + textInput->text + m_text.substr(m_cursorOffset)))
|
||||
m_cursorOffset += textInput->text.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setDrawBorder(bool drawBorder) {
|
||||
m_drawBorder = drawBorder;
|
||||
}
|
||||
|
||||
void TextBoxWidget::setTextAlign(HorizontalAnchor hAnchor) {
|
||||
m_hAnchor = hAnchor;
|
||||
}
|
||||
|
||||
bool TextBoxWidget::modText(String const& text) {
|
||||
if (m_text != text && newTextValid(text)) {
|
||||
m_text = text;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TextBoxWidget::newTextValid(String const& text) const {
|
||||
if (!text.regexMatch(m_regex))
|
||||
return false;
|
||||
if ((m_maxWidth != -1) && !m_overfillMode) {
|
||||
context()->setFontSize(m_fontSize);
|
||||
return context()->stringInterfaceWidth(text) <= m_maxWidth;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
85
source/windowing/StarTextBoxWidget.hpp
Normal file
85
source/windowing/StarTextBoxWidget.hpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
#ifndef STAR_TEXTBOX_WIDGET_HPP
|
||||
#define STAR_TEXTBOX_WIDGET_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
enum class SpecialRepeatKeyCodes : String::Char { None, Delete, Backspace, Left, Right };
|
||||
|
||||
STAR_CLASS(TextBoxWidget);
|
||||
class TextBoxWidget : public Widget {
|
||||
public:
|
||||
TextBoxWidget(String const& startingText, String const& hint, WidgetCallbackFunc callback);
|
||||
|
||||
virtual void update() override;
|
||||
|
||||
String getText();
|
||||
bool setText(String const& text, bool callback = true);
|
||||
|
||||
// Set the regex that the text-box must match. Defaults to .*
|
||||
String getRegex();
|
||||
void setRegex(String const& regex);
|
||||
|
||||
void setColor(Color const& color);
|
||||
void setFontSize(int fontSize);
|
||||
void setMaxWidth(int maxWidth);
|
||||
void setOverfillMode(bool overfillMode);
|
||||
|
||||
void setOnBlurCallback(WidgetCallbackFunc onBlur);
|
||||
void setOnEnterKeyCallback(WidgetCallbackFunc onEnterKey);
|
||||
void setOnEscapeKeyCallback(WidgetCallbackFunc onEscapeKey);
|
||||
|
||||
void setNextFocus(Maybe<String> nextFocus);
|
||||
void setPrevFocus(Maybe<String> prevFocus);
|
||||
|
||||
bool sendEvent(InputEvent const& event) override;
|
||||
|
||||
void setDrawBorder(bool drawBorder);
|
||||
void setTextAlign(HorizontalAnchor hAnchor);
|
||||
int getCursorOffset();
|
||||
|
||||
virtual void mouseOver() override;
|
||||
virtual void mouseOut() override;
|
||||
virtual void mouseReturnStillDown() override;
|
||||
|
||||
virtual void blur() override;
|
||||
|
||||
virtual KeyboardCaptureMode keyboardCaptured() const override;
|
||||
|
||||
protected:
|
||||
virtual void renderImpl() override;
|
||||
|
||||
private:
|
||||
bool innerSendEvent(InputEvent const& event);
|
||||
bool modText(String const& text);
|
||||
bool newTextValid(String const& text) const;
|
||||
|
||||
String m_text;
|
||||
String m_hint;
|
||||
String m_regex;
|
||||
HorizontalAnchor m_hAnchor;
|
||||
VerticalAnchor m_vAnchor;
|
||||
Color m_color;
|
||||
int m_fontSize;
|
||||
int m_maxWidth;
|
||||
int m_cursorOffset;
|
||||
bool m_isHover;
|
||||
bool m_isPressed;
|
||||
SpecialRepeatKeyCodes m_repeatCode;
|
||||
int64_t m_repeatKeyThreshold;
|
||||
WidgetCallbackFunc m_callback;
|
||||
WidgetCallbackFunc m_onBlur;
|
||||
WidgetCallbackFunc m_onEnterKey;
|
||||
WidgetCallbackFunc m_onEscapeKey;
|
||||
Maybe<String> m_nextFocus;
|
||||
Maybe<String> m_prevFocus;
|
||||
bool m_drawBorder;
|
||||
Vec2I m_cursorHoriz;
|
||||
Vec2I m_cursorVert;
|
||||
bool m_overfillMode;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
101
source/windowing/StarVerticalLayout.cpp
Normal file
101
source/windowing/StarVerticalLayout.cpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
#include "StarVerticalLayout.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
VerticalLayout::VerticalLayout(VerticalAnchor verticalAnchor, int verticalSpacing) {
|
||||
setVerticalAnchor(verticalAnchor);
|
||||
setVerticalSpacing(verticalSpacing);
|
||||
|
||||
disableScissoring();
|
||||
}
|
||||
|
||||
void VerticalLayout::update() {
|
||||
m_size = Vec2I(0, 0);
|
||||
|
||||
if (m_members.empty())
|
||||
return;
|
||||
|
||||
for (auto const& child : m_members) {
|
||||
auto childSize = child->size();
|
||||
m_size[1] += childSize[1];
|
||||
m_size[0] = max(m_size[0], childSize[0]);
|
||||
}
|
||||
m_size[1] += (m_members.size() - 1) * m_verticalSpacing;
|
||||
|
||||
auto bounds = contentBoundRect();
|
||||
|
||||
int verticalPos = m_fillDown ? bounds.yMax() : bounds.yMin();
|
||||
for (auto const& child : reverseIterate(m_members)) {
|
||||
auto childSize = child->size();
|
||||
|
||||
Vec2I targetPosition;
|
||||
|
||||
if (m_horizontalAnchor == HorizontalAnchor::LeftAnchor)
|
||||
targetPosition[0] = bounds.xMin();
|
||||
else if (m_horizontalAnchor == HorizontalAnchor::RightAnchor)
|
||||
targetPosition[0] = bounds.xMax() - childSize[0];
|
||||
else if (m_horizontalAnchor == HorizontalAnchor::HMidAnchor)
|
||||
targetPosition[0] = -childSize[0] / 2;
|
||||
|
||||
if (m_fillDown) {
|
||||
verticalPos -= childSize[1];
|
||||
targetPosition[1] = verticalPos;
|
||||
verticalPos -= m_verticalSpacing;
|
||||
} else {
|
||||
targetPosition[1] = verticalPos;
|
||||
verticalPos -= childSize[1];
|
||||
verticalPos -= m_verticalSpacing;
|
||||
}
|
||||
|
||||
// needed because position is included in relativeBoundRect
|
||||
child->setPosition(Vec2I(0, 0));
|
||||
|
||||
child->setPosition(targetPosition - child->relativeBoundRect().min());
|
||||
}
|
||||
}
|
||||
|
||||
Vec2I VerticalLayout::size() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
RectI VerticalLayout::relativeBoundRect() const {
|
||||
return contentBoundRect().translated(relativePosition());
|
||||
}
|
||||
|
||||
void VerticalLayout::setHorizontalAnchor(HorizontalAnchor horizontalAnchor) {
|
||||
m_horizontalAnchor = horizontalAnchor;
|
||||
update();
|
||||
}
|
||||
|
||||
void VerticalLayout::setVerticalAnchor(VerticalAnchor verticalAnchor) {
|
||||
m_verticalAnchor = verticalAnchor;
|
||||
update();
|
||||
}
|
||||
|
||||
void VerticalLayout::setVerticalSpacing(int verticalSpacing) {
|
||||
m_verticalSpacing = verticalSpacing;
|
||||
update();
|
||||
}
|
||||
|
||||
void VerticalLayout::setFillDown(bool fillDown) {
|
||||
m_fillDown = fillDown;
|
||||
update();
|
||||
}
|
||||
|
||||
RectI VerticalLayout::contentBoundRect() const {
|
||||
auto min = Vec2I(0, 0);
|
||||
|
||||
if (m_horizontalAnchor == HorizontalAnchor::RightAnchor)
|
||||
min[0] -= m_size[0];
|
||||
else if (m_horizontalAnchor == HorizontalAnchor::HMidAnchor)
|
||||
min[0] -= m_size[0] / 2;
|
||||
|
||||
if (m_verticalAnchor == VerticalAnchor::TopAnchor)
|
||||
min[1] -= m_size[1];
|
||||
else if (m_verticalAnchor == VerticalAnchor::VMidAnchor)
|
||||
min[1] -= m_size[1] / 2;
|
||||
|
||||
return RectI::withSize(min, m_size);
|
||||
}
|
||||
|
||||
}
|
35
source/windowing/StarVerticalLayout.hpp
Normal file
35
source/windowing/StarVerticalLayout.hpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef STAR_VERTICAL_LAYOUT_HPP
|
||||
#define STAR_VERTICAL_LAYOUT_HPP
|
||||
|
||||
#include "StarLayout.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(VerticalLayout);
|
||||
|
||||
class VerticalLayout : public Layout {
|
||||
public:
|
||||
VerticalLayout(VerticalAnchor verticalAnchor = VerticalAnchor::TopAnchor, int verticalSpacing = 0);
|
||||
|
||||
void update() override;
|
||||
Vec2I size() const override;
|
||||
RectI relativeBoundRect() const override;
|
||||
|
||||
void setHorizontalAnchor(HorizontalAnchor horizontalAnchor);
|
||||
void setVerticalAnchor(VerticalAnchor verticalAnchor);
|
||||
void setVerticalSpacing(int verticalSpacing);
|
||||
void setFillDown(bool fillDown);
|
||||
|
||||
private:
|
||||
RectI contentBoundRect() const;
|
||||
|
||||
HorizontalAnchor m_horizontalAnchor;
|
||||
VerticalAnchor m_verticalAnchor;
|
||||
int m_verticalSpacing;
|
||||
bool m_fillDown;
|
||||
Vec2I m_size;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
474
source/windowing/StarWidget.cpp
Normal file
474
source/windowing/StarWidget.cpp
Normal file
|
@ -0,0 +1,474 @@
|
|||
#include "StarWidget.hpp"
|
||||
#include "StarPane.hpp"
|
||||
|
||||
#include "StarLabelWidget.hpp"
|
||||
#include "StarFlowLayout.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
Widget::Widget() {
|
||||
m_parent = nullptr;
|
||||
m_context = GuiContext::singletonPtr();
|
||||
m_visible = true;
|
||||
m_focus = false;
|
||||
m_doScissor = true;
|
||||
m_container = false;
|
||||
m_mouseTransparent = false;
|
||||
}
|
||||
|
||||
Widget::~Widget() {
|
||||
removeAllChildren();
|
||||
}
|
||||
|
||||
void Widget::update() {
|
||||
for (auto widget : m_members)
|
||||
widget->update();
|
||||
}
|
||||
|
||||
GuiContext* Widget::context() const {
|
||||
return m_context;
|
||||
}
|
||||
|
||||
void Widget::render(RectI const& region) {
|
||||
if (!m_visible)
|
||||
return;
|
||||
|
||||
if (!setupDrawRegion(region))
|
||||
return;
|
||||
|
||||
renderImpl();
|
||||
drawChildren();
|
||||
}
|
||||
|
||||
void Widget::renderImpl() {}
|
||||
|
||||
void Widget::drawChildren() {
|
||||
for (auto child : m_members)
|
||||
child->render(m_drawingArea);
|
||||
}
|
||||
|
||||
Vec2I Widget::position() const {
|
||||
return m_position + m_drawingOffset;
|
||||
}
|
||||
|
||||
Vec2I Widget::relativePosition() const {
|
||||
return m_position;
|
||||
}
|
||||
|
||||
bool Widget::setupDrawRegion(RectI const& region) {
|
||||
RectI scissorRect;
|
||||
if (m_doScissor) {
|
||||
scissorRect = getScissorRect();
|
||||
} else {
|
||||
scissorRect = noScissor();
|
||||
}
|
||||
m_drawingArea = scissorRect.limited(region);
|
||||
if (m_drawingArea.isEmpty())
|
||||
return false;
|
||||
|
||||
m_context->setInterfaceScissorRect(m_drawingArea);
|
||||
return true;
|
||||
}
|
||||
|
||||
Vec2I Widget::drawingOffset() const {
|
||||
return m_drawingOffset;
|
||||
}
|
||||
|
||||
void Widget::setDrawingOffset(Vec2I const& offset) {
|
||||
m_drawingOffset = offset;
|
||||
}
|
||||
|
||||
RectI Widget::getScissorRect() const {
|
||||
return screenBoundRect();
|
||||
}
|
||||
|
||||
RectI Widget::noScissor() const {
|
||||
return RectI::inf();
|
||||
}
|
||||
|
||||
void Widget::disableScissoring() {
|
||||
m_doScissor = false;
|
||||
}
|
||||
|
||||
void Widget::enableScissoring() {
|
||||
m_doScissor = true;
|
||||
}
|
||||
|
||||
void Widget::setPosition(Vec2I const& position) {
|
||||
m_position = position;
|
||||
}
|
||||
|
||||
Vec2I Widget::size() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
void Widget::setSize(Vec2I const& size) {
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
RectI Widget::relativeBoundRect() const {
|
||||
return RectI::withSize(relativePosition(), size());
|
||||
}
|
||||
|
||||
RectI Widget::screenBoundRect() const {
|
||||
return relativeBoundRect().translated(screenPosition() - relativePosition());
|
||||
}
|
||||
|
||||
void Widget::determineSizeFromChildren() {
|
||||
Vec2I max;
|
||||
|
||||
for (auto& child : m_members) {
|
||||
Vec2I childMax = child->position() + child->size();
|
||||
max = max.piecewiseMax(childMax);
|
||||
}
|
||||
|
||||
setSize(max);
|
||||
}
|
||||
|
||||
void Widget::markAsContainer() {
|
||||
m_container = true;
|
||||
}
|
||||
|
||||
KeyboardCaptureMode Widget::keyboardCaptured() const {
|
||||
if (active()) {
|
||||
for (auto const& member : m_members) {
|
||||
auto mode = member->keyboardCaptured();
|
||||
if (mode != KeyboardCaptureMode::None)
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return KeyboardCaptureMode::None;
|
||||
}
|
||||
|
||||
void Widget::setData(Json const& data) {
|
||||
m_data = data;
|
||||
}
|
||||
|
||||
Json const& Widget::data() {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
Vec2I Widget::screenPosition() const {
|
||||
if (m_parent) {
|
||||
return m_parent->screenPosition() + position();
|
||||
} else {
|
||||
return position();
|
||||
}
|
||||
}
|
||||
|
||||
bool Widget::inMember(Vec2I const& position) const {
|
||||
if (!m_visible)
|
||||
return false;
|
||||
|
||||
if (m_mouseTransparent)
|
||||
return false;
|
||||
|
||||
if (!m_drawingArea.isNull() && !m_drawingArea.contains(Vec2I::floor(position)))
|
||||
return false;
|
||||
|
||||
if (m_container) {
|
||||
for (auto child : m_members)
|
||||
if (child->inMember(position))
|
||||
return true;
|
||||
} else {
|
||||
return screenBoundRect().contains(position);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Widget::sendEvent(InputEvent const& event) {
|
||||
if (!m_visible)
|
||||
return false;
|
||||
|
||||
for (auto child : reverseIterate(m_members)) {
|
||||
if (child->sendEvent(event))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Widget::mouseOver() {}
|
||||
|
||||
void Widget::mouseOut() {}
|
||||
|
||||
void Widget::mouseReturnStillDown() {}
|
||||
|
||||
void Widget::setMouseTransparent(bool transparent) {
|
||||
m_mouseTransparent = transparent;
|
||||
}
|
||||
|
||||
bool Widget::mouseTransparent() {
|
||||
return m_mouseTransparent;
|
||||
}
|
||||
|
||||
Widget* Widget::parent() const {
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
void Widget::setParent(Widget* parent) {
|
||||
m_parent = parent;
|
||||
}
|
||||
|
||||
void Widget::show() {
|
||||
m_visible = true;
|
||||
}
|
||||
|
||||
void Widget::hide() {
|
||||
m_visible = false;
|
||||
}
|
||||
|
||||
void Widget::toggleVisibility() {
|
||||
m_visible = !m_visible;
|
||||
}
|
||||
|
||||
void Widget::setVisibility(bool visibility) {
|
||||
if (visibility)
|
||||
show();
|
||||
else
|
||||
hide();
|
||||
}
|
||||
|
||||
bool Widget::active() const {
|
||||
return m_visible;
|
||||
}
|
||||
|
||||
bool Widget::interactive() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Widget::hasFocus() const {
|
||||
return m_focus;
|
||||
}
|
||||
|
||||
void Widget::focus() {
|
||||
m_focus = true;
|
||||
if (auto w = window())
|
||||
w->setFocus(this);
|
||||
}
|
||||
|
||||
void Widget::blur() {
|
||||
m_focus = false;
|
||||
if (window())
|
||||
window()->removeFocus(this);
|
||||
}
|
||||
|
||||
unsigned Widget::windowHeight() const {
|
||||
return context()->windowHeight();
|
||||
}
|
||||
|
||||
unsigned Widget::windowWidth() const {
|
||||
return context()->windowWidth();
|
||||
}
|
||||
|
||||
Vec2I Widget::windowSize() const {
|
||||
return Vec2I(context()->windowSize());
|
||||
}
|
||||
|
||||
Pane* Widget::window() {
|
||||
if (m_parent)
|
||||
return m_parent->window();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Pane const* Widget::window() const {
|
||||
if (m_parent)
|
||||
return m_parent->window();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Widget::addChild(String const& name, WidgetPtr member) {
|
||||
member->setName(name);
|
||||
m_members.push_back(member);
|
||||
m_memberHash[member->name()] = member;
|
||||
member->setParent(this);
|
||||
}
|
||||
|
||||
void Widget::addChildAt(String const& name, WidgetPtr member, size_t at) {
|
||||
if (at > m_members.size())
|
||||
throw GuiException("Attempted to insert item after the end of the list.");
|
||||
|
||||
m_members.insert(m_members.begin() + at, member);
|
||||
m_memberHash[name] = member;
|
||||
member->setName(name);
|
||||
member->setParent(this);
|
||||
}
|
||||
|
||||
bool Widget::removeChild(Widget* member) {
|
||||
m_memberHash.erase(member->name());
|
||||
for (auto child = m_members.begin(); child != m_members.end(); ++child) {
|
||||
if (child->get() == member) {
|
||||
(*child)->setParent(nullptr);
|
||||
m_members.erase(child);
|
||||
return true;
|
||||
} else {
|
||||
if ((*child)->removeChild(member))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Widget::removeChild(String const& name) {
|
||||
m_memberHash.erase(name);
|
||||
for (auto child = m_members.begin(); child != m_members.end(); ++child) {
|
||||
auto ptr = *child;
|
||||
if (ptr->name() == name) {
|
||||
m_members.erase(child);
|
||||
ptr->setParent(nullptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Widget::removeChildAt(size_t at) {
|
||||
if (at >= m_members.size())
|
||||
return false;
|
||||
|
||||
m_memberHash.erase(m_members.at(at)->name());
|
||||
|
||||
m_members.at(at)->setParent(nullptr);
|
||||
m_members.erase(m_members.begin() + at);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Widget::removeAllChildren() {
|
||||
for (auto child : m_members)
|
||||
child->setParent(nullptr);
|
||||
|
||||
m_members.clear();
|
||||
m_memberHash.clear();
|
||||
}
|
||||
|
||||
bool Widget::containsChild(String const& name) {
|
||||
if (fetchChild(name))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
WidgetPtr Widget::fetchChild(String const& name) {
|
||||
WidgetPtr res;
|
||||
if (name.contains(".")) {
|
||||
StringList nameList = name.split(".", 1);
|
||||
if (auto child = m_memberHash.value(nameList[0], {}))
|
||||
return child->fetchChild(nameList[1]);
|
||||
} else {
|
||||
if (auto child = m_memberHash.value(name, {}))
|
||||
return child;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
WidgetPtr Widget::findChild(String const& name) {
|
||||
if (auto found = fetchChild(name))
|
||||
return found;
|
||||
for (auto const& child : m_members) {
|
||||
if (auto found = child->findChild(name))
|
||||
return found;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
WidgetPtr Widget::childPtr(Widget const* child) const {
|
||||
for (auto const& m : m_members) {
|
||||
if (m.get() == child)
|
||||
return m;
|
||||
if (auto c = m->childPtr(child))
|
||||
return c;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
WidgetPtr Widget::getChildAt(Vec2I const& pos) {
|
||||
for (auto child : reverseIterate(m_members)) {
|
||||
if (child->inMember(pos)) {
|
||||
auto res = child->getChildAt(pos);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t Widget::numChildren() const {
|
||||
return m_members.size();
|
||||
}
|
||||
|
||||
WidgetPtr Widget::getChildNum(size_t num) const {
|
||||
return m_members.at(num);
|
||||
}
|
||||
|
||||
String const& Widget::name() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void Widget::setName(String const& name) {
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
String Widget::fullName() const {
|
||||
if (m_parent) {
|
||||
return m_parent->fullName() + "." + name();
|
||||
}
|
||||
|
||||
return name();
|
||||
}
|
||||
|
||||
String Widget::toStringImpl(int indentLevel) const {
|
||||
auto leader = String(" ") * indentLevel;
|
||||
String childrenString;
|
||||
for (auto child : m_members) {
|
||||
childrenString.append(child->toStringImpl(indentLevel + 4));
|
||||
}
|
||||
String output = strf(R"OUTPUT(%s%s : {
|
||||
%s address : %p,
|
||||
%s visible : %s,
|
||||
%s position : %s,
|
||||
%s size : %s,
|
||||
%s children : {
|
||||
%s
|
||||
%s }
|
||||
%s}
|
||||
)OUTPUT",
|
||||
leader,
|
||||
m_name,
|
||||
leader,
|
||||
this,
|
||||
leader,
|
||||
m_visible ? "true" : "false",
|
||||
leader,
|
||||
m_position,
|
||||
leader,
|
||||
m_size,
|
||||
leader,
|
||||
childrenString,
|
||||
leader,
|
||||
leader);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
bool Widget::setLabel(String const& name, String const& value) {
|
||||
if (containsChild(name)) {
|
||||
auto child = fetchChild(name);
|
||||
if (auto label = as<LabelWidget>(child)) {
|
||||
label->setText(value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Widget const& widget) {
|
||||
os << widget.toStringImpl(0);
|
||||
return os;
|
||||
}
|
||||
|
||||
}
|
178
source/windowing/StarWidget.hpp
Normal file
178
source/windowing/StarWidget.hpp
Normal file
|
@ -0,0 +1,178 @@
|
|||
#ifndef STAR_WIDGET_HPP
|
||||
#define STAR_WIDGET_HPP
|
||||
|
||||
#include "StarVector.hpp"
|
||||
#include "StarCasting.hpp"
|
||||
#include "StarInputEvent.hpp"
|
||||
#include "StarGuiContext.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_EXCEPTION(GuiException, StarException);
|
||||
|
||||
STAR_CLASS(Widget);
|
||||
STAR_CLASS(Pane);
|
||||
|
||||
enum class KeyboardCaptureMode {
|
||||
None,
|
||||
KeyEvents,
|
||||
TextInput
|
||||
};
|
||||
|
||||
typedef function<void(Widget*)> WidgetCallbackFunc;
|
||||
|
||||
class Widget {
|
||||
public:
|
||||
Widget();
|
||||
virtual ~Widget();
|
||||
|
||||
Widget(Widget const& copy) = delete;
|
||||
Widget& operator=(Widget const&) = delete;
|
||||
|
||||
virtual void render(RectI const& region) final;
|
||||
virtual void update();
|
||||
|
||||
GuiContext* context() const;
|
||||
|
||||
// Position of widget with drawing offset (useful for drawing)
|
||||
virtual Vec2I position() const;
|
||||
// Position of widget ignoring offset (useful for moving and placing widgets
|
||||
// relative to its current position)
|
||||
virtual Vec2I relativePosition() const;
|
||||
// Set position of widget, ignoring offset
|
||||
virtual void setPosition(Vec2I const& position);
|
||||
|
||||
virtual Vec2I drawingOffset() const;
|
||||
virtual void setDrawingOffset(Vec2I const& offset);
|
||||
|
||||
virtual Vec2I size() const;
|
||||
virtual void setSize(Vec2I const& size);
|
||||
|
||||
virtual RectI relativeBoundRect() const;
|
||||
virtual RectI screenBoundRect() const;
|
||||
|
||||
virtual bool inMember(Vec2I const& position) const;
|
||||
|
||||
virtual bool sendEvent(InputEvent const& event);
|
||||
|
||||
virtual void show();
|
||||
virtual void hide();
|
||||
virtual void toggleVisibility();
|
||||
virtual void setVisibility(bool visibility);
|
||||
|
||||
virtual void mouseOver();
|
||||
virtual void mouseOut();
|
||||
virtual void mouseReturnStillDown();
|
||||
virtual void setMouseTransparent(bool transparent);
|
||||
virtual bool mouseTransparent();
|
||||
|
||||
virtual bool active() const;
|
||||
|
||||
virtual bool interactive() const;
|
||||
|
||||
virtual bool hasFocus() const;
|
||||
virtual void focus();
|
||||
virtual void blur();
|
||||
|
||||
virtual Widget* parent() const;
|
||||
virtual void setParent(Widget* parent);
|
||||
|
||||
virtual Pane const* window() const;
|
||||
virtual Pane* window();
|
||||
|
||||
virtual void addChild(String const& name, WidgetPtr member);
|
||||
virtual void addChildAt(String const& name, WidgetPtr member, size_t at);
|
||||
virtual bool removeChild(Widget* member);
|
||||
virtual bool removeChild(String const& name);
|
||||
virtual bool removeChildAt(size_t at);
|
||||
virtual WidgetPtr getChildAt(Vec2I const& pos);
|
||||
virtual bool containsChild(String const& name);
|
||||
virtual WidgetPtr fetchChild(String const& name);
|
||||
template <typename WidgetType>
|
||||
shared_ptr<WidgetType> fetchChild(String const& name);
|
||||
|
||||
virtual WidgetPtr findChild(String const& name);
|
||||
template <typename WidgetType>
|
||||
shared_ptr<WidgetType> findChild(String const& name);
|
||||
|
||||
WidgetPtr childPtr(Widget const* widget) const;
|
||||
|
||||
virtual size_t numChildren() const;
|
||||
virtual WidgetPtr getChildNum(size_t num) const;
|
||||
template <typename WidgetType>
|
||||
shared_ptr<WidgetType> getChildNum(size_t num) const;
|
||||
virtual void removeAllChildren();
|
||||
|
||||
virtual String const& name() const;
|
||||
virtual void setName(String const& name);
|
||||
String fullName() const;
|
||||
|
||||
unsigned windowHeight() const;
|
||||
unsigned windowWidth() const;
|
||||
Vec2I windowSize() const;
|
||||
virtual Vec2I screenPosition() const;
|
||||
void disableScissoring();
|
||||
void enableScissoring();
|
||||
void determineSizeFromChildren();
|
||||
void markAsContainer();
|
||||
|
||||
virtual KeyboardCaptureMode keyboardCaptured() const;
|
||||
|
||||
void setData(Json const& data);
|
||||
Json const& data();
|
||||
|
||||
bool setLabel(String const& name, String const& value);
|
||||
|
||||
protected:
|
||||
friend std::ostream& operator<<(std::ostream& os, Widget const& widget);
|
||||
String toStringImpl(int indentLevel) const;
|
||||
|
||||
virtual void renderImpl();
|
||||
virtual void drawChildren();
|
||||
bool setupDrawRegion(RectI const& region);
|
||||
virtual RectI getScissorRect() const;
|
||||
virtual RectI noScissor() const;
|
||||
|
||||
Widget* m_parent;
|
||||
|
||||
GuiContext* m_context;
|
||||
|
||||
bool m_visible;
|
||||
PolyF m_boundPoly;
|
||||
|
||||
Vec2I m_position;
|
||||
Vec2I m_size;
|
||||
RectI m_drawingArea;
|
||||
Vec2I m_drawingOffset;
|
||||
String m_name;
|
||||
List<WidgetPtr> m_members;
|
||||
StringMap<WidgetPtr> m_memberHash;
|
||||
Vec2I m_memberSize;
|
||||
bool m_focus;
|
||||
bool m_doScissor;
|
||||
bool m_container;
|
||||
bool m_mouseTransparent;
|
||||
|
||||
Json m_data;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Widget const& widget);
|
||||
|
||||
template <typename WidgetType>
|
||||
shared_ptr<WidgetType> Widget::getChildNum(size_t num) const {
|
||||
return as<WidgetType>(getChildNum(num));
|
||||
}
|
||||
|
||||
template <typename WidgetType>
|
||||
shared_ptr<WidgetType> Widget::fetchChild(String const& name) {
|
||||
return as<WidgetType>(fetchChild(name));
|
||||
}
|
||||
|
||||
template <typename WidgetType>
|
||||
shared_ptr<WidgetType> Widget::findChild(String const& name) {
|
||||
return as<WidgetType>(findChild(name));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
815
source/windowing/StarWidgetParsing.cpp
Normal file
815
source/windowing/StarWidgetParsing.cpp
Normal file
|
@ -0,0 +1,815 @@
|
|||
#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. %s", 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. %s", 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 %s", 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. %s", 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: %s", 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 %s", hAnchor));
|
||||
}
|
||||
|
||||
if (config.contains("fontSize"))
|
||||
button->setFontSize(config.getInt("fontSize"));
|
||||
|
||||
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: '%s.up'", name);
|
||||
if (!m_callbacks.contains(callback + ".down"))
|
||||
throw WidgetParserException::format("Failed to find spinner callback named: '%s.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. %s", outputException(e, false)));
|
||||
}
|
||||
|
||||
if (!m_callbacks.contains(callback))
|
||||
throw WidgetParserException::format("Failed to find callback named: '%s'", 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(strf("%d", button->buttonGroupId()), button);
|
||||
} catch (MapException const& e) {
|
||||
throw WidgetParserException(
|
||||
strf("Malformed gui json, missing a required value in the map. %s", 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")));
|
||||
|
||||
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: '%s'", 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 %s", hAnchor));
|
||||
}
|
||||
|
||||
if (config.contains("fontSize"))
|
||||
textbox->setFontSize(config.getInt("fontSize"));
|
||||
if (config.contains("color"))
|
||||
textbox->setColor(jsonToColor(config.get("color")));
|
||||
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"));
|
||||
|
||||
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;
|
||||
if (callback.equals("null"))
|
||||
rightClickCallback = callback;
|
||||
else
|
||||
rightClickCallback = callback + ".right";
|
||||
rightClickCallback = config.getString("rightClickCallback", rightClickCallback);
|
||||
|
||||
auto itemSlot = make_shared<ItemSlotWidget>(ItemPtr(), backingImage);
|
||||
|
||||
if (!m_callbacks.contains(callback))
|
||||
throw WidgetParserException::format("Failed to find itemSlot callback named: '%s'", callback);
|
||||
itemSlot->setCallback(m_callbacks.get(callback));
|
||||
|
||||
if (!m_callbacks.contains(rightClickCallback))
|
||||
throw WidgetParserException::format("Failed to find itemslot rightClickCallback named: '%s'", rightClickCallback);
|
||||
|
||||
itemSlot->setRightClickCallback(m_callbacks.get(rightClickCallback));
|
||||
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. %s", outputException(e, false));
|
||||
}
|
||||
|
||||
String backingImage = config.getString("backingImage", "");
|
||||
String callback = config.getString("callback", name);
|
||||
String rightClickCallback;
|
||||
if (callback.equals("null"))
|
||||
rightClickCallback = callback;
|
||||
else
|
||||
rightClickCallback = callback + ".right";
|
||||
rightClickCallback = config.getString("rightClickCallback", rightClickCallback);
|
||||
|
||||
unsigned slotOffset = config.getInt("slotOffset", 0);
|
||||
|
||||
auto itemGrid = make_shared<ItemGridWidget>(ItemBagConstPtr(), dimensions, rowSpacing, columnSpacing, backingImage, slotOffset);
|
||||
|
||||
if (!m_callbacks.contains(callback))
|
||||
throw WidgetParserException::format("Failed to find itemgrid callback named: '%s'", callback);
|
||||
|
||||
itemGrid->setCallback(m_callbacks.get(callback));
|
||||
|
||||
itemGrid->setBackingImageAffinity(
|
||||
config.getBool("showBackingImageWhenFull", false), config.getBool("showBackingImageWhenEmpty", true));
|
||||
itemGrid->showDurability(config.getBool("showDurability", false));
|
||||
|
||||
if (!m_callbacks.contains(rightClickCallback))
|
||||
throw WidgetParserException::format("Failed to find itemgrid rightClickCallback named: '%s'", rightClickCallback);
|
||||
|
||||
itemGrid->setRightClickCallback(m_callbacks.get(rightClickCallback));
|
||||
|
||||
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. %s", 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. %s", 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: '%s'", 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. %s", 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: %s.", 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 \"%s\". Options are \"basic\", \"flow\", \"vertical\".", type));
|
||||
}
|
||||
common(widget, config);
|
||||
if (config.contains("children"))
|
||||
constructImpl(config.get("children"), widget.get());
|
||||
widget->update();
|
||||
|
||||
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(strf("%d", 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: %s"));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
76
source/windowing/StarWidgetParsing.hpp
Normal file
76
source/windowing/StarWidgetParsing.hpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#ifndef STAR_PANE_OBJECT_PARSING_HPP
|
||||
#define STAR_PANE_OBJECT_PARSING_HPP
|
||||
|
||||
#include "StarWidget.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(Widget);
|
||||
STAR_CLASS(Pane);
|
||||
|
||||
STAR_EXCEPTION(WidgetParserException, StarException);
|
||||
|
||||
struct WidgetConstructResult {
|
||||
WidgetConstructResult();
|
||||
WidgetConstructResult(WidgetPtr obj, String const& name, float zlevel);
|
||||
|
||||
WidgetPtr obj;
|
||||
String name;
|
||||
float zlevel;
|
||||
};
|
||||
|
||||
typedef std::function<WidgetConstructResult(String const& name, Json const& config)> ConstuctorFunc;
|
||||
|
||||
class WidgetParser {
|
||||
public:
|
||||
WidgetParser();
|
||||
virtual ~WidgetParser() {}
|
||||
|
||||
virtual void construct(Json const& config, Widget* widget = nullptr);
|
||||
void registerCallback(String const& name, WidgetCallbackFunc callback);
|
||||
WidgetPtr makeSingle(String const& name, Json const& config);
|
||||
|
||||
protected:
|
||||
void constructImpl(Json const& config, Widget* widget);
|
||||
List<WidgetConstructResult> constructor(Json const& config);
|
||||
|
||||
// Parents
|
||||
WidgetConstructResult stackHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult scrollAreaHandler(String const& name, Json const& config);
|
||||
|
||||
// Interactive
|
||||
WidgetConstructResult radioGroupHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult buttonHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult spinnerHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult textboxHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult itemSlotHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult itemGridHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult listHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult sliderHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult largeCharPlateHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult tabSetHandler(String const& name, Json const& config);
|
||||
|
||||
// Non-interactive
|
||||
WidgetConstructResult widgetHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult imageHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult imageStretchHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult portraitHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult labelHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult canvasHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult fuelGaugeHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult progressHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult containerHandler(String const& name, Json const& config);
|
||||
WidgetConstructResult layoutHandler(String const& name, Json const& config);
|
||||
|
||||
// Utilities
|
||||
void common(WidgetPtr widget, Json const& config);
|
||||
ImageStretchSet parseImageStretchSet(Json const& config);
|
||||
|
||||
Pane* m_pane;
|
||||
StringMap<ConstuctorFunc> m_constructors;
|
||||
StringMap<WidgetCallbackFunc> m_callbacks;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue