From 7872fba6feab09757c89070641aaabbaf7b179c9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 4 Jan 2026 22:08:53 +0100 Subject: [PATCH] hefty: morphing thingy --- .../ii/modules/common/widgets/shapes | 2 +- .../hefty/topLayer/HAbstractMorphedPanel.qml | 32 +++++ .../ii/modules/hefty/topLayer/HBar.qml | 89 +++++++++++++ .../ii/modules/hefty/topLayer/HOverview.qml | 75 +++++++++++ .../ii/modules/hefty/topLayer/HTopLayer.qml | 15 +++ .../modules/hefty/topLayer/HTopLayerPanel.qml | 123 ++++++++++++++++++ .../ii/panelFamilies/HeftyHypeFamily.qml | 48 +++++++ dots/.config/quickshell/ii/shell.qml | 7 +- 8 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/hefty/topLayer/HAbstractMorphedPanel.qml create mode 100644 dots/.config/quickshell/ii/modules/hefty/topLayer/HBar.qml create mode 100644 dots/.config/quickshell/ii/modules/hefty/topLayer/HOverview.qml create mode 100644 dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayer.qml create mode 100644 dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml create mode 100644 dots/.config/quickshell/ii/panelFamilies/HeftyHypeFamily.qml diff --git a/dots/.config/quickshell/ii/modules/common/widgets/shapes b/dots/.config/quickshell/ii/modules/common/widgets/shapes index 7f0f0709e..5916b5ae6 160000 --- a/dots/.config/quickshell/ii/modules/common/widgets/shapes +++ b/dots/.config/quickshell/ii/modules/common/widgets/shapes @@ -1 +1 @@ -Subproject commit 7f0f0709ec5bbe4c3158c7f5fc68fd890af46618 +Subproject commit 5916b5ae6f4a3d9c351ed5c78ae24ce4013c1764 diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/HAbstractMorphedPanel.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/HAbstractMorphedPanel.qml new file mode 100644 index 000000000..53b03769a --- /dev/null +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/HAbstractMorphedPanel.qml @@ -0,0 +1,32 @@ +import QtQuick +import Quickshell + +/** + * Abstract morphed panel to be used in TopLayerPanel. + * Screen width and height are to be supplied when declared in the top layer panel + * Others are to be declared by panels deriving from this + * + * To make sure morph movements don't look weird: + * - Follow the convention of having points start from bottom-middle and go clockwise + * - Make sure the number of points is "balanced" in all directions + * - Tip: Sometimes symmetry is not enough. Try to have more intermediate points if ones you have are too spaced out and act funny. + */ +Item { + id: root + + // To be fed + required property int screenWidth + required property int screenHeight + + // Some info + property int reservedTop: 0 + property int reservedBottom: 0 + property int reservedLeft: 0 + property int reservedRight: 0 + + // Main stuff + property var backgroundPolygon + property Region maskRegion: Region { + item: root + } +} diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/HBar.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/HBar.qml new file mode 100644 index 000000000..cdfa7a05c --- /dev/null +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/HBar.qml @@ -0,0 +1,89 @@ +import QtQuick +import qs.modules.common +import "../../common/widgets/shapes/material-shapes.js" as MaterialShapes +import "../../common/widgets/shapes/shapes/corner-rounding.js" as CornerRounding +import "../../common/widgets/shapes/geometry/offset.js" as Offset + +HAbstractMorphedPanel { + id: root + + // Own props + property int barHeight: Appearance.sizes.baseBarHeight + function getRounding(cornerStyle) { + switch(cornerStyle) { + case 0: return Appearance.rounding.screenRounding; + case 1: return Appearance.rounding.windowRounding; + case 2: return 0; + default: return Appearance.rounding.screenRounding; + } + } + function getEdgeGap(cornerStyle) { + switch(cornerStyle) { + case 0: return 0; + case 1: return Appearance.sizes.hyprlandGapsOut; + case 2: return 0; + default: return 0; + } + } + function getEdgeRounding(cornerStyle) { + switch(cornerStyle) { + case 0: return 0; + case 1: return Appearance.rounding.windowRounding; + case 2: return 0; + default: return Appearance.rounding.windowRounding; + } + } + function getHug(cornerStyle) { + return cornerStyle === 0; + } + + // Some info + reservedTop: barHeight + getEdgeGap(Config.options.bar.cornerStyle) + + // Background + backgroundPolygon: { + // It's certainly cleaner to have the below props declared outside, but we do this + // to make sure config change only makes this re-evaluate exactly once + const cornerStyle = Config.options.bar.cornerStyle + const rounding = root.getRounding(cornerStyle) + const edgeGap = root.getEdgeGap(cornerStyle) + const edgeRounding = root.getEdgeRounding(cornerStyle) + const hug = root.getHug(cornerStyle) + const points = [ + + // bottom-middle + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth / 2, root.barHeight + edgeGap), new CornerRounding.CornerRounding(0)), + + // bottom-left /|| + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap + rounding, edgeGap + barHeight), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap, edgeGap + barHeight), new CornerRounding.CornerRounding(rounding)), + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap, edgeGap + barHeight + rounding * (hug ? 1 : -1)), new CornerRounding.CornerRounding(edgeRounding)), + // top-left |/- + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap, edgeGap + rounding), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap, edgeGap), new CornerRounding.CornerRounding(edgeRounding)), + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap + rounding, edgeGap), new CornerRounding.CornerRounding(0)), + + // top-middle + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth / 2, edgeGap), new CornerRounding.CornerRounding(0)), + + // top-right -\| + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap - rounding, edgeGap), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap, edgeGap), new CornerRounding.CornerRounding(edgeRounding)), + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap, edgeGap + rounding), new CornerRounding.CornerRounding(0)), + + // bottom-right ||\ + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap, root.barHeight + edgeGap + rounding * (hug ? 1 : -1)), new CornerRounding.CornerRounding(edgeRounding)), + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap, root.barHeight + edgeGap), new CornerRounding.CornerRounding(rounding)), + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap - rounding, root.barHeight + edgeGap), new CornerRounding.CornerRounding(0)), + ] + return MaterialShapes.customPolygon(points, 1, new Offset.Offset(root.screenWidth / 2, edgeGap + barHeight / 2)) + } + + // Content + implicitHeight: barHeight + getEdgeGap(Config.options.bar.cornerStyle) * 2 + anchors { + top: parent.top + left: parent.left + right: parent.right + } +} diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/HOverview.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/HOverview.qml new file mode 100644 index 000000000..960693fb1 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/HOverview.qml @@ -0,0 +1,75 @@ +import QtQuick +import Quickshell.Hyprland +import qs +import qs.modules.common +import "../../common/widgets/shapes/material-shapes.js" as MaterialShapes +import "../../common/widgets/shapes/shapes/corner-rounding.js" as CornerRounding +import "../../common/widgets/shapes/geometry/offset.js" as Offset + +HAbstractMorphedPanel { + id: root + + // Own props + property int edgeGap: Appearance.sizes.hyprlandGapsOut + property real rounding: Appearance.rounding.windowRounding + property real contentHeight: 300 // For now + + // Background + backgroundPolygon: MaterialShapes.customPolygon([ + // bottom-middle + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth / 2, edgeGap + root.contentHeight), new CornerRounding.CornerRounding(0)), + // bottom-left + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap + rounding, edgeGap + root.contentHeight), new CornerRounding.CornerRounding(rounding)), + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap, edgeGap + root.contentHeight), new CornerRounding.CornerRounding(rounding)), + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap, edgeGap + root.contentHeight - rounding), new CornerRounding.CornerRounding(rounding)), + // top-left + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap, edgeGap + rounding), new CornerRounding.CornerRounding(rounding)), + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap, edgeGap), new CornerRounding.CornerRounding(rounding)), + new MaterialShapes.PointNRound(new Offset.Offset(edgeGap + rounding, edgeGap), new CornerRounding.CornerRounding(rounding)), + // top-middle + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth / 2, edgeGap), new CornerRounding.CornerRounding(0)), + // top-right + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap - rounding, edgeGap), new CornerRounding.CornerRounding(rounding)), + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap, edgeGap), new CornerRounding.CornerRounding(rounding)), + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap, edgeGap + rounding), new CornerRounding.CornerRounding(rounding)), + + // bottom-right + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap, edgeGap + root.contentHeight - rounding), new CornerRounding.CornerRounding(rounding)), + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap, edgeGap + root.contentHeight), new CornerRounding.CornerRounding(rounding)), + new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - edgeGap - rounding, edgeGap + root.contentHeight), new CornerRounding.CornerRounding(rounding)), + ], 1, new Offset.Offset(root.screenWidth / 2, edgeGap + contentHeight / 2)) + + // Keybinds + GlobalShortcut { + name: "searchToggle" + description: "Toggles search on press" + + onPressed: { + GlobalStates.overviewOpen = !GlobalStates.overviewOpen; + } + } + GlobalShortcut { + name: "searchToggleRelease" + description: "Toggles search on release" + + onPressed: { + GlobalStates.superReleaseMightTrigger = true; + } + + onReleased: { + if (!GlobalStates.superReleaseMightTrigger) { + GlobalStates.superReleaseMightTrigger = true; + return; + } + GlobalStates.overviewOpen = !GlobalStates.overviewOpen; + } + } + GlobalShortcut { + name: "searchToggleReleaseInterrupt" + description: "Interrupts possibility of search being toggled on release. " + "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. " + "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything." + + onPressed: { + GlobalStates.superReleaseMightTrigger = false; + } + } +} \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayer.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayer.qml new file mode 100644 index 000000000..3f30e2d78 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayer.qml @@ -0,0 +1,15 @@ +import QtQuick +import Quickshell + +// The stuff that sits on the "top" layer for layershells. Not to be confused with "toplevels" as in windows. +Scope { + id: root + + Variants { + model: Quickshell.screens + delegate: HTopLayerPanel { + required property var modelData + screen: modelData + } + } +} diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml new file mode 100644 index 000000000..9109d4769 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml @@ -0,0 +1,123 @@ +import QtQuick +import Qt5Compat.GraphicalEffects +import Quickshell +import Quickshell.Wayland +import Quickshell.Hyprland +import qs +import "../../common" +import "../../common/widgets/shapes" as S +import "../../common/widgets/shapes/material-shapes.js" as MaterialShapes +import "../../common/widgets/shapes/shapes/corner-rounding.js" as CornerRounding +import "../../common/widgets/shapes/geometry/offset.js" as Offset + +/** + * Fullscreen layer. Uses masking to not block clicks on windows n' stuff. + */ +PanelWindow { + id: root + + ///////////////// Window ////////////////// + color: "transparent" + WlrLayershell.namespace: "quickshell:topLayerPanel" + exclusionMode: ExclusionMode.Ignore + anchors { + top: true + left: true + right: true + bottom: true + } + + mask: Region { + item: root.currentPanel + } + // HyprlandWindow.visibleMask: mask // TODO: use this later to optimize hyprland's rendering + + ///////////////// Content ////////////////// + + property alias roundedPolygon: backgroundShape.roundedPolygon + S.ShapeCanvas { + id: backgroundShape + anchors.fill: parent + polygonIsNormalized: false + roundedPolygon: MaterialShapes.customPolygon([new MaterialShapes.PointNRound(new Offset.Offset(root.screen.width, 0), new CornerRounding.CornerRounding(9999)),]) + animation: NumberAnimation { + duration: 500 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial + } + // animation: SpringAnimation { + // spring: 3.5 + // damping: 0.3 + // } + color: Appearance.colors.colLayer0 + borderWidth: (root.currentPanel === bar && Config.options.bar.cornerStyle !== 1) ? 0 : 1 + borderColor: Appearance.colors.colLayer0Border + visible: false // cuz there's already the shadow + // debug: true + } + DropShadow { + id: shadow + source: backgroundShape + anchors.fill: backgroundShape + radius: 10 + samples: radius * 2 + 1 // Ideally radius * 2 + 1, see qt docs + color: "#44000000" + } + + property HAbstractMorphedPanel currentPanel: bar + roundedPolygon: currentPanel.backgroundPolygon + + // Do we want to have reserved area always follow the bar or maybe differ per panel? + EdgeReservedArea { + anchors.top: true + exclusiveZone: bar.reservedTop + } + EdgeReservedArea { + anchors.bottom: true + exclusiveZone: bar.reservedBottom + } + EdgeReservedArea { + anchors.left: true + exclusiveZone: bar.reservedLeft + } + EdgeReservedArea { + anchors.right: true + exclusiveZone: bar.reservedRight + } + + ////////////// Content: Panels /////////////// + + HBar { + id: bar + screenWidth: root.width + screenHeight: root.height + } + + HOverview { + id: overview + screenWidth: root.width + screenHeight: root.height + } + + Connections { + target: GlobalStates + function onOverviewOpenChanged() { + if (GlobalStates.overviewOpen) { + currentPanel = overview; + } else { + currentPanel = bar; + } + } + } + + //////////////// Components ///////////////// + + component EdgeReservedArea: PanelWindow { + WlrLayershell.namespace: "quickshell:edgeReservedArea" + implicitWidth: 0 + implicitHeight: 0 + mask: Region { + item: null + } + } +} diff --git a/dots/.config/quickshell/ii/panelFamilies/HeftyHypeFamily.qml b/dots/.config/quickshell/ii/panelFamilies/HeftyHypeFamily.qml new file mode 100644 index 000000000..62e969f2f --- /dev/null +++ b/dots/.config/quickshell/ii/panelFamilies/HeftyHypeFamily.qml @@ -0,0 +1,48 @@ +import QtQuick +import Quickshell + +import qs.modules.common +import qs.modules.ii.background +import qs.modules.ii.cheatsheet +import qs.modules.ii.dock +import qs.modules.ii.lock +import qs.modules.ii.mediaControls +import qs.modules.ii.notificationPopup +import qs.modules.ii.onScreenDisplay +import qs.modules.ii.onScreenKeyboard +import qs.modules.ii.overview +import qs.modules.ii.polkit +import qs.modules.ii.regionSelector +import qs.modules.ii.screenCorners +import qs.modules.ii.sessionScreen +import qs.modules.ii.sidebarLeft +import qs.modules.ii.sidebarRight +import qs.modules.ii.overlay +import qs.modules.ii.verticalBar +import qs.modules.ii.wallpaperSelector + +import qs.modules.hefty.topLayer + +Scope { + // New + PanelLoader { component: HTopLayer {} } + + // Fallbacks + PanelLoader { component: Background {} } + PanelLoader { component: Cheatsheet {} } + PanelLoader { extraCondition: Config.options.dock.enable; component: Dock {} } + PanelLoader { component: Lock {} } + PanelLoader { component: MediaControls {} } + PanelLoader { component: NotificationPopup {} } + PanelLoader { component: OnScreenDisplay {} } + PanelLoader { component: OnScreenKeyboard {} } + PanelLoader { component: Overlay {} } + // PanelLoader { component: Overview {} } + PanelLoader { component: Polkit {} } + PanelLoader { component: RegionSelector {} } + PanelLoader { component: ScreenCorners {} } + PanelLoader { component: SessionScreen {} } + PanelLoader { component: SidebarLeft {} } + PanelLoader { component: SidebarRight {} } + PanelLoader { component: WallpaperSelector {} } +} diff --git a/dots/.config/quickshell/ii/shell.qml b/dots/.config/quickshell/ii/shell.qml index a1cb57baa..96d469c03 100644 --- a/dots/.config/quickshell/ii/shell.qml +++ b/dots/.config/quickshell/ii/shell.qml @@ -34,7 +34,7 @@ ShellRoot { // Panel families - property list families: ["ii", "waffle"] + property list families: ["ii", "waffle", "hefty"] function cyclePanelFamily() { const currentIndex = families.indexOf(Config.options.panelFamily) const nextIndex = (currentIndex + 1) % families.length @@ -57,6 +57,11 @@ ShellRoot { component: WaffleFamily {} } + PanelFamilyLoader { + identifier: "hefty" + component: HeftyHypeFamily {} + } + // Shortcuts IpcHandler {