diff options
Diffstat (limited to 'source/windowing/StarPaneManager.cpp')
-rw-r--r-- | source/windowing/StarPaneManager.cpp | 374 |
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; +} + +} |