From 64e6f1e9343c43b7a2ae39163ba19a7f50647e76 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Wed, 3 Jun 2026 04:22:39 +1000 Subject: [PATCH] feat: allow scrolling nexus nav --- modules/nexus/NavPane.qml | 112 ++-------------------- modules/nexus/Nexus.qml | 7 +- modules/nexus/navpane/NavLocations.qml | 127 +++++++++++++++++++++++++ plugin/src/Caelestia/Config/tokens.hpp | 5 +- 4 files changed, 140 insertions(+), 111 deletions(-) create mode 100644 modules/nexus/navpane/NavLocations.qml diff --git a/modules/nexus/NavPane.qml b/modules/nexus/NavPane.qml index e8c47dcc..71f5d14c 100644 --- a/modules/nexus/NavPane.qml +++ b/modules/nexus/NavPane.qml @@ -1,11 +1,7 @@ -pragma ComponentBehavior: Bound - +import "navpane" import QtQuick import QtQuick.Layouts import Caelestia.Config -import qs.components -import qs.components.controls -import qs.services import qs.modules.nexus ColumnLayout { @@ -13,107 +9,11 @@ ColumnLayout { required property NexusState nState - spacing: Tokens.spacing.extraSmall + spacing: Tokens.spacing.large - Repeater { - id: list - - model: PageRegistry.pages - - StyledRect { - id: item - - required property var modelData - required property int index - - readonly property bool isCurrentPage: index === root.nState.currentPageIdx - readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1].category !== modelData.category - readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1].category !== modelData.category - - Layout.fillWidth: true - Layout.topMargin: index !== 0 && isCategoryStart ? Tokens.spacing.medium : 0 - implicitHeight: { - const h = layout.implicitHeight + layout.anchors.margins * 2; - return h % 2 === 0 ? h : h + 1; - } - - color: isCurrentPage ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainerHigh - - topLeftRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryStart ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall - topRightRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryStart ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall - bottomLeftRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryEnd ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall - bottomRightRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryEnd ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall - - RadiusBehavior on topLeftRadius {} - RadiusBehavior on topRightRadius {} - RadiusBehavior on bottomLeftRadius {} - RadiusBehavior on bottomRightRadius {} - - StateLayer { - id: stateLayer - - anchors.fill: parent - topLeftRadius: parent.topLeftRadius - topRightRadius: parent.topRightRadius - bottomLeftRadius: parent.bottomLeftRadius - bottomRightRadius: parent.bottomRightRadius - - onClicked: root.nState.currentPageIdx = item.index - } - - RowLayout { - id: layout - - anchors.fill: parent - anchors.margins: Tokens.padding.large - spacing: Tokens.spacing.medium - - StyledRect { - Layout.fillHeight: true - Layout.topMargin: -1 - Layout.bottomMargin: -1 - implicitWidth: height - - radius: Tokens.rounding.full - color: item.isCurrentPage ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer - - MaterialIcon { - anchors.centerIn: parent - anchors.verticalCenterOffset: 1 - - text: item.modelData.icon - color: item.isCurrentPage ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer - fontStyle: Tokens.font.icon.medium - fill: 1 - } - } - - ColumnLayout { - Layout.fillWidth: true - spacing: 0 //Tokens.spacing.extraSmall - - StyledText { - Layout.fillWidth: true - text: item.modelData.label - font: Tokens.font.title.small - elide: Text.ElideRight - } - - StyledText { - Layout.fillWidth: true - text: item.modelData.description - color: Colours.palette.m3onSurfaceVariant - font: Tokens.font.label.small - elide: Text.ElideRight - } - } - } - } - } - - component RadiusBehavior: Behavior { - Anim { - type: Anim.DefaultEffects - } + NavLocations { + Layout.fillWidth: true + Layout.fillHeight: true + nState: root.nState } } diff --git a/modules/nexus/Nexus.qml b/modules/nexus/Nexus.qml index 0a87f1f3..b9ae0d76 100644 --- a/modules/nexus/Nexus.qml +++ b/modules/nexus/Nexus.qml @@ -60,7 +60,8 @@ Item { inactiveOnColour: hovered ? nState.isWindow ? Colours.palette.m3error : Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant stateLayer.opacity: 0 onClicked: { - if (!nState.isWindow); // TODO: open floating window via factory + if (!nState.isWindow) + WindowFactory.create(); root.close(); } @@ -74,7 +75,7 @@ Item { } NavPane { - id: navPane // TODO + id: navPane anchors.top: parent.top anchors.bottom: parent.bottom @@ -82,6 +83,6 @@ Item { anchors.margins: Tokens.padding.large nState: nState - width: Math.round(root.width / 3) + width: Math.min(Tokens.sizes.nexus.maxNavWidth, Math.round(root.width / 3)) } } diff --git a/modules/nexus/navpane/NavLocations.qml b/modules/nexus/navpane/NavLocations.qml new file mode 100644 index 00000000..079ce087 --- /dev/null +++ b/modules/nexus/navpane/NavLocations.qml @@ -0,0 +1,127 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Caelestia.Config +import qs.components +import qs.components.containers +import qs.services +import qs.modules.nexus + +StyledFlickable { + id: root + + required property NexusState nState + + contentHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.left: parent.left + anchors.right: parent.right + spacing: Tokens.spacing.extraSmall + + Repeater { + id: list + + model: PageRegistry.pages + + StyledRect { + id: item + + required property var modelData + required property int index + + readonly property bool isCurrentPage: index === root.nState.currentPageIdx + readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1].category !== modelData.category + readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1].category !== modelData.category + + Layout.fillWidth: true + Layout.topMargin: index !== 0 && isCategoryStart ? Tokens.spacing.medium : 0 + implicitHeight: { + const h = layout.implicitHeight + layout.anchors.margins * 2; + return h % 2 === 0 ? h : h + 1; + } + + color: isCurrentPage ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainerHigh + + topLeftRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryStart ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall + topRightRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryStart ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall + bottomLeftRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryEnd ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall + bottomRightRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryEnd ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall + + RadiusBehavior on topLeftRadius {} + RadiusBehavior on topRightRadius {} + RadiusBehavior on bottomLeftRadius {} + RadiusBehavior on bottomRightRadius {} + + StateLayer { + id: stateLayer + + anchors.fill: parent + topLeftRadius: parent.topLeftRadius + topRightRadius: parent.topRightRadius + bottomLeftRadius: parent.bottomLeftRadius + bottomRightRadius: parent.bottomRightRadius + + onClicked: root.nState.currentPageIdx = item.index + } + + RowLayout { + id: layout + + anchors.fill: parent + anchors.margins: Tokens.padding.large + spacing: Tokens.spacing.medium + + StyledRect { + Layout.fillHeight: true + Layout.topMargin: -1 + Layout.bottomMargin: -1 + implicitWidth: height + + radius: Tokens.rounding.full + color: item.isCurrentPage ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer + + MaterialIcon { + anchors.centerIn: parent + anchors.verticalCenterOffset: 1 + + text: item.modelData.icon + color: item.isCurrentPage ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer + fontStyle: Tokens.font.icon.medium + fill: 1 + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 //Tokens.spacing.extraSmall + + StyledText { + Layout.fillWidth: true + text: item.modelData.label + font: Tokens.font.title.small + elide: Text.ElideRight + } + + StyledText { + Layout.fillWidth: true + text: item.modelData.description + color: Colours.palette.m3onSurfaceVariant + font: Tokens.font.label.small + elide: Text.ElideRight + } + } + } + } + } + } + + component RadiusBehavior: Behavior { + Anim { + type: Anim.DefaultEffects + } + } +} diff --git a/plugin/src/Caelestia/Config/tokens.hpp b/plugin/src/Caelestia/Config/tokens.hpp index 3657d43e..3d0267f4 100644 --- a/plugin/src/Caelestia/Config/tokens.hpp +++ b/plugin/src/Caelestia/Config/tokens.hpp @@ -329,8 +329,9 @@ class NexusTokens : public ConfigObject { CONFIG_PROPERTY(qreal, heightMult, 0.7) CONFIG_PROPERTY(qreal, ratio, 16.0 / 9.0) - CONFIG_PROPERTY(qreal, minWidth, 800) - CONFIG_PROPERTY(qreal, minHeight, 500) + CONFIG_PROPERTY(int, minWidth, 800) + CONFIG_PROPERTY(int, minHeight, 500) + CONFIG_PROPERTY(int, maxNavWidth, 600) public: explicit NexusTokens(QObject* parent = nullptr)