switches and popouts
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 25s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m11s

This commit is contained in:
2026-06-06 21:13:44 +02:00
parent 65c56bc598
commit 50e99501de
11 changed files with 566 additions and 519 deletions
+35 -64
View File
@@ -1,7 +1,6 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Helpers
Row {
id: root
@@ -12,62 +11,46 @@ Row {
}
property alias active: menu.active
property color color: type == CustomSplitButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer
property color colour: type == CustomSplitButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer
property bool disabled
property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color disabledTextColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color disabledTextColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
readonly property alias expandBtn: expandBtn
property alias expanded: menu.expanded
property string fallbackIcon
property string fallbackText
property real horizontalPadding: Appearance.padding.normal
property alias iconLabel: iconLabel
property alias label: label
property alias menu: menu
property real horizontalPadding: Appearance.padding.larger
readonly property alias iconLabel: iconLabel
readonly property alias label: label
readonly property alias menu: menu
property alias menuItems: menu.items
property bool menuOnTop
property alias stateLayer: stateLayer
property color textColor: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
property real minLeftWidth
readonly property alias stateLayer: stateLayer
property color textColour: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
readonly property alias textRow: textRow
property int type: CustomSplitButton.Filled
property real verticalPadding: Appearance.padding.smaller
property real verticalPadding: Appearance.padding.small
function closeDropdown(): void {
SettingsDropdowns.close(menu);
}
function openDropdown(): void {
SettingsDropdowns.open(menu, root);
}
function toggleDropdown(): void {
SettingsDropdowns.toggle(menu, root);
}
spacing: Math.floor(Appearance.spacing.small / 2)
onExpandedChanged: {
if (!expanded)
SettingsDropdowns.forget(menu);
}
spacing: Math.floor(Appearance.spacing.extraSmall)
CustomRect {
bottomRightRadius: Appearance.rounding.small / 2
color: !root.enabled ? root.disabledColor : root.color
color: root.disabled ? root.disabledColour : root.colour
implicitHeight: expandBtn.implicitHeight
implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2
implicitWidth: Math.max(root.minLeftWidth, textRow.implicitWidth + root.horizontalPadding * 2)
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
topRightRadius: Appearance.rounding.small / 2
StateLayer {
id: stateLayer
function onClicked(): void {
root.active?.clicked();
}
bottomRightRadius: parent.bottomRightRadius
color: root.textColour
disabled: root.disabled
topRightRadius: parent.topRightRadius
color: root.textColor
disabled: !root.enabled
rect.bottomRightRadius: parent.bottomRightRadius
rect.topRightRadius: parent.topRightRadius
onClicked: root.active?.clicked()
}
RowLayout {
@@ -82,7 +65,7 @@ Row {
Layout.alignment: Qt.AlignVCenter
animate: true
color: !root.enabled ? root.disabledTextColor : root.textColor
color: root.disabled ? root.disabledTextColour : root.textColour
fill: 1
text: root.active?.activeIcon ?? root.fallbackIcon
}
@@ -94,12 +77,12 @@ Row {
Layout.preferredWidth: implicitWidth
animate: true
clip: true
color: !root.enabled ? root.disabledTextColor : root.textColor
color: root.disabled ? root.disabledTextColour : root.textColour
text: root.active?.activeText ?? root.fallbackText
Behavior on Layout.preferredWidth {
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasized
type: Anim.Emphasized
}
}
}
@@ -112,7 +95,7 @@ Row {
property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) : Appearance.rounding.small / 2
bottomLeftRadius: rad
color: !root.enabled ? root.disabledColor : root.color
color: root.disabled ? root.disabledColour : root.colour
implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2
implicitWidth: implicitHeight
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
@@ -126,14 +109,12 @@ Row {
StateLayer {
id: expandStateLayer
function onClicked(): void {
root.toggleDropdown();
}
color: root.textColor
disabled: !root.enabled
color: root.textColour
disabled: root.disabled
rect.bottomLeftRadius: parent.bottomLeftRadius
rect.topLeftRadius: parent.topLeftRadius
onClicked: root.expanded = !root.expanded
}
MaterialIcon {
@@ -141,7 +122,7 @@ Row {
anchors.centerIn: parent
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
color: !root.enabled ? root.disabledTextColor : root.textColor
color: root.disabled ? root.disabledTextColour : root.textColour
rotation: root.expanded ? 180 : 0
text: "expand_more"
@@ -154,24 +135,14 @@ Row {
}
}
}
}
Menu {
id: menu
anchors.bottomMargin: Appearance.spacing.small
anchors.right: parent.right
anchors.top: parent.bottom
anchors.topMargin: Appearance.spacing.small
states: State {
when: root.menuOnTop
AnchorChanges {
anchors.bottom: expandBtn.top
anchors.top: undefined
target: menu
}
}
}
attachSideY: root.menuOnTop ? Menu.Top : Menu.Bottom
attachTo: expandBtn
marginY: Appearance.spacing.small * (root.menuOnTop ? -1 : 1)
thisSideY: root.menuOnTop ? Menu.Bottom : Menu.Top
}
}
+6 -6
View File
@@ -9,7 +9,6 @@ Item {
property alias active: splitButton.active
property alias buttonAlias: splitButton
property bool enabled: true
property alias expanded: splitButton.expanded
property int expandedZ: 100
required property string label
@@ -25,7 +24,7 @@ Item {
implicitHeight: row.implicitHeight + Appearance.padding.smaller * 2
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
z: root.expanded ? expandedZ : -1
z: splitButton.menu.implicitHeight > 0 ? expandedZ : 1
Behavior on opacity {
Anim {
@@ -50,7 +49,6 @@ Item {
color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger
text: root.label
z: root.expanded ? root.expandedZ : -1
}
CustomSplitButton {
@@ -58,14 +56,16 @@ Item {
enabled: root.enabled
type: CustomSplitButton.Filled
z: root.expanded ? root.expandedZ : -1
z: 2
menu.onItemSelected: item => {
root.selected(item);
splitButton.closeDropdown();
// splitButton.closeDropdown();
}
stateLayer.onClicked: {
splitButton.toggleDropdown();
// splitButton.toggleDropdown();
splitButton.expanded = !splitButton.expanded;
console.log(root.z);
}
}
}
+14 -12
View File
@@ -1,6 +1,6 @@
import QtQuick
import QtQuick.Templates
import QtQuick.Shapes
import QtQuick.Templates
import qs.Config
Switch {
@@ -13,26 +13,28 @@ Switch {
indicator: CustomRect {
color: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer)
implicitHeight: 13 + 7 * 2
implicitHeight: Appearance.font.size.medium + Appearance.padding.normal * 2
implicitWidth: implicitHeight * 1.7
radius: Appearance.rounding.full
CustomRect {
readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight
readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.2 : implicitHeight
anchors.verticalCenter: parent.verticalCenter
color: root.checked && root.enabled ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1)
implicitHeight: parent.implicitHeight - 10
implicitHeight: parent.implicitHeight - Appearance.padding.extraSmall
implicitWidth: nonAnimWidth
radius: Appearance.rounding.full
x: root.checked ? parent.implicitWidth - nonAnimWidth - 10 / 2 : 10 / 2
x: root.checked ? parent.implicitWidth - nonAnimWidth - Appearance.padding.extraSmall / 2 : Appearance.padding.extraSmall / 2
Behavior on implicitWidth {
Anim {
type: Anim.FastSpatial
}
}
Behavior on x {
Anim {
type: Anim.FastSpatial
}
}
@@ -44,6 +46,7 @@ Switch {
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
@@ -63,14 +66,14 @@ Switch {
}
property point end2: {
if (root.pressed)
return Qt.point(width, height / 2);
return Qt.point(width * 0.8, height / 2);
if (root.checked)
return Qt.point(width * 0.85, height * 0.2);
return Qt.point(width * 0.85, height * 0.15);
}
property point start1: {
if (root.pressed)
return Qt.point(width * 0.1, height / 2);
return Qt.point(width * 0.2, height / 2);
if (root.checked)
return Qt.point(width * 0.15, height / 2);
return Qt.point(width * 0.15, height * 0.15);
@@ -88,7 +91,7 @@ Switch {
anchors.centerIn: parent
asynchronous: true
height: parent.implicitHeight - Appearance.padding.small * 2
height: parent.implicitHeight - Appearance.padding.larger
preferredRendererType: Shape.CurveRenderer
width: height
@@ -110,7 +113,7 @@ Switch {
}
ShapePath {
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: icon.start1.x
startY: icon.start1.y
@@ -148,8 +151,7 @@ Switch {
}
component PropAnim: PropertyAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
+2 -1
View File
@@ -1,6 +1,6 @@
import qs.Config
import QtQuick
import QtQuick.Effects
import qs.Config
RectangularShadow {
property real dp: [0, 1, 3, 6, 8, 12][level]
@@ -13,6 +13,7 @@ RectangularShadow {
Behavior on dp {
Anim {
type: Anim.SlowEffects
}
}
}
+98 -38
View File
@@ -2,49 +2,107 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Config
import qs.Drawers
Elevation {
MouseArea {
id: root
enum Side {
Top,
Bottom,
Left,
Right
}
property MenuItem active: items[0] ?? null
property int attachSideX: Menu.Right
property int attachSideY: Menu.Bottom
required property Item attachTo
property bool expanded
property list<MenuItem> items
property real marginX
property real marginY
property int thisSideX: Menu.Right
property int thisSideY: Menu.Top
signal itemSelected(item: MenuItem)
implicitHeight: root.expanded ? column.implicitHeight + Appearance.padding.small * 2 : 0
implicitWidth: Math.max(200, column.implicitWidth)
level: 2
opacity: root.expanded ? 1 : 0
radius: Appearance.rounding.normal
anchors.fill: parent
enabled: expanded
layer.enabled: opacity < 1
opacity: expanded ? 1 : 0
parent: {
const win = QsWindow.window;
const contentWin = win as Windows;
return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem;
}
Behavior on implicitHeight {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
type: Anim.DefaultEffects
}
}
CustomClippingRect {
onClicked: expanded = false
TransformWatcher {
id: watcher
a: root.parent
b: root.attachTo
}
Elevation {
id: menu
implicitHeight: column.implicitHeight + column.anchors.margins * 2
implicitWidth: Math.max(200, column.implicitWidth + column.anchors.margins * 2)
level: 2
radius: Appearance.rounding.medium
x: {
watcher.transform;
const item = root.attachTo;
let off = root.attachSideX === Menu.Left ? 0 : item.width;
if (root.thisSideX === Menu.Right)
off -= width;
return item.mapToItem(root.parent, off, 0).x + root.marginX;
}
y: {
watcher.transform;
const item = root.attachTo;
let off = root.attachSideY === Menu.Top ? 0 : item.height;
if (root.thisSideY === Menu.Bottom)
off -= height;
return item.mapToItem(root.parent, 0, off).y + root.marginY;
}
transform: Scale {
origin.y: root.thisSideY === Menu.Bottom ? menu.height : 0
yScale: root.expanded ? 1 : 0.1
Behavior on yScale {
Anim {
}
}
}
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer
color: DynamicColors.palette.m3surfaceContainerLow
radius: parent.radius
ColumnLayout {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 5
anchors.fill: parent
anchors.margins: Appearance.padding.extraSmall
spacing: Appearance.spacing.extraSmall
Repeater {
id: repeater
model: root.items
CustomRect {
@@ -55,25 +113,25 @@ Elevation {
required property MenuItem modelData
Layout.fillWidth: true
implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.normal * 2
implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.normal * 2
color: Qt.alpha(DynamicColors.palette.m3tertiaryContainer, active ? 1 : 0)
implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.larger * 2
implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.larger * 2
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.small
anchors.rightMargin: Appearance.padding.small
color: Qt.alpha(DynamicColors.palette.m3secondaryContainer, active ? 1 : 0)
radius: Appearance.rounding.normal - Appearance.padding.small
StateLayer {
function onClicked(): void {
root.itemSelected(item.modelData);
root.active = item.modelData;
root.expanded = false;
Behavior on radius {
Anim {
}
}
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
StateLayer {
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurface
disabled: !root.expanded
onClicked: {
root.itemSelected(item.modelData);
root.active = item.modelData;
item.modelData.clicked();
root.expanded = false;
}
}
@@ -81,29 +139,30 @@ Elevation {
id: menuOptionRow
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.margins: Appearance.padding.larger
spacing: Appearance.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.icon
}
CustomText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.text
}
Loader {
Layout.alignment: Qt.AlignVCenter
active: item.modelData.trailingIcon.length > 0
asynchronous: true
visible: active
sourceComponent: MaterialIcon {
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.trailingIcon
}
}
@@ -112,4 +171,5 @@ Elevation {
}
}
}
}
}
+7 -3
View File
@@ -63,7 +63,8 @@ JsonObject {
component FontSize: JsonObject {
property int extraLarge: 28 * scale
property int large: 18 * scale
property int larger: 15 * scale
property int larger: 16 * scale
property int medium: 14 * scale
property int normal: 13 * scale
property real scale: 1
property int small: 11 * scale
@@ -76,9 +77,11 @@ JsonObject {
}
}
component Padding: JsonObject {
property int large: 15 * scale
property int extraLargeIncreased: 32 * scale
property int extraSmall: 4 * scale
property int large: 16 * scale
property int larger: 12 * scale
property int normal: 9 * scale
property int normal: 8 * scale
property real scale: 1
property int small: 5 * scale
property int smaller: 7 * scale
@@ -88,6 +91,7 @@ JsonObject {
property int extraSmall: 4 * scale
property int full: 1000 * scale
property int large: 24 * scale
property int medium: 16 * scale
property int normal: 18 * scale
property real scale: 1
property int small: 12 * scale
+25
View File
@@ -0,0 +1,25 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Variants {
model: Quickshell.screens
Scope {
id: scope
required property ShellScreen modelData
Exclusions {
bar: content.bar
screen: scope.modelData
}
Windows {
id: content
screen: scope.modelData
}
}
}
+1 -1
View File
@@ -59,7 +59,7 @@ Item {
cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
dragThreshold: 0
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfDifferentType | PointerHandler.ApprovesTakeOverByAnything
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything
maximumPointCount: 1
minimumPointCount: 1
target: null
+12 -27
View File
@@ -9,29 +9,17 @@ import Quickshell.Hyprland
import ZShell.Blobs
import qs.Daemons
import qs.Components
import qs.Modules
import qs.Modules.Bar
import qs.Config
import qs.Helpers
import qs.Drawers
Variants {
model: Quickshell.screens
Scope {
id: scope
required property var modelData
Exclusions {
bar: bar
screen: scope.modelData
}
CustomWindow {
id: win
CustomWindow {
id: root
readonly property alias bar: bar
readonly property bool hasFullscreen: Hypr.monitorFor(screen)?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2)
readonly property alias interactionWrapper: interactions
property var root: Quickshell.shellDir
WlrLayershell.exclusionMode: ExclusionMode.Ignore
@@ -40,7 +28,6 @@ Variants {
contentItem.focus: true
mask: visibilities.isDrawing ? null : region
name: "Bar"
screen: scope.modelData
contentItem.Keys.onEscapePressed: {
if (Config.barConfig.autoHide)
@@ -62,10 +49,10 @@ Variants {
Region {
id: region
height: win.height - bar.implicitHeight - Config.barConfig.border
height: root.height - bar.implicitHeight - Config.barConfig.border
intersection: Intersection.Xor
regions: popoutRegions.instances
width: win.width - Config.barConfig.border * 2
width: root.width - Config.barConfig.border * 2
x: Config.barConfig.border
y: bar.implicitHeight
}
@@ -97,7 +84,7 @@ Variants {
id: focusGrab
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
windows: [win]
windows: [root]
onCleared: {
visibilities.launcher = false;
@@ -125,7 +112,7 @@ Variants {
property bool settings
property bool sidebar
Component.onCompleted: Visibilities.load(scope.modelData, this)
Component.onCompleted: Visibilities.load(root.screen, this)
}
IpcHandler {
@@ -336,7 +323,7 @@ Variants {
}
Interactions {
id: mouseArea
id: interactions
anchors.fill: parent
bar: bar
@@ -345,7 +332,7 @@ Variants {
input: inputLoader.item
panels: panels
popouts: panels.popouts
screen: scope.modelData
screen: root.screen
visibilities: visibilities
z: 1
@@ -354,7 +341,7 @@ Variants {
bar: bar
drawingItem: drawingLoader.item
screen: scope.modelData
screen: root.screen
visibilities: visibilities
dashboard.transform: Matrix4x4 {
@@ -396,12 +383,10 @@ Variants {
anchors.right: parent.right
popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper
screen: scope.modelData
screen: root.screen
visibilities: visibilities
}
}
}
}
component PanelBg: BlobRect {
property real deformAmount: 0.15
+1 -2
View File
@@ -62,6 +62,7 @@ SettingsPage {
SettingsSection {
sectionId: "Color"
z: 1
SettingsHeader {
name: "Color"
@@ -105,7 +106,6 @@ SettingsPage {
active: root.schemeTypeItem(menuItems, Config.colors.schemeType)
enabled: Config.general.color.schemeGeneration
label: qsTr("Scheme type")
z: 2
menuItems: [
MenuItem {
@@ -250,7 +250,6 @@ SettingsPage {
SettingsSection {
sectionId: "Default Apps"
z: -1
SettingsHeader {
name: "Default Apps"
+1 -1
View File
@@ -22,7 +22,7 @@ ShellRoot {
settings.watchFiles: true
Windows {
Drawers {
}
Wallpaper {