left sidebar foundations

This commit is contained in:
end-4 2025-04-27 18:58:06 +02:00
parent 0ef575b082
commit f2a9641a95
7 changed files with 300 additions and 71 deletions

View file

@ -8,6 +8,7 @@ pragma ComponentBehavior: Bound
Singleton {
id: root
property int sidebarLeftOpenCount: 0
property int sidebarRightOpenCount: 0
property bool overviewOpen: false
property bool workspaceShowNumbers: false

View file

@ -199,6 +199,11 @@ Scope {
barLeftSideMouseArea.hovered = false
barLeftSideMouseArea.trackingScroll = false
}
onPressed: (event) => {
if (event.button === Qt.LeftButton) {
openSidebarLeft.running = true
}
}
// Scroll to change brightness
WheelHandler {
onWheel: (event) => {

View file

@ -22,7 +22,7 @@ Item {
property list<bool> workspaceOccupied: []
property int widgetPadding: 4
property int workspaceButtonWidth: 26
property real workspaceIconSize: workspaceButtonWidth * 0.8
property real workspaceIconSize: workspaceButtonWidth * 0.75
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4

View file

@ -0,0 +1,86 @@
import "root:/modules/common"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
ColumnLayout {
id: root
spacing: 0
required property var tabButtonList // Something like [{"icon": "notifications", "name": qsTr("Notifications")}, {"icon": "volume_up", "name": qsTr("Volume mixer")}]
required property var externalTrackedTab
property bool enableIndicatorAnimation: false
signal currentIndexChanged(int index)
TabBar {
id: tabBar
Layout.fillWidth: true
currentIndex: root.externalTrackedTab
onCurrentIndexChanged: root.onCurrentIndexChanged(currentIndex)
background: Item {
WheelHandler {
onWheel: (event) => {
if (event.angleDelta.y < 0)
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
else if (event.angleDelta.y > 0)
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
}
Repeater {
model: root.tabButtonList
delegate: PrimaryTabButton {
selected: (index == root.externalTrackedTab)
buttonText: modelData.name
buttonIcon: modelData.icon
}
}
}
Item { // Tab indicator
id: tabIndicator
Layout.fillWidth: true
height: 3
Connections {
target: root
function onExternalTrackedTabChanged() {
root.enableIndicatorAnimation = true
}
}
Rectangle {
color: Appearance.m3colors.m3primary
radius: Appearance.rounding.full
z: 2
anchors.fill: parent
anchors.leftMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = root.width / tabCount;
return fullTabSize * root.externalTrackedTab + (fullTabSize - targetWidth) / 2;
}
anchors.rightMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = root.width / tabCount;
return fullTabSize * (tabCount - root.externalTrackedTab - 1) + (fullTabSize - targetWidth) / 2;
}
Behavior on anchors.leftMargin {
enabled: root.enableIndicatorAnimation
SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
}
}
Behavior on anchors.rightMargin {
enabled: root.enableIndicatorAnimation
SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
}
}
}
}
}

View file

@ -0,0 +1,196 @@
import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland
import Qt5Compat.GraphicalEffects
Scope { // Scope
id: root
property int sidebarWidth: Appearance.sizes.sidebarWidth
property int sidebarPadding: 15
property var tabButtonList: [{"icon": "neurology", "name": qsTr("LLMs")}, {"icon": "flare", "name": qsTr("Waifus")}]
Variants { // Window repeater
id: sidebarVariants
model: Quickshell.screens
PanelWindow { // Window
id: sidebarRoot
visible: false
focusable: true
property int currentTab: 0
onVisibleChanged: {
GlobalStates.sidebarLeftOpenCount += visible ? 1 : -1
}
property var modelData
screen: modelData
exclusiveZone: 0
width: sidebarWidth
WlrLayershell.namespace: "quickshell:sidebarLeft"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
anchors {
top: true
left: true
bottom: true
}
HyprlandFocusGrab { // Click outside to close
id: grab
windows: [ sidebarRoot ]
active: false
onCleared: () => {
if (!active) sidebarRoot.visible = false
}
}
Connections {
target: sidebarRoot
function onVisibleChanged() {
delayedGrabTimer.start()
}
}
Timer {
id: delayedGrabTimer
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
repeat: false
onTriggered: {
grab.active = sidebarRoot.visible
}
}
// Background
Rectangle {
id: sidebarLeftBackground
anchors.centerIn: parent
width: parent.width - Appearance.sizes.hyprlandGapsOut * 2
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
sidebarRoot.visible = false;
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: sidebarPadding
spacing: sidebarPadding
PrimaryTabBar { // Tab strip
id: tabBar
tabButtonList: root.tabButtonList
externalTrackedTab: sidebarRoot.currentTab
function onCurrentIndexChanged(currentIndex) {
sidebarRoot.currentTab = currentIndex
}
}
SwipeView { // Content pages
id: swipeView
Layout.topMargin: 5
Layout.fillWidth: true
Layout.fillHeight: true
currentIndex: currentTab
onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true
sidebarRoot.currentTab = currentIndex
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
Item {}
Item {}
}
}
}
// 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
}
}
}
IpcHandler {
target: "sidebarLeft"
function toggle(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) {
let panelWindow = sidebarVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = !panelWindow.visible;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
}
function close(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) {
let panelWindow = sidebarVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = false;
}
}
}
function open(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) {
let panelWindow = sidebarVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = true;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
}
}
GlobalShortcut {
name: "sidebarLeftToggle"
description: "Toggles left sidebar on press"
onPressed: {
for (let i = 0; i < sidebarVariants.instances.length; i++) {
let panelWindow = sidebarVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = !panelWindow.visible;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
}
}
}

