forked from Shinonome/dots-hyprland
left sidebar foundations
This commit is contained in:
@@ -8,6 +8,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
property int sidebarLeftOpenCount: 0
|
||||||
property int sidebarRightOpenCount: 0
|
property int sidebarRightOpenCount: 0
|
||||||
property bool overviewOpen: false
|
property bool overviewOpen: false
|
||||||
property bool workspaceShowNumbers: false
|
property bool workspaceShowNumbers: false
|
||||||
|
|||||||
@@ -199,6 +199,11 @@ Scope {
|
|||||||
barLeftSideMouseArea.hovered = false
|
barLeftSideMouseArea.hovered = false
|
||||||
barLeftSideMouseArea.trackingScroll = false
|
barLeftSideMouseArea.trackingScroll = false
|
||||||
}
|
}
|
||||||
|
onPressed: (event) => {
|
||||||
|
if (event.button === Qt.LeftButton) {
|
||||||
|
openSidebarLeft.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
// Scroll to change brightness
|
// Scroll to change brightness
|
||||||
WheelHandler {
|
WheelHandler {
|
||||||
onWheel: (event) => {
|
onWheel: (event) => {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Item {
|
|||||||
property list<bool> workspaceOccupied: []
|
property list<bool> workspaceOccupied: []
|
||||||
property int widgetPadding: 4
|
property int widgetPadding: 4
|
||||||
property int workspaceButtonWidth: 26
|
property int workspaceButtonWidth: 26
|
||||||
property real workspaceIconSize: workspaceButtonWidth * 0.8
|
property real workspaceIconSize: workspaceButtonWidth * 0.75
|
||||||
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
|
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
|
||||||
property real workspaceIconOpacityShrinked: 1
|
property real workspaceIconOpacityShrinked: 1
|
||||||
property real workspaceIconMarginShrinked: -4
|
property real workspaceIconMarginShrinked: -4
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -35,76 +35,12 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
TabBar {
|
PrimaryTabBar {
|
||||||
id: tabBar
|
id: tabBar
|
||||||
Layout.fillWidth: true
|
tabButtonList: root.tabButtonList
|
||||||
currentIndex: currentTab
|
externalTrackedTab: root.currentTab
|
||||||
onCurrentIndexChanged: currentTab = currentIndex
|
function onCurrentIndexChanged(currentIndex) {
|
||||||
|
root.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +50,10 @@ Rectangle {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
currentIndex: currentTab
|
currentIndex: currentTab
|
||||||
onCurrentIndexChanged: currentTab = currentIndex
|
onCurrentIndexChanged: {
|
||||||
|
tabBar.enableIndicatorAnimation = true
|
||||||
|
root.currentTab = currentIndex
|
||||||
|
}
|
||||||
|
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
layer.effect: OpacityMask {
|
layer.effect: OpacityMask {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import "./modules/onScreenDisplay/"
|
|||||||
import "./modules/overview/"
|
import "./modules/overview/"
|
||||||
import "./modules/screenCorners/"
|
import "./modules/screenCorners/"
|
||||||
import "./modules/session/"
|
import "./modules/session/"
|
||||||
|
import "./modules/sidebarLeft/"
|
||||||
import "./modules/sidebarRight/"
|
import "./modules/sidebarRight/"
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
@@ -27,6 +28,7 @@ ShellRoot {
|
|||||||
ReloadPopup {}
|
ReloadPopup {}
|
||||||
ScreenCorners {}
|
ScreenCorners {}
|
||||||
Session {}
|
Session {}
|
||||||
|
SidebarLeft {}
|
||||||
SidebarRight {}
|
SidebarRight {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user