diff --git a/Bar.qml b/Bar.qml index 297afd6..d727e15 100644 --- a/Bar.qml +++ b/Bar.qml @@ -25,7 +25,6 @@ Variants { color: "transparent" property var root: Quickshell.shellDir - WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.namespace: "ZShell-Bar" WlrLayershell.exclusionMode: ExclusionMode.Ignore @@ -96,6 +95,7 @@ Variants { property bool sidebar property bool dashboard property bool bar + property bool osd Component.onCompleted: Visibilities.load(scope.modelData, this) } @@ -122,44 +122,48 @@ Variants { } } - MouseArea { + Interactions { id: mouseArea + screen: scope.modelData + popouts: panels.popouts + visibilities: visibilities + panels: panels + bar: barLoader anchors.fill: parent - hoverEnabled: true - onContainsMouseChanged: { - if ( !containsMouse ) { - panels.popouts.hasCurrent = false; - if ( !visibilities.sidebar && !visibilities.dashboard ) - visibilities.bar = Config.autoHide ? false : true; - } - } - - onPositionChanged: event => { - if ( Config.autoHide && !visibilities.bar ? mouseY < 4 : mouseY < backgroundRect.implicitHeight ) { - visibilities.bar = true; - barLoader.checkPopout(mouseX); - } - } - - onPressed: event => { - var traywithinX = mouseX >= panels.popouts.x + 8 && mouseX < panels.popouts.x + panels.popouts.implicitWidth; - var traywithinY = mouseY >= panels.popouts.y + exclusionZone.implicitHeight && mouseY < panels.popouts.y + exclusionZone.implicitHeight + panels.popouts.implicitHeight; - var sidebarwithinX = mouseX >= bar.width - panels.sidebar.width - var dashboardWithinX = mouseX <= panels.dashboard.width + panels.dashboard.x && mouseX >= panels.dashboard.x - var dashboardWithinY = mouseY <= backgroundRect.implicitHeight + panels.dashboard.implicitHeight - - if ( panels.popouts.hasCurrent ) { - if ( traywithinX && traywithinY ) { - } else { - panels.popouts.hasCurrent = false; - } - } else if ( visibilities.sidebar && !sidebarwithinX ) { - visibilities.sidebar = false; - } else if ( visibilities.dashboard && ( !dashboardWithinX || !dashboardWithinY )) { - visibilities.dashboard = false; - } - } + // onContainsMouseChanged: { + // if ( !containsMouse ) { + // panels.popouts.hasCurrent = false; + // if ( !visibilities.sidebar && !visibilities.dashboard ) + // visibilities.bar = Config.autoHide ? false : true; + // } + // } + // + // onPositionChanged: event => { + // if ( Config.autoHide && !visibilities.bar ? mouseY < 4 : mouseY < backgroundRect.implicitHeight ) { + // visibilities.bar = true; + // barLoader.checkPopout(mouseX); + // } + // } + // + // onPressed: event => { + // var traywithinX = mouseX >= panels.popouts.x + 8 && mouseX < panels.popouts.x + panels.popouts.implicitWidth; + // var traywithinY = mouseY >= panels.popouts.y + exclusionZone.implicitHeight && mouseY < panels.popouts.y + exclusionZone.implicitHeight + panels.popouts.implicitHeight; + // var sidebarwithinX = mouseX >= bar.width - panels.sidebar.width + // var dashboardWithinX = mouseX <= panels.dashboard.width + panels.dashboard.x && mouseX >= panels.dashboard.x + // var dashboardWithinY = mouseY <= backgroundRect.implicitHeight + panels.dashboard.implicitHeight + // + // if ( panels.popouts.hasCurrent ) { + // if ( traywithinX && traywithinY ) { + // } else { + // panels.popouts.hasCurrent = false; + // } + // } else if ( visibilities.sidebar && !sidebarwithinX ) { + // visibilities.sidebar = false; + // } else if ( visibilities.dashboard && ( !dashboardWithinX || !dashboardWithinY )) { + // visibilities.dashboard = false; + // } + // } Panels { id: panels @@ -179,6 +183,7 @@ Variants { color: "transparent" radius: 0 + Behavior on color { CAnim {} } diff --git a/Components/FilledSlider.qml b/Components/FilledSlider.qml new file mode 100644 index 0000000..f5d3615 --- /dev/null +++ b/Components/FilledSlider.qml @@ -0,0 +1,146 @@ +import QtQuick +import QtQuick.Templates +import qs.Helpers +import qs.Config +import qs.Modules + +Slider { + id: root + + required property string icon + property real oldValue + property bool initialized + property color color: DynamicColors.palette.m3secondary + + orientation: Qt.Vertical + + background: CustomRect { + color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.full + + CustomRect { + anchors.left: parent.left + anchors.right: parent.right + + y: root.handle.y + implicitHeight: parent.height - y + + color: root.color + radius: parent.radius + } + } + + handle: Item { + id: handle + + property alias moving: icon.moving + + y: root.visualPosition * (root.availableHeight - height) + implicitWidth: root.width + implicitHeight: root.width + + Elevation { + anchors.fill: parent + radius: rect.radius + level: handleInteraction.containsMouse ? 2 : 1 + } + + CustomRect { + id: rect + + anchors.fill: parent + + color: DynamicColors.palette.m3inverseSurface + radius: Appearance.rounding.full + + MouseArea { + id: handleInteraction + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.NoButton + } + + MaterialIcon { + id: icon + + property bool moving + + function update(): void { + animate = !moving; + binding.when = moving; + font.pointSize = moving ? Appearance.font.size.small : Appearance.font.size.larger; + font.family = moving ? Appearance.font.family.sans : Appearance.font.family.material; + } + + text: root.icon + color: DynamicColors.palette.m3inverseOnSurface + anchors.centerIn: parent + + onMovingChanged: anim.restart() + + Binding { + id: binding + + target: icon + property: "text" + value: Math.round(root.value * 100) + when: false + } + + SequentialAnimation { + id: anim + + Anim { + target: icon + property: "scale" + to: 0 + duration: Appearance.anim.durations.normal / 2 + easing.bezierCurve: Appearance.anim.curves.standardAccel + } + ScriptAction { + script: icon.update() + } + Anim { + target: icon + property: "scale" + to: 1 + duration: Appearance.anim.durations.normal / 2 + easing.bezierCurve: Appearance.anim.curves.standardDecel + } + } + } + } + } + + onPressedChanged: handle.moving = pressed + + onValueChanged: { + if (!initialized) { + initialized = true; + return; + } + if (Math.abs(value - oldValue) < 0.01) + return; + oldValue = value; + handle.moving = true; + stateChangeDelay.restart(); + } + + Timer { + id: stateChangeDelay + + interval: 500 + onTriggered: { + if (!root.pressed) + handle.moving = false; + } + } + + Behavior on value { + Anim { + duration: Appearance.anim.durations.large + } + } +} diff --git a/Config/Config.qml b/Config/Config.qml index 53f87b1..a41f36c 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -33,6 +33,8 @@ Singleton { property alias dashboard: adapter.dashboard property alias appearance: adapter.appearance property alias autoHide: adapter.autoHide + property alias macchiato: adapter.macchiato + property alias osd: adapter.osd FileView { id: root @@ -74,6 +76,8 @@ Singleton { property DashboardConfig dashboard: DashboardConfig {} property AppearanceConf appearance: AppearanceConf {} property bool autoHide: false + property bool macchiato: false + property Osd osd: Osd {} } } } diff --git a/Config/DynamicColors.qml b/Config/DynamicColors.qml index 0952f19..434f435 100644 --- a/Config/DynamicColors.qml +++ b/Config/DynamicColors.qml @@ -214,4 +214,65 @@ Singleton { property color m3onTertiaryFixed: "#2f1500" property color m3onTertiaryFixedVariant: "#623f21" } + + component M3MaccchiatoPalette: QtObject { + property color m3primary_paletteKeyColor: "#6a73ac" + property color m3secondary_paletteKeyColor: "#72758e" + property color m3tertiary_paletteKeyColor: "#9b6592" + property color m3neutral_paletteKeyColor: "#77767b" + property color m3neutral_variant_paletteKeyColor: "#767680" + property color m3background: "#131317" + property color m3onBackground: "#e4e1e7" + property color m3surface: "#131317" + property color m3surfaceDim: "#131317" + property color m3surfaceBright: "#39393d" + property color m3surfaceContainerLowest: "#0e0e12" + property color m3surfaceContainerLow: "#1b1b1f" + property color m3surfaceContainer: "#1f1f23" + property color m3surfaceContainerHigh: "#2a2a2e" + property color m3surfaceContainerHighest: "#353438" + property color m3onSurface: "#e4e1e7" + property color m3surfaceVariant: "#46464f" + property color m3onSurfaceVariant: "#c6c5d1" + property color m3inverseSurface: "#e4e1e7" + property color m3inverseOnSurface: "#303034" + property color m3outline: "#90909a" + property color m3outlineVariant: "#46464f" + property color m3shadow: "#000000" + property color m3scrim: "#000000" + property color m3surfaceTint: "#bac3ff" + property color m3primary: "#bac3ff" + property color m3onPrimary: "#232c60" + property color m3primaryContainer: "#6a73ac" + property color m3onPrimaryContainer: "#ffffff" + property color m3inversePrimary: "#525b92" + property color m3secondary: "#c3c5e0" + property color m3onSecondary: "#2c2f44" + property color m3secondaryContainer: "#42455c" + property color m3onSecondaryContainer: "#b1b3ce" + property color m3tertiary: "#f1b3e5" + property color m3onTertiary: "#4c1f48" + property color m3tertiaryContainer: "#b77ead" + property color m3onTertiaryContainer: "#000000" + property color m3error: "#ffb4ab" + property color m3onError: "#690005" + property color m3errorContainer: "#93000a" + property color m3onErrorContainer: "#ffdad6" + property color m3primaryFixed: "#dee0ff" + property color m3primaryFixedDim: "#bac3ff" + property color m3onPrimaryFixed: "#0b154b" + property color m3onPrimaryFixedVariant: "#3a4378" + property color m3secondaryFixed: "#dfe1fd" + property color m3secondaryFixedDim: "#c3c5e0" + property color m3onSecondaryFixed: "#171a2e" + property color m3onSecondaryFixedVariant: "#42455c" + property color m3tertiaryFixed: "#ffd7f4" + property color m3tertiaryFixedDim: "#f1b3e5" + property color m3onTertiaryFixed: "#340831" + property color m3onTertiaryFixedVariant: "#66365f" + property color m3success: "#B5CCBA" + property color m3onSuccess: "#213528" + property color m3successContainer: "#374B3E" + property color m3onSuccessContainer: "#D1E9D6" + } } diff --git a/Config/Osd.qml b/Config/Osd.qml new file mode 100644 index 0000000..ee5127d --- /dev/null +++ b/Config/Osd.qml @@ -0,0 +1,14 @@ +import Quickshell.Io + +JsonObject { + property bool enabled: true + property int hideDelay: 5000 + property bool enableBrightness: true + property bool enableMicrophone: true + property Sizes sizes: Sizes {} + + component Sizes: JsonObject { + property int sliderWidth: 30 + property int sliderHeight: 150 + } +} diff --git a/Config/Services.qml b/Config/Services.qml index 422539c..559fbb1 100644 --- a/Config/Services.qml +++ b/Config/Services.qml @@ -2,13 +2,18 @@ import Quickshell.Io import QtQuick JsonObject { - property string weatherLocation: "" - property real brightnessIncrement: 0.1 - property string defaultPlayer: "Spotify" - property list playerAliases: [ - { - "from": "com.github.th_ch.youtube_music", - "to": "YT Music" - } - ] + property string weatherLocation: "" + property bool useFahrenheit: [Locale.ImperialUSSystem, Locale.ImperialSystem].includes(Qt.locale().measurementSystem) + property bool useTwelveHourClock: Qt.locale().timeFormat(Locale.ShortFormat).toLowerCase().includes("a") + property string gpuType: "" + property real audioIncrement: 0.1 + property real brightnessIncrement: 0.1 + property real maxVolume: 1.0 + property string defaultPlayer: "Spotify" + property list playerAliases: [ + { + "from": "com.github.th_ch.youtube_music", + "to": "YT Music" + } + ] } diff --git a/Drawers/Backgrounds.qml b/Drawers/Backgrounds.qml index 058e275..c8dd994 100644 --- a/Drawers/Backgrounds.qml +++ b/Drawers/Backgrounds.qml @@ -6,6 +6,7 @@ import qs.Modules.Notifications as Notifications import qs.Modules.Notifications.Sidebar as Sidebar import qs.Modules.Notifications.Sidebar.Utils as Utils import qs.Modules.Dashboard as Dashboard +import qs.Modules.Osd as Osd Shape { id: root @@ -16,9 +17,18 @@ Shape { anchors.fill: parent // anchors.margins: 8 - anchors.topMargin: !root.visibilities.bar ? 4 : bar.implicitHeight + anchors.topMargin: bar.implicitHeight preferredRendererType: Shape.CurveRenderer + Component.onCompleted: console.log(root.bar.implicitHeight, root.bar.anchors.topMargin) + + Osd.Background { + wrapper: root.panels.osd + + startX: root.width - root.panels.sidebar.width + startY: ( root.height - wrapper.height ) / 2 - rounding + } + Modules.Background { wrapper: root.panels.popouts invertBottomRounding: wrapper.x <= 0 diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml new file mode 100644 index 0000000..86eb411 --- /dev/null +++ b/Drawers/Interactions.qml @@ -0,0 +1,273 @@ +import Quickshell +import QtQuick +import qs.Components +import qs.Config +import qs.Modules as BarPopouts + +CustomMouseArea { + id: root + + required property ShellScreen screen + required property BarPopouts.Wrapper popouts + required property PersistentProperties visibilities + required property Panels panels + required property Item bar + + property point dragStart + property bool dashboardShortcutActive + property bool osdShortcutActive + property bool utilitiesShortcutActive + + function withinPanelHeight(panel: Item, x: real, y: real): bool { + const panelY = panel.y + bar.implicitHeight; + return y >= panelY && y <= panelY + panel.height; + } + + function withinPanelWidth(panel: Item, x: real, y: real): bool { + const panelX = panel.x; + return x >= panelX && x <= panelX + panel.width; + } + + function inLeftPanel(panel: Item, x: real, y: real): bool { + return x < panel.x + panel.width && withinPanelHeight(panel, x, y); + } + + function inRightPanel(panel: Item, x: real, y: real): bool { + return x > panel.x && withinPanelHeight(panel, x, y); + } + + function inTopPanel(panel: Item, x: real, y: real): bool { + return y < bar.implicitHeight + panel.height && withinPanelWidth(panel, x, y); + } + + function inBottomPanel(panel: Item, x: real, y: real): bool { + return y > root.height - panel.height && withinPanelWidth(panel, x, y); + } + + function onWheel(event: WheelEvent): void { + if (event.x < bar.implicitWidth) { + bar.handleWheel(event.y, event.angleDelta); + } + } + + anchors.fill: parent + hoverEnabled: true + + onPressed: event => { + if ( root.popouts.hasCurrent && !inTopPanel( root.popouts, event.x, event.y )) { + root.popouts.hasCurrent = false; + } else if (root.visibilities.sidebar && !inRightPanel( panels.sidebar, event.x, event.y )) { + root.visibilities.sidebar = false; + } else if (root.visibilities.dashboard && !inTopPanel( panels.dashboard, event.x, event.y )) { + root.visibilities.dashboard = false; + } + } + + onContainsMouseChanged: { + if (!containsMouse) { + // Only hide if not activated by shortcut + if (!osdShortcutActive) { + visibilities.osd = false; + root.panels.osd.hovered = false; + } + + if (!dashboardShortcutActive) + visibilities.dashboard = false; + + // if (!utilitiesShortcutActive) + // visibilities.utilities = false; + + if (!popouts.currentName.startsWith("traymenu") || (popouts.current?.depth ?? 0) <= 1) { + popouts.hasCurrent = false; + // bar.closeTray(); + } + + if (Config.autoHide) + root.visibilities.bar = false; + } + } + + onPositionChanged: event => { + if (popouts.isDetached) + return; + + const x = event.x; + const y = event.y; + const dragX = x - dragStart.x; + const dragY = y - dragStart.y; + + // Show bar in non-exclusive mode on hover + if (!visibilities.bar && Config.autoHide && y < bar.implicitHeight + bar.anchors.topMargin) + visibilities.bar = true; + + if (panels.sidebar.width === 0) { + // Show osd on hover + const showOsd = inRightPanel(panels.osd, x, y); + + // // Always update visibility based on hover if not in shortcut mode + if (!osdShortcutActive) { + visibilities.osd = showOsd; + root.panels.osd.hovered = showOsd; + } else if (showOsd) { + // If hovering over OSD area while in shortcut mode, transition to hover control + osdShortcutActive = false; + root.panels.osd.hovered = true; + } + + // const showSidebar = pressed && dragStart.x > bar.implicitWidth + panels.sidebar.x; + // + // // Show/hide session on drag + // if (pressed && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) { + // if (dragX < -Config.session.dragThreshold) + // visibilities.session = true; + // else if (dragX > Config.session.dragThreshold) + // visibilities.session = false; + // + // // Show sidebar on drag if in session area and session is nearly fully visible + // if (showSidebar && panels.session.width >= panels.session.nonAnimWidth && dragX < -Config.sidebar.dragThreshold) + // visibilities.sidebar = true; + // } else if (showSidebar && dragX < -Config.sidebar.dragThreshold) { + // // Show sidebar on drag if not in session area + // visibilities.sidebar = true; + // } + } else { + const outOfSidebar = x < width - panels.sidebar.width; + // Show osd on hover + const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y); + + // Always update visibility based on hover if not in shortcut mode + if (!osdShortcutActive) { + visibilities.osd = showOsd; + root.panels.osd.hovered = showOsd; + } else if (showOsd) { + // If hovering over OSD area while in shortcut mode, transition to hover control + osdShortcutActive = false; + root.panels.osd.hovered = true; + } + // + // // Show/hide session on drag + // if (pressed && outOfSidebar && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) { + // if (dragX < -Config.session.dragThreshold) + // visibilities.session = true; + // else if (dragX > Config.session.dragThreshold) + // visibilities.session = false; + // } + // + // // Hide sidebar on drag + // if (pressed && inRightPanel(panels.sidebar, dragStart.x, 0) && dragX > Config.sidebar.dragThreshold) + // visibilities.sidebar = false; + } + + // Show launcher on hover, or show/hide on drag if hover is disabled + // if (Config.launcher.showOnHover) { + // if (!visibilities.launcher && inBottomPanel(panels.launcher, x, y)) + // visibilities.launcher = true; + // } else if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) { + // if (dragY < -Config.launcher.dragThreshold) + // visibilities.launcher = true; + // else if (dragY > Config.launcher.dragThreshold) + // visibilities.launcher = false; + // } + // + // // Show dashboard on hover + // const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboard, x, y); + // + // // Always update visibility based on hover if not in shortcut mode + // if (!dashboardShortcutActive) { + // visibilities.dashboard = showDashboard; + // } else if (showDashboard) { + // // If hovering over dashboard area while in shortcut mode, transition to hover control + // dashboardShortcutActive = false; + // } + // + // // Show/hide dashboard on drag (for touchscreen devices) + // if (pressed && inTopPanel(panels.dashboard, dragStart.x, dragStart.y) && withinPanelWidth(panels.dashboard, x, y)) { + // if (dragY > Config.dashboard.dragThreshold) + // visibilities.dashboard = true; + // else if (dragY < -Config.dashboard.dragThreshold) + // visibilities.dashboard = false; + // } + // + // // Show utilities on hover + // const showUtilities = inBottomPanel(panels.utilities, x, y); + // + // // Always update visibility based on hover if not in shortcut mode + // if (!utilitiesShortcutActive) { + // visibilities.utilities = showUtilities; + // } else if (showUtilities) { + // // If hovering over utilities area while in shortcut mode, transition to hover control + // utilitiesShortcutActive = false; + // } + + // Show popouts on hover + console.log(y) + if (y < bar.implicitHeight) { + bar.checkPopout(x); + } + } + + // Monitor individual visibility changes + Connections { + target: root.visibilities + + function onLauncherChanged() { + // If launcher is hidden, clear shortcut flags for dashboard and OSD + if (!root.visibilities.launcher) { + root.dashboardShortcutActive = false; + root.osdShortcutActive = false; + root.utilitiesShortcutActive = false; + + // Also hide dashboard and OSD if they're not being hovered + const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); + const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); + + if (!inDashboardArea) { + root.visibilities.dashboard = false; + } + if (!inOsdArea) { + root.visibilities.osd = false; + root.panels.osd.hovered = false; + } + } + } + + function onDashboardChanged() { + if (root.visibilities.dashboard) { + // Dashboard became visible, immediately check if this should be shortcut mode + const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); + if (!inDashboardArea) { + root.dashboardShortcutActive = true; + } + } else { + // Dashboard hidden, clear shortcut flag + root.dashboardShortcutActive = false; + } + } + + function onOsdChanged() { + if (root.visibilities.osd) { + // OSD became visible, immediately check if this should be shortcut mode + const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); + if (!inOsdArea) { + root.osdShortcutActive = true; + } + } else { + // OSD hidden, clear shortcut flag + root.osdShortcutActive = false; + } + } + + function onUtilitiesChanged() { + if (root.visibilities.utilities) { + // Utilities became visible, immediately check if this should be shortcut mode + const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY); + if (!inUtilitiesArea) { + root.utilitiesShortcutActive = true; + } + } else { + // Utilities hidden, clear shortcut flag + root.utilitiesShortcutActive = false; + } + } + } +} diff --git a/Drawers/Panels.qml b/Drawers/Panels.qml index 5c22606..255eac3 100644 --- a/Drawers/Panels.qml +++ b/Drawers/Panels.qml @@ -6,6 +6,7 @@ import qs.Modules.Notifications as Notifications import qs.Modules.Notifications.Sidebar as Sidebar import qs.Modules.Notifications.Sidebar.Utils as Utils import qs.Modules.Dashboard as Dashboard +import qs.Modules.Osd as Osd import qs.Config Item { @@ -20,11 +21,24 @@ Item { readonly property alias notifications: notifications readonly property alias utilities: utilities readonly property alias dashboard: dashboard + readonly property alias osd: osd anchors.fill: parent // anchors.margins: 8 anchors.topMargin: bar.implicitHeight + Osd.Wrapper { + id: osd + + clip: session.width > 0 || sidebar.width > 0 + screen: root.screen + visibilities: root.visibilities + + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: sidebar.width + } + Modules.Wrapper { id: popouts diff --git a/Modules/Osd/Background.qml b/Modules/Osd/Background.qml new file mode 100644 index 0000000..5f127f0 --- /dev/null +++ b/Modules/Osd/Background.qml @@ -0,0 +1,61 @@ +import QtQuick +import QtQuick.Shapes +import qs.Components +import qs.Helpers +import qs.Config +import qs.Modules as Modules + +ShapePath { + id: root + + required property Wrapper wrapper + readonly property real rounding: 8 + readonly property bool flatten: wrapper.width < rounding * 2 + readonly property real roundingX: flatten ? wrapper.width / 2 : rounding + + strokeWidth: -1 + fillColor: DynamicColors.palette.m3surface + + PathArc { + relativeX: -root.roundingX + relativeY: root.rounding + radiusX: Math.min(root.rounding, root.wrapper.width) + radiusY: root.rounding + } + PathLine { + relativeX: -(root.wrapper.width - root.roundingX * 2) + relativeY: 0 + } + PathArc { + relativeX: -root.roundingX + relativeY: root.rounding + radiusX: Math.min(root.rounding, root.wrapper.width) + radiusY: root.rounding + direction: PathArc.Counterclockwise + } + PathLine { + relativeX: 0 + relativeY: root.wrapper.height - root.rounding * 2 + } + PathArc { + relativeX: root.roundingX + relativeY: root.rounding + radiusX: Math.min(root.rounding, root.wrapper.width) + radiusY: root.rounding + direction: PathArc.Counterclockwise + } + PathLine { + relativeX: root.wrapper.width - root.roundingX * 2 + relativeY: 0 + } + PathArc { + relativeX: root.roundingX + relativeY: root.rounding + radiusX: Math.min(root.rounding, root.wrapper.width) + radiusY: root.rounding + } + + Behavior on fillColor { + Modules.CAnim {} + } +} diff --git a/Modules/Osd/Content.qml b/Modules/Osd/Content.qml new file mode 100644 index 0000000..2b5e619 --- /dev/null +++ b/Modules/Osd/Content.qml @@ -0,0 +1,128 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Helpers +import qs.Config +import qs.Modules.Dashboard.Dash +import qs.Modules as Modules + +Item { + id: root + + required property Brightness.Monitor monitor + required property var visibilities + + required property real volume + required property bool muted + required property real sourceVolume + required property bool sourceMuted + required property real brightness + + implicitWidth: layout.implicitWidth + Appearance.padding.large * 2 + implicitHeight: layout.implicitHeight + Appearance.padding.large * 2 + + ColumnLayout { + id: layout + + anchors.centerIn: parent + spacing: Appearance.spacing.normal + + // Speaker volume + CustomMouseArea { + implicitWidth: Config.osd.sizes.sliderWidth + implicitHeight: Config.osd.sizes.sliderHeight + + function onWheel(event: WheelEvent) { + if (event.angleDelta.y > 0) + Audio.incrementVolume(); + else if (event.angleDelta.y < 0) + Audio.decrementVolume(); + } + + FilledSlider { + anchors.fill: parent + + icon: Icons.getVolumeIcon(value, root.muted) + value: root.volume + to: Config.services.maxVolume + onMoved: Audio.setVolume(value) + } + } + + // Microphone volume + WrappedLoader { + shouldBeActive: Config.osd.enableMicrophone && (!Config.osd.enableBrightness || !root.visibilities.session) + + sourceComponent: CustomMouseArea { + implicitWidth: Config.osd.sizes.sliderWidth + implicitHeight: Config.osd.sizes.sliderHeight + + function onWheel(event: WheelEvent) { + if (event.angleDelta.y > 0) + Audio.incrementSourceVolume(); + else if (event.angleDelta.y < 0) + Audio.decrementSourceVolume(); + } + + FilledSlider { + anchors.fill: parent + + icon: Icons.getMicVolumeIcon(value, root.sourceMuted) + value: root.sourceVolume + to: Config.services.maxVolume + color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3secondary + onMoved: Audio.setSourceVolume(value) + } + } + } + + // Brightness + WrappedLoader { + shouldBeActive: Config.osd.enableBrightness + + sourceComponent: CustomMouseArea { + implicitWidth: Config.osd.sizes.sliderWidth + implicitHeight: Config.osd.sizes.sliderHeight + + function onWheel(event: WheelEvent) { + const monitor = root.monitor; + if (!monitor) + return; + if (event.angleDelta.y > 0) + monitor.setBrightness(monitor.brightness + Config.services.brightnessIncrement); + else if (event.angleDelta.y < 0) + monitor.setBrightness(monitor.brightness - Config.services.brightnessIncrement); + } + + FilledSlider { + anchors.fill: parent + + icon: `brightness_${(Math.round(value * 6) + 1)}` + value: root.brightness + onMoved: root.monitor?.setBrightness(value) + } + } + } + } + + component WrappedLoader: Loader { + required property bool shouldBeActive + + Layout.preferredHeight: shouldBeActive ? Config.osd.sizes.sliderHeight : 0 + opacity: shouldBeActive ? 1 : 0 + active: opacity > 0 + visible: active + + Behavior on Layout.preferredHeight { + Modules.Anim { + easing.bezierCurve: Appearance.anim.curves.emphasized + } + } + + Behavior on opacity { + Modules.Anim {} + } + } +} diff --git a/Modules/Osd/Wrapper.qml b/Modules/Osd/Wrapper.qml new file mode 100644 index 0000000..53f361c --- /dev/null +++ b/Modules/Osd/Wrapper.qml @@ -0,0 +1,136 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import qs.Components +import qs.Helpers +import qs.Config +import qs.Modules as Modules +import qs.Modules.Dashboard.Dash + +Item { + id: root + + required property ShellScreen screen + required property var visibilities + property bool hovered + readonly property Brightness.Monitor monitor: Brightness.getMonitorForScreen(root.screen) + readonly property bool shouldBeActive: visibilities.osd && Config.osd.enabled && !(visibilities.utilities && Config.utilities.enabled) + + property real volume + property bool muted + property real sourceVolume + property bool sourceMuted + property real brightness + + function show(): void { + visibilities.osd = true; + timer.restart(); + } + + Component.onCompleted: { + volume = Audio.volume; + muted = Audio.muted; + sourceVolume = Audio.sourceVolume; + sourceMuted = Audio.sourceMuted; + brightness = root.monitor?.brightness ?? 0; + } + + visible: width > 0 + implicitWidth: 0 + implicitHeight: content.implicitHeight + + states: State { + name: "visible" + when: root.shouldBeActive + + PropertyChanges { + root.implicitWidth: content.implicitWidth + } + } + + transitions: [ + Transition { + from: "" + to: "visible" + + Modules.Anim { + target: root + property: "implicitWidth" + easing.bezierCurve: MaterialEasing.expressiveEffects + } + }, + Transition { + from: "visible" + to: "" + + Modules.Anim { + target: root + property: "implicitWidth" + easing.bezierCurve: MaterialEasing.expressiveEffects + } + } + ] + + Connections { + target: Audio + + function onMutedChanged(): void { + root.show(); + root.muted = Audio.muted; + } + + function onVolumeChanged(): void { + root.show(); + root.volume = Audio.volume; + } + + function onSourceMutedChanged(): void { + root.show(); + root.sourceMuted = Audio.sourceMuted; + } + + function onSourceVolumeChanged(): void { + root.show(); + root.sourceVolume = Audio.sourceVolume; + } + } + + Connections { + target: root.monitor + + function onBrightnessChanged(): void { + root.show(); + root.brightness = root.monitor?.brightness ?? 0; + } + } + + Timer { + id: timer + + interval: Config.osd.hideDelay + onTriggered: { + if (!root.hovered) + root.visibilities.osd = false; + } + } + + Loader { + id: content + + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + + Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible) + + sourceComponent: Content { + monitor: root.monitor + visibilities: root.visibilities + volume: root.volume + muted: root.muted + sourceVolume: root.sourceVolume + sourceMuted: root.sourceMuted + brightness: root.brightness + } + } +}