Rearrange for tidier structure (#2212)

This commit is contained in:
clsty
2025-10-16 07:19:55 +08:00
parent 13065d7e5a
commit 8b493e091d
529 changed files with 165 additions and 138 deletions
@@ -0,0 +1,148 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell.Io
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland
Scope { // Scope
id: root
property bool pinned: Config.options?.dock.pinnedOnStartup ?? false
Variants {
// For each monitor
model: Quickshell.screens
PanelWindow {
id: dockRoot
// Window
required property var modelData
screen: modelData
visible: !GlobalStates.screenLocked
property bool reveal: root.pinned || (Config.options?.dock.hoverToReveal && dockMouseArea.containsMouse) || dockApps.requestDockShow || (!ToplevelManager.activeToplevel?.activated)
anchors {
bottom: true
left: true
right: true
}
exclusiveZone: root.pinned ? implicitHeight - (Appearance.sizes.hyprlandGapsOut) - (Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut) : 0
implicitWidth: dockBackground.implicitWidth
WlrLayershell.namespace: "quickshell:dock"
color: "transparent"
implicitHeight: (Config.options?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut
mask: Region {
item: dockMouseArea
}
MouseArea {
id: dockMouseArea
height: parent.height
anchors {
top: parent.top
topMargin: dockRoot.reveal ? 0 : Config.options?.dock.hoverToReveal ? (dockRoot.implicitHeight - Config.options.dock.hoverRegionHeight) : (dockRoot.implicitHeight + 1)
horizontalCenter: parent.horizontalCenter
}
implicitWidth: dockHoverRegion.implicitWidth + Appearance.sizes.elevationMargin * 2
hoverEnabled: true
Behavior on anchors.topMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Item {
id: dockHoverRegion
anchors.fill: parent
implicitWidth: dockBackground.implicitWidth
Item { // Wrapper for the dock background
id: dockBackground
anchors {
top: parent.top
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
implicitWidth: dockRow.implicitWidth + 5 * 2
height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
StyledRectangularShadow {
target: dockVisualBackground
}
Rectangle { // The real rectangle that is visible
id: dockVisualBackground
property real margin: Appearance.sizes.elevationMargin
anchors.fill: parent
anchors.topMargin: Appearance.sizes.elevationMargin
anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut
color: Appearance.colors.colLayer0
border.width: 1
border.color: Appearance.colors.colLayer0Border
radius: Appearance.rounding.large
}
RowLayout {
id: dockRow
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 3
property real padding: 5
VerticalButtonGroup {
Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work
GroupButton {
// Pin button
baseWidth: 35
baseHeight: 35
clickedWidth: baseWidth
clickedHeight: baseHeight + 20
buttonRadius: Appearance.rounding.normal
toggled: root.pinned
onClicked: root.pinned = !root.pinned
contentItem: MaterialSymbol {
text: "keep"
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0
}
}
}
DockSeparator {}
DockApps {
id: dockApps
buttonPadding: dockRow.padding
}
DockSeparator {}
DockButton {
Layout.fillHeight: true
onClicked: GlobalStates.overviewOpen = !GlobalStates.overviewOpen
topInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding
bottomInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding
contentItem: MaterialSymbol {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
font.pixelSize: parent.width / 2
text: "apps"
color: Appearance.colors.colOnLayer0
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,136 @@
import qs.services
import qs.modules.common
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
DockButton {
id: root
property var appToplevel
property var appListRoot
property int lastFocused: -1
property real iconSize: 35
property real countDotWidth: 10
property real countDotHeight: 4
property bool appIsActive: appToplevel.toplevels.find(t => (t.activated == true)) !== undefined
property bool isSeparator: appToplevel.appId === "SEPARATOR"
property var desktopEntry: DesktopEntries.heuristicLookup(appToplevel.appId)
enabled: !isSeparator
implicitWidth: isSeparator ? 1 : implicitHeight - topInset - bottomInset
Loader {
active: isSeparator
anchors {
fill: parent
topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
}
sourceComponent: DockSeparator {}
}
Loader {
anchors.fill: parent
active: appToplevel.toplevels.length > 0
sourceComponent: MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onEntered: {
appListRoot.lastHoveredButton = root
appListRoot.buttonHovered = true
lastFocused = appToplevel.toplevels.length - 1
}
onExited: {
if (appListRoot.lastHoveredButton === root) {
appListRoot.buttonHovered = false
}
}
}
}
onClicked: {
if (appToplevel.toplevels.length === 0) {
root.desktopEntry?.execute();
return;
}
lastFocused = (lastFocused + 1) % appToplevel.toplevels.length
appToplevel.toplevels[lastFocused].activate()
}
middleClickAction: () => {
root.desktopEntry?.execute();
}
altAction: () => {
if (Config.options.dock.pinnedApps.indexOf(appToplevel.appId) !== -1) {
Config.options.dock.pinnedApps = Config.options.dock.pinnedApps.filter(id => id !== appToplevel.appId)
} else {
Config.options.dock.pinnedApps = Config.options.dock.pinnedApps.concat([appToplevel.appId])
}
}
contentItem: Loader {
active: !isSeparator
sourceComponent: Item {
anchors.centerIn: parent
Loader {
id: iconImageLoader
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
active: !root.isSeparator
sourceComponent: IconImage {
source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing")
implicitSize: root.iconSize
}
}
Loader {
active: Config.options.dock.monochromeIcons
anchors.fill: iconImageLoader
sourceComponent: Item {
Desaturate {
id: desaturatedIcon
visible: false // There's already color overlay
anchors.fill: parent
source: iconImageLoader
desaturation: 0.8
}
ColorOverlay {
anchors.fill: desaturatedIcon
source: desaturatedIcon
color: ColorUtils.transparentize(Appearance.colors.colPrimary, 0.9)
}
}
}
RowLayout {
spacing: 3
anchors {
top: iconImageLoader.bottom
topMargin: 2
horizontalCenter: parent.horizontalCenter
}
Repeater {
model: Math.min(appToplevel.toplevels.length, 3)
delegate: Rectangle {
required property int index
radius: Appearance.rounding.full
implicitWidth: (appToplevel.toplevels.length <= 3) ?
root.countDotWidth : root.countDotHeight // Circles when too many
implicitHeight: root.countDotHeight
color: appIsActive ? Appearance.colors.colPrimary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4)
}
}
}
}
}
}
@@ -0,0 +1,265 @@
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
Item {
id: root
property real maxWindowPreviewHeight: 200
property real maxWindowPreviewWidth: 300
property real windowControlsHeight: 30
property real buttonPadding: 5
property Item lastHoveredButton
property bool buttonHovered: false
property bool requestDockShow: previewPopup.show
Layout.fillHeight: true
Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work
implicitWidth: listView.implicitWidth
StyledListView {
id: listView
spacing: 2
orientation: ListView.Horizontal
anchors {
top: parent.top
bottom: parent.bottom
}
implicitWidth: contentWidth
Behavior on implicitWidth {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
model: ScriptModel {
objectProp: "appId"
values: {
var map = new Map();
// Pinned apps
const pinnedApps = Config.options?.dock.pinnedApps ?? [];
for (const appId of pinnedApps) {
if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({
pinned: true,
toplevels: []
}));
}
// Separator
if (pinnedApps.length > 0) {
map.set("SEPARATOR", { pinned: false, toplevels: [] });
}
// Ignored apps
const ignoredRegexStrings = Config.options?.dock.ignoredAppRegexes ?? [];
const ignoredRegexes = ignoredRegexStrings.map(pattern => new RegExp(pattern, "i"));
// Open windows
for (const toplevel of ToplevelManager.toplevels.values) {
if (ignoredRegexes.some(re => re.test(toplevel.appId))) continue;
if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), ({
pinned: false,
toplevels: []
}));
map.get(toplevel.appId.toLowerCase()).toplevels.push(toplevel);
}
var values = [];
for (const [key, value] of map) {
values.push({ appId: key, toplevels: value.toplevels, pinned: value.pinned });
}
return values;
}
}
delegate: DockAppButton {
required property var modelData
appToplevel: modelData
appListRoot: root
topInset: Appearance.sizes.hyprlandGapsOut + root.buttonPadding
bottomInset: Appearance.sizes.hyprlandGapsOut + root.buttonPadding
}
}
PopupWindow {
id: previewPopup
property var appTopLevel: root.lastHoveredButton?.appToplevel
property bool allPreviewsReady: false
Connections {
target: root
function onLastHoveredButtonChanged() {
previewPopup.allPreviewsReady = false; // Reset readiness when the hovered button changes
}
}
function updatePreviewReadiness() {
for(var i = 0; i < previewRowLayout.children.length; i++) {
const view = previewRowLayout.children[i];
if (view.hasContent === false) {
allPreviewsReady = false;
return;
}
}
allPreviewsReady = true;
}
property bool shouldShow: {
const hoverConditions = (popupMouseArea.containsMouse || root.buttonHovered)
return hoverConditions && allPreviewsReady;
}
property bool show: false
onShouldShowChanged: {
if (shouldShow) {
// show = true;
updateTimer.restart();
} else {
updateTimer.restart();
}
}
Timer {
id: updateTimer
interval: 100
onTriggered: {
previewPopup.show = previewPopup.shouldShow
}
}
anchor {
window: root.QsWindow.window
adjustment: PopupAdjustment.None
gravity: Edges.Top | Edges.Right
edges: Edges.Top | Edges.Left
}
visible: popupBackground.visible
color: "transparent"
implicitWidth: root.QsWindow.window?.width ?? 1
implicitHeight: popupMouseArea.implicitHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2
MouseArea {
id: popupMouseArea
anchors.bottom: parent.bottom
implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
implicitHeight: root.maxWindowPreviewHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2
hoverEnabled: true
x: {
const itemCenter = root.QsWindow?.mapFromItem(root.lastHoveredButton, root.lastHoveredButton?.width / 2, 0);
return itemCenter.x - width / 2
}
StyledRectangularShadow {
target: popupBackground
opacity: previewPopup.show ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
Rectangle {
id: popupBackground
property real padding: 5
opacity: previewPopup.show ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
clip: true
color: Appearance.colors.colSurfaceContainer
radius: Appearance.rounding.normal
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.sizes.elevationMargin
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: previewRowLayout.implicitHeight + padding * 2
implicitWidth: previewRowLayout.implicitWidth + padding * 2
Behavior on implicitWidth {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
RowLayout {
id: previewRowLayout
anchors.centerIn: parent
Repeater {
model: ScriptModel {
values: previewPopup.appTopLevel?.toplevels ?? []
}
RippleButton {
id: windowButton
required property var modelData
padding: 0
middleClickAction: () => {
windowButton.modelData?.close();
}
onClicked: {
windowButton.modelData?.activate();
}
contentItem: ColumnLayout {
implicitWidth: screencopyView.implicitWidth
implicitHeight: screencopyView.implicitHeight
ButtonGroup {
contentWidth: parent.width - anchors.margins * 2
WrapperRectangle {
Layout.fillWidth: true
color: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
radius: Appearance.rounding.small
margin: 5
StyledText {
Layout.fillWidth: true
font.pixelSize: Appearance.font.pixelSize.small
text: windowButton.modelData?.title
elide: Text.ElideRight
color: Appearance.m3colors.m3onSurface
}
}
GroupButton {
id: closeButton
colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
baseWidth: windowControlsHeight
baseHeight: windowControlsHeight
buttonRadius: Appearance.rounding.full
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "close"
iconSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSurface
}
onClicked: {
windowButton.modelData?.close();
}
}
}
ScreencopyView {
id: screencopyView
captureSource: previewPopup ? windowButton.modelData : null
live: true
paintCursor: true
constraintSize: Qt.size(root.maxWindowPreviewWidth, root.maxWindowPreviewHeight)
onHasContentChanged: {
previewPopup.updatePreviewReadiness();
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: screencopyView.width
height: screencopyView.height
radius: Appearance.rounding.small
}
}
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,13 @@
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Layouts
RippleButton {
Layout.fillHeight: true
Layout.topMargin: Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
implicitWidth: implicitHeight - topInset - bottomInset
buttonRadius: Appearance.rounding.normal
background.implicitHeight: 50
}
@@ -0,0 +1,11 @@
import qs.modules.common
import QtQuick
import QtQuick.Layouts
Rectangle {
Layout.topMargin: Appearance.sizes.elevationMargin + dockRow.padding + Appearance.rounding.normal
Layout.bottomMargin: Appearance.sizes.hyprlandGapsOut + dockRow.padding + Appearance.rounding.normal
Layout.fillHeight: true
implicitWidth: 1
color: Appearance.colors.colOutlineVariant
}