View file

@ -35,76 +35,12 @@ Rectangle {
anchors.fill: parent
spacing: 0
TabBar {
PrimaryTabBar {
id: tabBar
Layout.fillWidth: true
currentIndex: currentTab
onCurrentIndexChanged: currentTab = currentIndex
background: Item {
WheelHandler {
onWheel: (event) => {
if (event.angleDelta.y < 0)
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
else if (event.angleDelta.y > 0)
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
}
Repeater {
model: root.tabButtonList
delegate: PrimaryTabButton {
selected: (index == currentTab)
buttonText: modelData.name
buttonIcon: modelData.icon
}
}
}
Item { // Tab indicator
id: tabIndicator
Layout.fillWidth: true
height: 3
property bool enableIndicatorAnimation: false
Connections {
target: root
function onCurrentTabChanged() {
tabIndicator.enableIndicatorAnimation = true
}
}
Rectangle {
color: Appearance.m3colors.m3primary
radius: Appearance.rounding.full
z: 2
anchors.fill: parent
anchors.leftMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = tabBar.width / tabCount;
return fullTabSize * currentTab + (fullTabSize - targetWidth) / 2;
}
anchors.rightMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = tabBar.width / tabCount;
return fullTabSize * (tabCount - currentTab - 1) + (fullTabSize - targetWidth) / 2;
}
Behavior on anchors.leftMargin {
enabled: tabIndicator.enableIndicatorAnimation
SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
}
}
Behavior on anchors.rightMargin {
enabled: tabIndicator.enableIndicatorAnimation
SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
}
}
tabButtonList: root.tabButtonList
externalTrackedTab: root.currentTab
function onCurrentIndexChanged(currentIndex) {
root.currentTab = currentIndex
}
}
@ -114,7 +50,10 @@ Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
currentIndex: currentTab
onCurrentIndexChanged: currentTab = currentIndex
onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true
root.currentTab = currentIndex
}
layer.enabled: true
layer.effect: OpacityMask {

View file

@ -6,6 +6,7 @@ import "./modules/onScreenDisplay/"
import "./modules/overview/"
import "./modules/screenCorners/"
import "./modules/session/"
import "./modules/sidebarLeft/"
import "./modules/sidebarRight/"
import QtQuick
import QtQuick.Controls
@ -27,6 +28,7 @@ ShellRoot {
ReloadPopup {}
ScreenCorners {}
Session {}
SidebarLeft {}
SidebarRight {}
}