detachable sidebar

This commit is contained in:
end-4
2025-06-01 17:47:32 +02:00
parent a6a3a144d2
commit aaf652b17c
4 changed files with 146 additions and 75 deletions
@@ -16,7 +16,6 @@ import Quickshell.Hyprland
Item { Item {
id: root id: root
property var panelWindow
property var inputField: messageInputField property var inputField: messageInputField
property string commandPrefix: "/" property string commandPrefix: "/"
@@ -17,7 +17,6 @@ import Quickshell.Hyprland
Item { Item {
id: root id: root
property var panelWindow
property var inputField: tagInputField property var inputField: tagInputField
readonly property var responses: Booru.responses readonly property var responses: Booru.responses
property string previewDownloadPath: Directories.booruPreviews property string previewDownloadPath: Directories.booruPreviews
@@ -17,25 +17,50 @@ Scope { // Scope
id: root id: root
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")}]
property int selectedTab: 0
property bool pin: false
property Component contentComponent: SidebarLeftContent {}
property Item sidebarContent
Component.onCompleted: {
root.sidebarContent = contentComponent.createObject(null, {
"scopeRoot": root,
});
sidebarLoader.item.contentParent.children = [root.sidebarContent];
}
onPinChanged: {
console.log("Sidebar pin state changed:", root.pin);
if (root.pin) {
sidebarContent.parent = null; // Detach content from sidebar
sidebarLoader.active = false; // Unload sidebar
detachedSidebarLoader.active = true; // Load detached window
detachedSidebarLoader.item.contentParent.children = [sidebarContent];
} else {
sidebarContent.parent = null; // Detach content from window
detachedSidebarLoader.active = false; // Unload detached window
sidebarLoader.active = true; // Load sidebar
sidebarLoader.item.contentParent.children = [sidebarContent];
}
}
Loader { Loader {
id: sidebarLoader id: sidebarLoader
active: GlobalStates.sidebarLeftOpen active: true
PanelWindow { // Window sourceComponent: PanelWindow { // Window
id: sidebarRoot id: sidebarRoot
visible: GlobalStates.sidebarLeftOpen visible: GlobalStates.sidebarLeftOpen
property int selectedTab: 0
property bool extend: false property bool extend: false
property bool pin: false
property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth
property var contentParent: sidebarLeftBackground
function hide() { function hide() {
GlobalStates.sidebarLeftOpen = false GlobalStates.sidebarLeftOpen = false
} }
exclusiveZone: sidebarRoot.pin ? sidebarWidth : 0 exclusiveZone: 0
implicitWidth: Appearance.sizes.sidebarWidthExtended + Appearance.sizes.elevationMargin implicitWidth: Appearance.sizes.sidebarWidthExtended + Appearance.sizes.elevationMargin
WlrLayershell.namespace: "quickshell:sidebarLeft" WlrLayershell.namespace: "quickshell:sidebarLeft"
// Hyprland 0.49: OnDemand is Exclusive, Exclusive just breaks click-outside-to-close // Hyprland 0.49: OnDemand is Exclusive, Exclusive just breaks click-outside-to-close
@@ -55,22 +80,22 @@ Scope { // Scope
HyprlandFocusGrab { // Click outside to close HyprlandFocusGrab { // Click outside to close
id: grab id: grab
windows: [ sidebarRoot ] windows: [ sidebarRoot ]
active: sidebarRoot.visible && !sidebarRoot.pin active: sidebarRoot.visible
onActiveChanged: { // Focus the selected tab onActiveChanged: { // Focus the selected tab
if (active) swipeView.currentItem.forceActiveFocus() if (active) sidebarLeftBackground.children[0].focusActiveItem()
} }
onCleared: () => { onCleared: () => {
if (!active) sidebarRoot.hide() if (!active) sidebarRoot.hide()
} }
} }
// Background // Content
StyledRectangularShadow { StyledRectangularShadow {
target: sidebarLeftBackground target: sidebarLeftBackground
radius: sidebarLeftBackground.radius
} }
Rectangle { Rectangle {
id: sidebarLeftBackground id: sidebarLeftBackground
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: Appearance.sizes.hyprlandGapsOut anchors.topMargin: Appearance.sizes.hyprlandGapsOut
@@ -79,89 +104,48 @@ Scope { // Scope
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0 color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
focus: sidebarRoot.visible
Behavior on width {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
// console.log("Key pressed: " + event.key)
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
sidebarRoot.hide(); sidebarRoot.hide();
} }
if (event.modifiers === Qt.ControlModifier) { if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) { if (event.key === Qt.Key_O) {
sidebarRoot.selectedTab = Math.min(sidebarRoot.selectedTab + 1, root.tabButtonList.length - 1)
}
else if (event.key === Qt.Key_PageUp) {
sidebarRoot.selectedTab = Math.max(sidebarRoot.selectedTab - 1, 0)
}
else if (event.key === Qt.Key_Tab) {
sidebarRoot.selectedTab = (sidebarRoot.selectedTab + 1) % root.tabButtonList.length;
}
else if (event.key === Qt.Key_Backtab) {
sidebarRoot.selectedTab = (sidebarRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
}
else if (event.key === Qt.Key_O) {
sidebarRoot.extend = !sidebarRoot.extend; sidebarRoot.extend = !sidebarRoot.extend;
} }
else if (event.key === Qt.Key_P) { else if (event.key === Qt.Key_P) {
sidebarRoot.pin = !sidebarRoot.pin; root.pin = !root.pin;
} }
event.accepted = true; event.accepted = true;
} }
} }
}
}
}
ColumnLayout { Loader {
anchors.fill: parent id: detachedSidebarLoader
anchors.margins: sidebarPadding active: false
spacing: sidebarPadding sourceComponent: FloatingWindow {
id: detachedSidebarRoot
visible: GlobalStates.sidebarLeftOpen
property var contentParent: detachedSidebarBackground
PrimaryTabBar { // Tab strip Rectangle {
id: tabBar id: detachedSidebarBackground
tabButtonList: root.tabButtonList anchors.fill: parent
externalTrackedTab: sidebarRoot.selectedTab color: Appearance.colors.colLayer0
function onCurrentIndexChanged(currentIndex) {
sidebarRoot.selectedTab = currentIndex Keys.onPressed: (event) => {
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_P) {
root.pin = !root.pin;
} }
event.accepted = true;
} }
SwipeView { // Content pages
id: swipeView
Layout.topMargin: 5
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
currentIndex: tabBar.externalTrackedTab
onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true
sidebarRoot.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
}
}
} }
} }
} }
} }
@@ -0,0 +1,89 @@
import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Effects
import Qt5Compat.GraphicalEffects
import Quickshell.Io
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland
Item {
id: sidebarLeftBackground
required property var scopeRoot
anchors.fill: parent
function focusActiveItem() {
swipeView.currentItem.forceActiveFocus()
}
Keys.onPressed: (event) => {
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) {
scopeRoot.selectedTab = Math.min(scopeRoot.selectedTab + 1, scopeRoot.tabButtonList.length - 1)
event.accepted = true;
}
else if (event.key === Qt.Key_PageUp) {
scopeRoot.selectedTab = Math.max(scopeRoot.selectedTab - 1, 0)
event.accepted = true;
}
else if (event.key === Qt.Key_Tab) {
scopeRoot.selectedTab = (scopeRoot.selectedTab + 1) % scopeRoot.tabButtonList.length;
event.accepted = true;
}
else if (event.key === Qt.Key_Backtab) {
scopeRoot.selectedTab = (scopeRoot.selectedTab - 1 + scopeRoot.tabButtonList.length) % scopeRoot.tabButtonList.length;
event.accepted = true;
}
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: sidebarPadding
spacing: sidebarPadding
PrimaryTabBar { // Tab strip
id: tabBar
tabButtonList: scopeRoot.tabButtonList
externalTrackedTab: scopeRoot.selectedTab
function onCurrentIndexChanged(currentIndex) {
scopeRoot.selectedTab = currentIndex
}
}
SwipeView { // Content pages
id: swipeView
Layout.topMargin: 5
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
currentIndex: tabBar.externalTrackedTab
onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true
scopeRoot.selectedTab = currentIndex
}
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
AiChat {}
Anime {}
}
}
}