2023-06-20 04:33:09 +00:00
|
|
|
#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) {
|
2023-06-26 18:48:27 +00:00
|
|
|
if (!m_displayedPanes[paneLayer].insertFront(pane, move(onDismiss)).second)
|
2023-06-20 04:33:09 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-07-28 16:12:03 +00:00
|
|
|
void PaneManager::dismissWhere(function<bool(PanePtr const&)> func) {
|
|
|
|
if (!func)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (auto& layerPair : m_displayedPanes) {
|
|
|
|
eraseWhere(layerPair.second, [&](auto& panePair) {
|
|
|
|
if (func(panePair.first)) {
|
|
|
|
panePair.first->dismissed();
|
|
|
|
if (panePair.second)
|
|
|
|
panePair.second(panePair.first);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2023-07-20 14:58:49 +00:00
|
|
|
void PaneManager::update(float dt) {
|
2023-06-20 04:33:09 +00:00
|
|
|
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)) {
|
2023-07-20 14:58:49 +00:00
|
|
|
panePair.first->tick(dt);
|
2023-06-20 04:33:09 +00:00
|
|
|
if (panePair.first->active())
|
2023-07-20 14:58:49 +00:00
|
|
|
panePair.first->update(dt);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|