Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/windowing/StarPaneManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/windowing/StarPaneManager.cpp')
-rw-r--r--source/windowing/StarPaneManager.cpp374
1 files changed, 374 insertions, 0 deletions
diff --git a/source/windowing/StarPaneManager.cpp b/source/windowing/StarPaneManager.cpp
new file mode 100644
index 0000000..9eab577
--- /dev/null
+++ b/source/windowing/StarPaneManager.cpp
@@ -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;
+}
+
+}