From daed74733b270dc0c11e45616df6263a7b5e297d Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 17 May 2026 23:10:42 -0400 Subject: [PATCH] Extract ChoiceButton + Toggle from bar settings into qs.Ui --- .../omarchy-shell/Ui/ChoiceButton.qml | 66 ++++++++ .../quickshell/omarchy-shell/Ui/Toggle.qml | 118 +++++++++++++ default/quickshell/omarchy-shell/Ui/qmldir | 2 + .../plugins/settings/SettingsPanel.qml | 155 ++---------------- 4 files changed, 196 insertions(+), 145 deletions(-) create mode 100644 default/quickshell/omarchy-shell/Ui/ChoiceButton.qml create mode 100644 default/quickshell/omarchy-shell/Ui/Toggle.qml diff --git a/default/quickshell/omarchy-shell/Ui/ChoiceButton.qml b/default/quickshell/omarchy-shell/Ui/ChoiceButton.qml new file mode 100644 index 00000000..a2ac37e9 --- /dev/null +++ b/default/quickshell/omarchy-shell/Ui/ChoiceButton.qml @@ -0,0 +1,66 @@ +import QtQuick +import qs.Commons + +// A single button in a mutually-exclusive choice group (a Row of these +// makes a "segmented control"). Distinct from PillButton because it has +// a real `selected` state semantic — used for picking between options +// (bar position: top/bottom/left/right), not for momentary actions. +// +// Selected styling uses the accent fill+border; focus styling uses the +// Style.focusBorderColor outline so keyboard nav can land on a non-selected +// option without it reading as the chosen one. This separation matters in +// the settings panel and any future "pick one" UI. +Rectangle { + id: root + + property string text: "" + property bool selected: false + + property color foreground: Color.foreground + property color background: Color.background + property color accent: Color.accent + property string fontFamily: "monospace" + property real fontSize: 12 + + signal clicked() + + activeFocusOnTab: true + Keys.onReturnPressed: root.clicked() + Keys.onEnterPressed: root.clicked() + Keys.onSpacePressed: root.clicked() + + implicitWidth: Math.max(56, label.implicitWidth + 22) + implicitHeight: 28 + radius: Style.cornerRadius + + color: selected + ? Qt.rgba(accent.r, accent.g, accent.b, 0.18) + : (mouse.containsMouse ? Style.hotFill : background) + border.color: selected + ? accent + : (activeFocus ? foreground : Qt.rgba(foreground.r, foreground.g, foreground.b, 0.4)) + border.width: selected ? 2 : (activeFocus ? 2 : 1) + + Behavior on color { ColorAnimation { duration: 100 } } + + Text { + id: label + anchors.centerIn: parent + text: root.text + color: root.selected ? root.accent : root.foreground + font.family: root.fontFamily + font.pixelSize: root.fontSize + font.bold: root.selected + } + + MouseArea { + id: mouse + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + root.forceActiveFocus() + root.clicked() + } + } +} diff --git a/default/quickshell/omarchy-shell/Ui/Toggle.qml b/default/quickshell/omarchy-shell/Ui/Toggle.qml new file mode 100644 index 00000000..443dac33 --- /dev/null +++ b/default/quickshell/omarchy-shell/Ui/Toggle.qml @@ -0,0 +1,118 @@ +import QtQuick +import qs.Commons + +// Labeled toggle row: title + optional description on the left, a switch +// on the right. Clicking anywhere on the row emits `clicked()`; consumers +// flip `checked` in response (the component is stateless about the actual +// value so it composes cleanly with model-driven UI). +// +// Focus styling follows the shared Style tokens (accent border + tinted +// fill on activeFocus) so keyboard nav looks the same here as on +// ChoiceButton and other focusable Ui components. +Rectangle { + id: root + + property string label: "" + property string description: "" + property bool checked: false + + property color foreground: Color.foreground + property color accent: Color.accent + property string fontFamily: "monospace" + property real titleSize: 13 + property real descriptionSize: 10 + + signal clicked() + + activeFocusOnTab: true + Keys.onReturnPressed: root.clicked() + Keys.onEnterPressed: root.clicked() + Keys.onSpacePressed: root.clicked() + + implicitHeight: Math.max(54, content.implicitHeight + 18) + implicitWidth: 240 + radius: Style.cornerRadius + + color: activeFocus + ? Style.focusFillColor + : (mouse.containsMouse ? Style.hotFill : Qt.rgba(foreground.r, foreground.g, foreground.b, 0.03)) + border.color: activeFocus + ? Style.focusBorderColor + : Qt.rgba(foreground.r, foreground.g, foreground.b, 0.12) + border.width: activeFocus ? Style.focusBorderWidth : 1 + + Behavior on color { ColorAnimation { duration: 100 } } + + Row { + id: content + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 12 + anchors.rightMargin: 12 + spacing: 12 + + Column { + width: parent.width - track.width - parent.spacing + spacing: 3 + anchors.verticalCenter: parent.verticalCenter + + Text { + text: root.label + color: root.foreground + font.family: root.fontFamily + font.pixelSize: root.titleSize + font.bold: true + elide: Text.ElideRight + width: parent.width + } + + Text { + visible: root.description !== "" + text: root.description + color: Qt.darker(root.foreground, 1.5) + font.family: root.fontFamily + font.pixelSize: root.descriptionSize + wrapMode: Text.WordWrap + width: parent.width + } + } + + Rectangle { + id: track + width: 42 + height: 22 + radius: height / 2 + color: root.checked + ? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.35) + : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) + border.color: root.checked + ? root.accent + : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.28) + border.width: 1 + anchors.verticalCenter: parent.verticalCenter + + Behavior on color { ColorAnimation { duration: 120 } } + + Rectangle { + width: 16 + height: 16 + radius: 8 + x: root.checked ? track.width - width - 3 : 3 + y: 3 + color: root.checked ? root.accent : Qt.darker(root.foreground, 1.25) + + Behavior on x { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } } + Behavior on color { ColorAnimation { duration: 120 } } + } + } + } + + MouseArea { + id: mouse + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: root.clicked() + } +} diff --git a/default/quickshell/omarchy-shell/Ui/qmldir b/default/quickshell/omarchy-shell/Ui/qmldir index 611f7cb1..a221b5cc 100644 --- a/default/quickshell/omarchy-shell/Ui/qmldir +++ b/default/quickshell/omarchy-shell/Ui/qmldir @@ -1,5 +1,6 @@ module qs.Ui +ChoiceButton 1.0 ChoiceButton.qml CursorSurface 1.0 CursorSurface.qml KeyboardPanel 1.0 KeyboardPanel.qml PanelActionButton 1.0 PanelActionButton.qml @@ -9,4 +10,5 @@ PanelSlider 1.0 PanelSlider.qml PanelToolTip 1.0 PanelToolTip.qml PillButton 1.0 PillButton.qml PopupCard 1.0 PopupCard.qml +Toggle 1.0 Toggle.qml WidgetButton 1.0 WidgetButton.qml diff --git a/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml b/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml index 79ba000e..74f3a088 100644 --- a/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml +++ b/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import qs.Commons +import qs.Ui import "../../ui/settings" as SettingsUi import "./components" as Cmp @@ -642,10 +643,13 @@ Item { } } - BarToggleRow { + Toggle { Layout.fillWidth: true label: "Transparent bar" description: "Hide the bar background so the wallpaper shows through." + foreground: root.foreground + accent: root.accent + fontFamily: root.fontFamily checked: root.draft.bar.transparent === true onClicked: { var next = root.cloneJson(root.draft) @@ -705,11 +709,15 @@ Item { Repeater { model: positionGroup.positions - delegate: PositionButton { + delegate: ChoiceButton { required property string modelData text: modelData selected: positionGroup.value === modelData + foreground: root.foreground + background: root.background + accent: root.accent + fontFamily: root.fontFamily onClicked: positionGroup.changed(modelData) } } @@ -717,149 +725,6 @@ Item { } } - component PositionButton: Rectangle { - id: positionButton - - property string text: "" - property bool selected: false - - signal clicked() - - activeFocusOnTab: true - Keys.onReturnPressed: positionButton.clicked() - Keys.onEnterPressed: positionButton.clicked() - Keys.onSpacePressed: positionButton.clicked() - - implicitWidth: Math.max(56, positionLabel.implicitWidth + 22) - implicitHeight: 28 - radius: root.cornerRadius - color: positionButton.selected - ? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.18) - : (positionArea.containsMouse ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.08) : root.background) - border.color: positionButton.selected - ? root.accent - : (positionButton.activeFocus ? root.foreground : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.4)) - border.width: positionButton.selected ? 2 : (positionButton.activeFocus ? 2 : 1) - - Behavior on color { ColorAnimation { duration: 100 } } - - Text { - id: positionLabel - anchors.centerIn: parent - text: positionButton.text - color: positionButton.selected ? root.accent : root.foreground - font.family: root.fontFamily - font.pixelSize: 12 - font.bold: positionButton.selected - } - - MouseArea { - id: positionArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - positionButton.forceActiveFocus() - positionButton.clicked() - } - } - } - - component BarToggleRow: Rectangle { - id: toggleRow - - property string label: "" - property string description: "" - property bool checked: false - - signal clicked() - - activeFocusOnTab: true - Keys.onReturnPressed: toggleRow.clicked() - Keys.onEnterPressed: toggleRow.clicked() - Keys.onSpacePressed: toggleRow.clicked() - - Layout.fillWidth: true - implicitHeight: Math.max(54, toggleContent.implicitHeight + 18) - radius: root.cornerRadius - color: toggleRow.activeFocus - ? root.focusFillColor - : (toggleArea.containsMouse ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.08) : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.03)) - border.color: toggleRow.activeFocus ? root.focusBorderColor : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) - border.width: toggleRow.activeFocus ? root.focusBorderWidth : 1 - - Behavior on color { ColorAnimation { duration: 100 } } - - Row { - id: toggleContent - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: 12 - spacing: 12 - - Column { - width: parent.width - switchTrack.width - parent.spacing - spacing: 3 - anchors.verticalCenter: parent.verticalCenter - - Text { - text: toggleRow.label - color: root.foreground - font.family: root.fontFamily - font.pixelSize: 13 - font.bold: true - elide: Text.ElideRight - width: parent.width - } - - Text { - text: toggleRow.description - color: Qt.darker(root.foreground, 1.5) - font.family: root.fontFamily - font.pixelSize: 10 - wrapMode: Text.WordWrap - width: parent.width - } - } - - Rectangle { - id: switchTrack - width: 42 - height: 22 - radius: height / 2 - color: toggleRow.checked - ? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.35) - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) - border.color: toggleRow.checked ? root.accent : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.28) - border.width: 1 - anchors.verticalCenter: parent.verticalCenter - - Behavior on color { ColorAnimation { duration: 120 } } - - Rectangle { - width: 16 - height: 16 - radius: 8 - x: toggleRow.checked ? switchTrack.width - width - 3 : 3 - y: 3 - color: toggleRow.checked ? root.accent : Qt.darker(root.foreground, 1.25) - - Behavior on x { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } } - Behavior on color { ColorAnimation { duration: 120 } } - } - } - } - - MouseArea { - id: toggleArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: toggleRow.clicked() - } - } - component ActionPill: Rectangle { id: pill property string text: ""