left sidebar: pinning, single instance

This commit is contained in:
end-4
2025-05-17 23:42:16 +02:00
parent e66606170b
commit 01cb51d6b4
@@ -17,174 +17,172 @@ Scope { // Scope
property int sidebarPadding: 15 property int sidebarPadding: 15
property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}] property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}]
Variants { // Window repeater PanelWindow { // Window
id: sidebarVariants id: sidebarRoot
model: Quickshell.screens visible: false
focusable: true
property int selectedTab: PersistentStates.sidebar.leftSide.selectedTab
property bool extend: false
property bool pin: false
property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth
PanelWindow { // Window onVisibleChanged: {
id: sidebarRoot GlobalStates.sidebarLeftOpenCount += visible ? 1 : -1
visible: false }
focusable: true
property int selectedTab: PersistentStates.sidebar.leftSide.selectedTab
property bool extend: false
property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth
onVisibleChanged: { exclusiveZone: pin ? sidebarWidth : 0
GlobalStates.sidebarLeftOpenCount += visible ? 1 : -1 implicitWidth: Appearance.sizes.sidebarWidthExtended
WlrLayershell.namespace: "quickshell:sidebarLeft"
// Hyprland 0.49: OnDemand is Exclusive, Exclusive just breaks click-outside-to-close
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent"
anchors {
top: true
left: true
bottom: true
}
mask: Region {
item: sidebarLeftBackground
}
HyprlandFocusGrab { // Click outside to close
id: grab
windows: [ sidebarRoot ]
active: false
onCleared: () => {
if (!active) sidebarRoot.visible = false
} }
}
property var modelData Connections {
target: sidebarRoot
screen: modelData function onVisibleChanged() {
exclusiveZone: 0 delayedGrabTimer.start()
implicitWidth: Appearance.sizes.sidebarWidthExtended swipeView.children[0].children[0].children[sidebarRoot.selectedTab].forceActiveFocus()
WlrLayershell.namespace: "quickshell:sidebarLeft"
// Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
anchors {
top: true
left: true
bottom: true
} }
function onPinChanged() {
mask: Region { grab.active = !sidebarRoot.pin
item: sidebarLeftBackground
} }
}
HyprlandFocusGrab { // Click outside to close Timer {
id: grab id: delayedGrabTimer
windows: [ sidebarRoot ] interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
active: false repeat: false
onCleared: () => { onTriggered: {
if (!active) sidebarRoot.visible = false grab.active = sidebarRoot.visible && !sidebarRoot.pin
}
}
// Background
Rectangle {
id: sidebarLeftBackground
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: Appearance.sizes.hyprlandGapsOut
anchors.leftMargin: Appearance.sizes.hyprlandGapsOut
width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
focus: sidebarRoot.visible
Behavior on width {
NumberAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
} }
} }
Connections { Keys.onPressed: (event) => {
target: sidebarRoot // console.log("Key pressed: " + event.key)
function onVisibleChanged() { if (event.key === Qt.Key_Escape) {
delayedGrabTimer.start() sidebarRoot.visible = false;
swipeView.children[0].children[0].children[sidebarRoot.selectedTab].forceActiveFocus() }
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", Math.min(sidebarRoot.selectedTab + 1, root.tabButtonList.length - 1))
}
else if (event.key === Qt.Key_PageUp) {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", Math.max(sidebarRoot.selectedTab - 1, 0))
}
else if (event.key === Qt.Key_Tab) {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", (sidebarRoot.selectedTab + 1) % root.tabButtonList.length);
}
else if (event.key === Qt.Key_Backtab) {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", (sidebarRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length);
}
else if (event.key === Qt.Key_O) {
sidebarRoot.extend = !sidebarRoot.extend;
}
else if (event.key === Qt.Key_P) {
sidebarRoot.pin = !sidebarRoot.pin;
}
event.accepted = true;
} }
} }
Timer { ColumnLayout {
id: delayedGrabTimer anchors.fill: parent
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay anchors.margins: sidebarPadding
repeat: false
onTriggered: { spacing: sidebarPadding
grab.active = sidebarRoot.visible
}
}
// Background PrimaryTabBar { // Tab strip
Rectangle { id: tabBar
id: sidebarLeftBackground tabButtonList: root.tabButtonList
externalTrackedTab: sidebarRoot.selectedTab
anchors.top: parent.top function onCurrentIndexChanged(currentIndex) {
anchors.left: parent.left PersistentStateManager.setState("sidebar.leftSide.selectedTab", currentIndex)
anchors.topMargin: Appearance.sizes.hyprlandGapsOut
anchors.leftMargin: Appearance.sizes.hyprlandGapsOut
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
focus: sidebarRoot.visible
Behavior on width {
NumberAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
} }
} }
Keys.onPressed: (event) => { SwipeView { // Content pages
// console.log("Key pressed: " + event.key) id: swipeView
if (event.key === Qt.Key_Escape) { Layout.topMargin: 5
sidebarRoot.visible = false; Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
currentIndex: sidebarRoot.selectedTab
onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true
PersistentStateManager.setState("sidebar.leftSide.selectedTab", currentIndex)
} }
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) { clip: true
PersistentStateManager.setState("sidebar.leftSide.selectedTab", Math.min(sidebarRoot.selectedTab + 1, root.tabButtonList.length - 1)) layer.enabled: true
} layer.effect: OpacityMask {
else if (event.key === Qt.Key_PageUp) { maskSource: Rectangle {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", Math.max(sidebarRoot.selectedTab - 1, 0)) width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
} }
else if (event.key === Qt.Key_Tab) { }
PersistentStateManager.setState("sidebar.leftSide.selectedTab", (sidebarRoot.selectedTab + 1) % root.tabButtonList.length);
} AiChat {
else if (event.key === Qt.Key_Backtab) { panelWindow: sidebarRoot
PersistentStateManager.setState("sidebar.leftSide.selectedTab", (sidebarRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length); }
} Anime {
else if (event.key === Qt.Key_O) { panelWindow: sidebarRoot
sidebarRoot.extend = !sidebarRoot.extend;
}
event.accepted = true;
} }
} }
ColumnLayout {
anchors.fill: parent
anchors.margins: sidebarPadding
spacing: sidebarPadding
PrimaryTabBar { // Tab strip
id: tabBar
tabButtonList: root.tabButtonList
externalTrackedTab: sidebarRoot.selectedTab
function onCurrentIndexChanged(currentIndex) {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", currentIndex)
}
}
SwipeView { // Content pages
id: swipeView
Layout.topMargin: 5
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
currentIndex: sidebarRoot.selectedTab
onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true
PersistentStateManager.setState("sidebar.leftSide.selectedTab", currentIndex)
}
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
AiChat {
panelWindow: sidebarRoot
}
Anime {
panelWindow: sidebarRoot
}
}
}
}
// Shadow
DropShadow {
anchors.fill: sidebarLeftBackground
horizontalOffset: 0
verticalOffset: 2
radius: Appearance.sizes.elevationMargin
samples: Appearance.sizes.elevationMargin * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs
color: Appearance.colors.colShadow
source: sidebarLeftBackground
} }
}
// Shadow
DropShadow {
anchors.fill: sidebarLeftBackground
horizontalOffset: 0
verticalOffset: 2
radius: Appearance.sizes.elevationMargin
samples: Appearance.sizes.elevationMargin * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs
color: Appearance.colors.colShadow
source: sidebarLeftBackground
} }
} }
@@ -193,32 +191,17 @@ Scope { // Scope
target: "sidebarLeft" target: "sidebarLeft"
function toggle(): void { function toggle(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarRoot.visible = !sidebarRoot.visible;
let panelWindow = sidebarVariants.instances[i]; if(sidebarRoot.visible) Notifications.timeoutAll();
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = !panelWindow.visible;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
function close(): void { function close(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarRoot.visible = false;
let panelWindow = sidebarVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = false;
}
}
} }
function open(): void { function open(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarRoot.visible = true;
let panelWindow = sidebarVariants.instances[i]; if(sidebarRoot.visible) Notifications.timeoutAll();
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = true;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
} }
@@ -227,13 +210,8 @@ Scope { // Scope
description: "Toggles left sidebar on press" description: "Toggles left sidebar on press"
onPressed: { onPressed: {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarRoot.visible = !sidebarRoot.visible;
let panelWindow = sidebarVariants.instances[i]; if(sidebarRoot.visible) Notifications.timeoutAll();
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = !panelWindow.visible;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
} }
@@ -242,13 +220,8 @@ Scope { // Scope
description: "Opens left sidebar on press" description: "Opens left sidebar on press"
onPressed: { onPressed: {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarRoot.visible = true;
let panelWindow = sidebarVariants.instances[i]; if(sidebarRoot.visible) Notifications.timeoutAll();
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = true;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
} }
@@ -257,12 +230,7 @@ Scope { // Scope
description: "Closes left sidebar on press" description: "Closes left sidebar on press"
onPressed: { onPressed: {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarRoot.visible = false;
let panelWindow = sidebarVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = false;
}
}
} }
} }