mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-07 19:59:14 -04:00
control center: improve drag handling
misc: fix layer shell enum usage
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property var log: Log.scoped("LayerShell")
|
||||
|
||||
function _toLayer(name) {
|
||||
switch (name) {
|
||||
case "background":
|
||||
return WlrLayer.Background;
|
||||
case "bottom":
|
||||
return WlrLayer.Bottom;
|
||||
case "top":
|
||||
return WlrLayer.Top;
|
||||
case "overlay":
|
||||
return WlrLayer.Overlay;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function _toName(layer) {
|
||||
switch (layer) {
|
||||
case WlrLayer.Background:
|
||||
return "background";
|
||||
case WlrLayer.Bottom:
|
||||
return "bottom";
|
||||
case WlrLayer.Top:
|
||||
return "top";
|
||||
case WlrLayer.Overlay:
|
||||
return "overlay";
|
||||
}
|
||||
return "top";
|
||||
}
|
||||
|
||||
// Resolve a WlrLayer from a DMS_*_LAYER env override.
|
||||
// name: env var to read, e.g. "DMS_OSD_LAYER"
|
||||
// fallback: WlrLayer used when the var is unset or unrecognized
|
||||
// opts (optional):
|
||||
// allow: array of honored layer names; recognized names outside it
|
||||
// are treated as invalid
|
||||
// invalidLayer: WlrLayer used for a recognized-but-disallowed value
|
||||
// (default: fallback)
|
||||
// label: context for the diagnostic, e.g. "OSDs"; omit to stay silent
|
||||
// error: log at error level instead of warn
|
||||
function fromEnv(name, fallback, opts) {
|
||||
const value = Quickshell.env(name);
|
||||
if (!value)
|
||||
return fallback;
|
||||
|
||||
const requested = _toLayer(value);
|
||||
if (requested === undefined)
|
||||
return fallback;
|
||||
|
||||
const allow = opts?.allow;
|
||||
if (!allow || allow.indexOf(value) !== -1)
|
||||
return requested;
|
||||
|
||||
const invalid = opts?.invalidLayer ?? fallback;
|
||||
if (opts?.label) {
|
||||
const msg = `'${value}' layer is not valid for ${opts.label}. Defaulting to '${_toName(invalid)}' layer.`;
|
||||
if (opts?.error)
|
||||
log.error(msg);
|
||||
else
|
||||
log.warn(msg);
|
||||
}
|
||||
return invalid;
|
||||
}
|
||||
|
||||
// For call sites that only need "is the override the overlay layer?".
|
||||
// Honors "overlay" (true) and bottom/background/top (false); anything else
|
||||
// returns `fallback`.
|
||||
function envUsesOverlay(name, fallback) {
|
||||
switch (Quickshell.env(name)) {
|
||||
case "overlay":
|
||||
return true;
|
||||
case "bottom":
|
||||
case "background":
|
||||
case "top":
|
||||
return false;
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -497,22 +497,12 @@ Item {
|
||||
}
|
||||
|
||||
WlrLayershell.namespace: root.layerNamespace
|
||||
WlrLayershell.layer: {
|
||||
if (root.useOverlayLayer)
|
||||
return WlrLayershell.Overlay;
|
||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||
case "bottom":
|
||||
log.error("'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
log.error("'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
WlrLayershell.layer: root.useOverlayLayer ? WlrLayer.Overlay : LayerShell.fromEnv("DMS_MODAL_LAYER", WlrLayer.Top, {
|
||||
"allow": ["top", "overlay"],
|
||||
"invalidLayer": WlrLayer.Top,
|
||||
"label": "modals",
|
||||
"error": true
|
||||
})
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (customKeyboardFocus !== null)
|
||||
|
||||
@@ -251,22 +251,12 @@ Item {
|
||||
}
|
||||
|
||||
WlrLayershell.namespace: root.layerNamespace
|
||||
WlrLayershell.layer: {
|
||||
if (root.useOverlayLayer)
|
||||
return WlrLayershell.Overlay;
|
||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||
case "bottom":
|
||||
log.error("'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
log.error("'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
WlrLayershell.layer: root.useOverlayLayer ? WlrLayer.Overlay : LayerShell.fromEnv("DMS_MODAL_LAYER", WlrLayer.Top, {
|
||||
"allow": ["top", "overlay"],
|
||||
"invalidLayer": WlrLayer.Top,
|
||||
"label": "modals",
|
||||
"error": true
|
||||
})
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (customKeyboardFocus !== null)
|
||||
|
||||
@@ -42,20 +42,12 @@ Item {
|
||||
readonly property real screenHeight: effectiveScreen?.height ?? 1080
|
||||
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
||||
readonly property bool usesOverlayLayer: SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
|
||||
readonly property var effectiveLauncherLayer: {
|
||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||
case "bottom":
|
||||
log.error("'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
log.error("'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return root.usesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
||||
"allow": ["top", "overlay"],
|
||||
"invalidLayer": WlrLayer.Top,
|
||||
"label": "modals",
|
||||
"error": true
|
||||
})
|
||||
|
||||
readonly property int baseWidth: {
|
||||
switch (SettingsData.dankLauncherV2Size) {
|
||||
|
||||
@@ -32,20 +32,12 @@ Item {
|
||||
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
||||
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
|
||||
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
|
||||
readonly property var effectiveLauncherLayer: {
|
||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||
case "bottom":
|
||||
log.error("'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
log.error("'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return root.usesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
||||
"allow": ["top", "overlay"],
|
||||
"invalidLayer": WlrLayer.Top,
|
||||
"label": "modals",
|
||||
"error": true
|
||||
})
|
||||
|
||||
readonly property int _openDuration: 50
|
||||
readonly property int _closeDuration: 40
|
||||
|
||||
@@ -81,20 +81,12 @@ Item {
|
||||
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
|
||||
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
|
||||
readonly property var effectiveLauncherLayer: {
|
||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||
case "bottom":
|
||||
log.error("'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
log.error("'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return root.usesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
||||
"allow": ["top", "overlay"],
|
||||
"invalidLayer": WlrLayer.Top,
|
||||
"label": "modals",
|
||||
"error": true
|
||||
})
|
||||
readonly property real cornerRadius: Theme.cornerRadius
|
||||
readonly property color borderColor: {
|
||||
if (!SettingsData.dankLauncherV2BorderEnabled)
|
||||
|
||||
@@ -54,6 +54,8 @@ Column {
|
||||
}
|
||||
|
||||
readonly property real targetImplicitHeight: {
|
||||
if (editMode)
|
||||
return editModeGrid.implicitHeight;
|
||||
const rows = layoutResult.rows;
|
||||
let totalHeight = 0;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
@@ -106,8 +108,40 @@ Column {
|
||||
item.z = 1000;
|
||||
}
|
||||
|
||||
function componentForWidget(widgetData) {
|
||||
const id = widgetData.id || "";
|
||||
const widgetWidth = widgetData.width || 50;
|
||||
if (id.startsWith("builtin_"))
|
||||
return builtinPluginWidgetComponent;
|
||||
if (id.startsWith("plugin_"))
|
||||
return pluginWidgetComponent;
|
||||
switch (id) {
|
||||
case "wifi":
|
||||
case "bluetooth":
|
||||
case "audioOutput":
|
||||
case "audioInput":
|
||||
return compoundPillComponent;
|
||||
case "volumeSlider":
|
||||
return audioSliderComponent;
|
||||
case "brightnessSlider":
|
||||
return brightnessSliderComponent;
|
||||
case "inputVolumeSlider":
|
||||
return inputAudioSliderComponent;
|
||||
case "battery":
|
||||
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent;
|
||||
case "diskUsage":
|
||||
return widgetWidth <= 25 ? smallDiskUsageComponent : diskUsagePillComponent;
|
||||
case "colorPicker":
|
||||
return colorPickerPillComponent;
|
||||
case "doNotDisturb":
|
||||
return widgetWidth <= 25 ? smallToggleComponent : dndPillComponent;
|
||||
default:
|
||||
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent;
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.layoutResult.rows
|
||||
model: root.editMode ? [] : root.layoutResult.rows
|
||||
|
||||
Column {
|
||||
width: root.width
|
||||
@@ -174,32 +208,7 @@ Column {
|
||||
return id === "volumeSlider" || id === "brightnessSlider" || id === "inputVolumeSlider";
|
||||
}
|
||||
|
||||
widgetComponent: {
|
||||
const id = modelData.id || "";
|
||||
if (id.startsWith("builtin_")) {
|
||||
return builtinPluginWidgetComponent;
|
||||
} else if (id.startsWith("plugin_")) {
|
||||
return pluginWidgetComponent;
|
||||
} else if (id === "wifi" || id === "bluetooth" || id === "audioOutput" || id === "audioInput") {
|
||||
return compoundPillComponent;
|
||||
} else if (id === "volumeSlider") {
|
||||
return audioSliderComponent;
|
||||
} else if (id === "brightnessSlider") {
|
||||
return brightnessSliderComponent;
|
||||
} else if (id === "inputVolumeSlider") {
|
||||
return inputAudioSliderComponent;
|
||||
} else if (id === "battery") {
|
||||
return widgetWidth <= 25 ? smallBatteryComponent : batteryPillComponent;
|
||||
} else if (id === "diskUsage") {
|
||||
return widgetWidth <= 25 ? smallDiskUsageComponent : diskUsagePillComponent;
|
||||
} else if (id === "colorPicker") {
|
||||
return colorPickerPillComponent;
|
||||
} else if (id === "doNotDisturb") {
|
||||
return widgetWidth <= 25 ? smallToggleComponent : dndPillComponent;
|
||||
} else {
|
||||
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent;
|
||||
}
|
||||
}
|
||||
widgetComponent: root.componentForWidget(modelData)
|
||||
|
||||
onWidgetMoved: (fromIndex, toIndex) => root.moveWidget(fromIndex, toIndex)
|
||||
onRemoveWidget: index => root.removeWidget(index)
|
||||
@@ -279,6 +288,18 @@ Column {
|
||||
}
|
||||
}
|
||||
|
||||
EditModeGrid {
|
||||
id: editModeGrid
|
||||
width: root.width
|
||||
visible: root.editMode
|
||||
active: root.editMode
|
||||
model: root.model
|
||||
componentProvider: root
|
||||
onRemoveWidget: index => root.removeWidget(index)
|
||||
onToggleWidgetSize: index => root.toggleWidgetSize(index)
|
||||
onConfigRequested: (idx, data, anchor) => root.configRequested(idx, data, anchor)
|
||||
}
|
||||
|
||||
Component {
|
||||
id: errorPillComponent
|
||||
ErrorPill {
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.ControlCenter.Components
|
||||
import "../utils/layout.js" as LayoutUtils
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var model: null
|
||||
property var componentProvider: null
|
||||
property bool active: true
|
||||
|
||||
signal removeWidget(int index)
|
||||
signal toggleWidgetSize(int index)
|
||||
signal configRequested(int index, var widgetData, var anchor)
|
||||
|
||||
property var sourceWidgets: SettingsData.controlCenterWidgets || []
|
||||
property var visualOrder: []
|
||||
property int draggingSourceIndex: -1
|
||||
property var dragStartOrder: []
|
||||
|
||||
readonly property real rowSpacing: Theme.spacingL
|
||||
readonly property real sliderCellHeight: 48
|
||||
readonly property real normalCellHeight: 60
|
||||
|
||||
readonly property var slotLayout: LayoutUtils.computeSlots(sourceWidgets, visualOrder, width, Theme.spacingS, rowSpacing, sliderCellHeight, normalCellHeight)
|
||||
|
||||
implicitHeight: slotLayout.totalHeight
|
||||
|
||||
function rebuildOrder() {
|
||||
const n = (sourceWidgets || []).length;
|
||||
const arr = [];
|
||||
for (var i = 0; i < n; i++)
|
||||
arr.push(i);
|
||||
visualOrder = arr;
|
||||
}
|
||||
|
||||
onSourceWidgetsChanged: rebuildOrder()
|
||||
Component.onCompleted: rebuildOrder()
|
||||
|
||||
function beginDrag(sourceIndex) {
|
||||
draggingSourceIndex = sourceIndex;
|
||||
dragStartOrder = visualOrder.slice();
|
||||
}
|
||||
|
||||
function sameOrder(a, b) {
|
||||
if (a.length !== b.length)
|
||||
return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateDragTarget(centerX, centerY) {
|
||||
if (draggingSourceIndex < 0)
|
||||
return;
|
||||
const p = LayoutUtils.slotContainingPoint(slotLayout.slots, visualOrder, centerX, centerY);
|
||||
if (p < 0)
|
||||
return;
|
||||
const arr = visualOrder.slice();
|
||||
const d = arr.indexOf(draggingSourceIndex);
|
||||
if (d < 0 || d === p)
|
||||
return;
|
||||
arr.splice(d, 1);
|
||||
arr.splice(p, 0, draggingSourceIndex);
|
||||
visualOrder = arr;
|
||||
}
|
||||
|
||||
function endDrag() {
|
||||
if (draggingSourceIndex < 0)
|
||||
return;
|
||||
draggingSourceIndex = -1;
|
||||
if (!sameOrder(visualOrder, dragStartOrder))
|
||||
commit();
|
||||
}
|
||||
|
||||
function commit() {
|
||||
const widgets = sourceWidgets || [];
|
||||
const arr = visualOrder.map(i => widgets[i]);
|
||||
if (root.model)
|
||||
root.model.reorderWidgets(arr);
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.active ? root.sourceWidgets : []
|
||||
|
||||
EditModeWidgetDelegate {
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
grid: root
|
||||
sourceIndex: index
|
||||
widgetData: modelData
|
||||
isSlider: LayoutUtils.isSliderWidget(modelData.id || "")
|
||||
widgetComponent: root.componentProvider ? root.componentProvider.componentForWidget(modelData) : null
|
||||
|
||||
slotX: root.slotLayout.slots[index] ? root.slotLayout.slots[index].x : 0
|
||||
slotY: root.slotLayout.slots[index] ? root.slotLayout.slots[index].y : 0
|
||||
cellW: root.slotLayout.slots[index] ? root.slotLayout.slots[index].w : root.width
|
||||
cellH: root.slotLayout.slots[index] ? root.slotLayout.slots[index].h : root.normalCellHeight
|
||||
|
||||
onRemoveWidget: idx => root.removeWidget(idx)
|
||||
onToggleWidgetSize: idx => root.toggleWidgetSize(idx)
|
||||
onConfigRequested: (idx, data, anchor) => root.configRequested(idx, data, anchor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var grid: null
|
||||
property int sourceIndex: -1
|
||||
property var widgetData: null
|
||||
property Component widgetComponent: null
|
||||
property bool isSlider: false
|
||||
|
||||
property real slotX: 0
|
||||
property real slotY: 0
|
||||
property real cellW: 100
|
||||
property real cellH: 60
|
||||
|
||||
property bool dragging: !!grid && grid.draggingSourceIndex === sourceIndex
|
||||
|
||||
signal removeWidget(int index)
|
||||
signal toggleWidgetSize(int index)
|
||||
signal configRequested(int index, var widgetData, var anchor)
|
||||
|
||||
width: cellW
|
||||
height: cellH
|
||||
z: dragging ? 10000 : 1
|
||||
|
||||
Binding {
|
||||
target: root
|
||||
property: "x"
|
||||
value: root.slotX
|
||||
when: !root.dragging
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
Binding {
|
||||
target: root
|
||||
property: "y"
|
||||
value: root.slotY
|
||||
when: !root.dragging
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
onXChanged: {
|
||||
if (dragging && grid)
|
||||
grid.updateDragTarget(x + width / 2, y + height / 2);
|
||||
}
|
||||
onYChanged: {
|
||||
if (dragging && grid)
|
||||
grid.updateDragTarget(x + width / 2, y + height / 2);
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
enabled: !root.dragging
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
enabled: !root.dragging
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dragIndicator
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: Theme.primary
|
||||
border.width: root.dragging ? 2 : 0
|
||||
radius: Theme.cornerRadius
|
||||
opacity: root.dragging ? 0.8 : 1.0
|
||||
z: root.dragging ? 10000 : 1
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: widgetLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.widgetComponent
|
||||
property var widgetData: root.widgetData
|
||||
property int widgetIndex: root.sourceIndex
|
||||
property int globalWidgetIndex: root.sourceIndex
|
||||
property int widgetWidth: root.widgetData?.width || 50
|
||||
|
||||
MouseArea {
|
||||
id: editModeBlocker
|
||||
anchors.fill: parent
|
||||
enabled: true
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onPressed: function (mouse) {
|
||||
mouse.accepted = true;
|
||||
}
|
||||
onWheel: function (wheel) {
|
||||
wheel.accepted = true;
|
||||
}
|
||||
z: 100
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.OpenHandCursor
|
||||
drag.target: root
|
||||
drag.axis: Drag.XAndYAxis
|
||||
drag.smoothed: false
|
||||
|
||||
onPressed: function (mouse) {
|
||||
cursorShape = Qt.ClosedHandCursor;
|
||||
if (root.grid)
|
||||
root.grid.beginDrag(root.sourceIndex);
|
||||
}
|
||||
|
||||
onReleased: function (mouse) {
|
||||
cursorShape = Qt.OpenHandCursor;
|
||||
if (root.grid)
|
||||
root.grid.endDrag();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: removeButton
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 8
|
||||
color: Theme.error
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: -4
|
||||
z: 10
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "close"
|
||||
size: 12
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.removeWidget(root.sourceIndex)
|
||||
}
|
||||
}
|
||||
|
||||
SizeControls {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: -6
|
||||
z: 10
|
||||
currentSize: root.widgetData?.width || 50
|
||||
isSlider: root.isSlider
|
||||
widgetIndex: root.sourceIndex
|
||||
onSizeChanged: newSize => {
|
||||
var widgets = SettingsData.controlCenterWidgets.slice();
|
||||
if (root.sourceIndex >= 0 && root.sourceIndex < widgets.length) {
|
||||
widgets[root.sourceIndex].width = newSize;
|
||||
SettingsData.set("controlCenterWidgets", widgets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool hasConfigMenu: widgetData?.id === "diskUsage"
|
||||
|
||||
Rectangle {
|
||||
id: configButton
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 8
|
||||
color: Theme.primary
|
||||
anchors.top: removeButton.top
|
||||
anchors.right: removeButton.left
|
||||
anchors.rightMargin: 4
|
||||
visible: root.hasConfigMenu
|
||||
z: 10
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "settings"
|
||||
size: 12
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.configRequested(root.sourceIndex, root.widgetData, configButton)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
width: 16
|
||||
height: 12
|
||||
radius: 2
|
||||
color: Theme.primary
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 4
|
||||
z: 15
|
||||
opacity: root.dragging ? 1.0 : 0.7
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "drag_indicator"
|
||||
size: 10
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: "transparent"
|
||||
border.width: 0
|
||||
z: -1
|
||||
}
|
||||
}
|
||||
@@ -352,6 +352,10 @@ QtObject {
|
||||
WidgetUtils.moveWidget(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
function reorderWidgets(newOrder) {
|
||||
WidgetUtils.reorderWidgets(newOrder);
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
WidgetUtils.resetToDefault();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,71 @@
|
||||
function spanWidthFor(baseWidth, widgetWidth, spacing) {
|
||||
const w = widgetWidth || 50
|
||||
if (w <= 25)
|
||||
return (baseWidth - spacing * 3) / 4
|
||||
if (w <= 50)
|
||||
return (baseWidth - spacing) / 2
|
||||
if (w <= 75)
|
||||
return (baseWidth - spacing * 2) * 0.75
|
||||
return baseWidth
|
||||
}
|
||||
|
||||
function isSliderWidget(id) {
|
||||
return id === "volumeSlider" || id === "brightnessSlider" || id === "inputVolumeSlider"
|
||||
}
|
||||
|
||||
function computeSlots(widgets, order, baseWidth, spacing, rowSpacing, sliderHeight, normalHeight) {
|
||||
const slots = []
|
||||
let x = 0
|
||||
let y = 0
|
||||
let rowRight = 0
|
||||
let rowMaxH = 0
|
||||
let countInRow = 0
|
||||
|
||||
for (let p = 0; p < order.length; p++) {
|
||||
const sourceIndex = order[p]
|
||||
const widget = widgets[sourceIndex]
|
||||
if (!widget)
|
||||
continue
|
||||
|
||||
const itemW = spanWidthFor(baseWidth, widget.width, spacing)
|
||||
const itemH = isSliderWidget(widget.id || "") ? sliderHeight : normalHeight
|
||||
|
||||
if (countInRow > 0 && (rowRight + spacing + itemW > baseWidth + 0.5)) {
|
||||
y += rowMaxH + rowSpacing
|
||||
rowRight = 0
|
||||
rowMaxH = 0
|
||||
countInRow = 0
|
||||
}
|
||||
|
||||
x = countInRow === 0 ? 0 : rowRight + spacing
|
||||
slots[sourceIndex] = {
|
||||
"x": x,
|
||||
"y": y,
|
||||
"w": itemW,
|
||||
"h": itemH
|
||||
}
|
||||
rowRight = x + itemW
|
||||
rowMaxH = Math.max(rowMaxH, itemH)
|
||||
countInRow++
|
||||
}
|
||||
|
||||
return {
|
||||
"slots": slots,
|
||||
"totalHeight": y + rowMaxH
|
||||
}
|
||||
}
|
||||
|
||||
function slotContainingPoint(slots, order, px, py) {
|
||||
for (let p = 0; p < order.length; p++) {
|
||||
const s = slots[order[p]]
|
||||
if (!s)
|
||||
continue
|
||||
if (px >= s.x && px < s.x + s.w && py >= s.y && py < s.y + s.h)
|
||||
return p
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
function calculateRowsAndWidgets(controlCenterColumn, expandedSection, expandedWidgetIndex) {
|
||||
var rows = []
|
||||
var currentRow = []
|
||||
|
||||
@@ -110,20 +110,7 @@ PanelWindow {
|
||||
|
||||
readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(barWindow.screen) || (barConfig?.useOverlayLayer ?? false)
|
||||
|
||||
readonly property var dBarLayer: {
|
||||
switch (Quickshell.env("DMS_DANKBAR_LAYER")) {
|
||||
case "bottom":
|
||||
return WlrLayer.Bottom;
|
||||
case "overlay":
|
||||
return WlrLayer.Overlay;
|
||||
case "background":
|
||||
return WlrLayer.Background;
|
||||
case "top":
|
||||
return WlrLayer.Top;
|
||||
default:
|
||||
return barWindow.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top;
|
||||
}
|
||||
}
|
||||
readonly property var dBarLayer: LayerShell.fromEnv("DMS_DANKBAR_LAYER", barWindow.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top)
|
||||
|
||||
property var blurRegion: null
|
||||
property var _blurWidgetItems: []
|
||||
|
||||
@@ -156,28 +156,9 @@ PanelWindow {
|
||||
|
||||
visible: !_finalized
|
||||
WlrLayershell.layer: {
|
||||
const envLayer = Quickshell.env("DMS_NOTIFICATION_LAYER");
|
||||
if (envLayer) {
|
||||
switch (envLayer) {
|
||||
case "bottom":
|
||||
return WlrLayershell.Bottom;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
case "background":
|
||||
return WlrLayershell.Background;
|
||||
case "top":
|
||||
return WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
|
||||
if (!notificationData)
|
||||
return WlrLayershell.Top;
|
||||
|
||||
SettingsData.notificationOverlayEnabled;
|
||||
|
||||
const shouldUseOverlay = (SettingsData.notificationOverlayEnabled) || (notificationData.urgency === NotificationUrgency.Critical);
|
||||
|
||||
return shouldUseOverlay ? WlrLayershell.Overlay : WlrLayershell.Top;
|
||||
const shouldUseOverlay = notificationData && (SettingsData.notificationOverlayEnabled || notificationData.urgency === NotificationUrgency.Critical);
|
||||
const fallback = shouldUseOverlay ? WlrLayer.Overlay : WlrLayer.Top;
|
||||
return LayerShell.fromEnv("DMS_NOTIFICATION_LAYER", fallback);
|
||||
}
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -39,18 +38,7 @@ Item {
|
||||
readonly property real rightMargin: !isVerticalOrientation ? (isRightBarEdge && isLast ? barEdgeExtension : (isLast ? gapExtension : gapExtension / 2)) : 0
|
||||
readonly property real topMargin: isVerticalOrientation ? (isTopBarEdge && isFirst ? barEdgeExtension : (isFirst ? gapExtension : gapExtension / 2)) : 0
|
||||
readonly property real bottomMargin: isVerticalOrientation ? (isBottomBarEdge && isLast ? barEdgeExtension : (isLast ? gapExtension : gapExtension / 2)) : 0
|
||||
readonly property bool barUsesOverlayLayer: {
|
||||
switch (Quickshell.env("DMS_DANKBAR_LAYER")) {
|
||||
case "overlay":
|
||||
return true;
|
||||
case "bottom":
|
||||
case "background":
|
||||
case "top":
|
||||
return false;
|
||||
default:
|
||||
return (barConfig?.useOverlayLayer ?? false) || CompositorService.framePeerSurfacesUseOverlayForScreen(parentScreen);
|
||||
}
|
||||
}
|
||||
readonly property bool barUsesOverlayLayer: LayerShell.envUsesOverlay("DMS_DANKBAR_LAYER", (barConfig?.useOverlayLayer ?? false) || CompositorService.framePeerSurfacesUseOverlayForScreen(parentScreen))
|
||||
|
||||
signal clicked
|
||||
signal rightClicked(real rootX, real rootY)
|
||||
|
||||
@@ -92,20 +92,11 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
WlrLayershell.layer: {
|
||||
switch (Quickshell.env("DMS_OSD_LAYER")) {
|
||||
case "bottom":
|
||||
log.warn("'bottom' layer is not valid for OSDs. Defaulting to 'overlay' layer.");
|
||||
return WlrLayershell.Overlay;
|
||||
case "background":
|
||||
log.warn("'background' layer is not valid for OSDs. Defaulting to 'overlay' layer.");
|
||||
return WlrLayershell.Overlay;
|
||||
case "top":
|
||||
return WlrLayershell.Top;
|
||||
default:
|
||||
return WlrLayershell.Overlay;
|
||||
}
|
||||
}
|
||||
WlrLayershell.layer: LayerShell.fromEnv("DMS_OSD_LAYER", WlrLayer.Overlay, {
|
||||
"allow": ["top", "overlay"],
|
||||
"invalidLayer": WlrLayer.Overlay,
|
||||
"label": "OSDs"
|
||||
})
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -170,16 +169,7 @@ Item {
|
||||
}
|
||||
|
||||
function _triggerBarUsesOverlayLayer(targetScreen, barConfig) {
|
||||
switch (Quickshell.env("DMS_DANKBAR_LAYER")) {
|
||||
case "overlay":
|
||||
return true;
|
||||
case "bottom":
|
||||
case "background":
|
||||
case "top":
|
||||
return false;
|
||||
default:
|
||||
return (barConfig?.useOverlayLayer ?? false) || CompositorService.framePeerSurfacesUseOverlayForScreen(targetScreen);
|
||||
}
|
||||
return LayerShell.envUsesOverlay("DMS_DANKBAR_LAYER", (barConfig?.useOverlayLayer ?? false) || CompositorService.framePeerSurfacesUseOverlayForScreen(targetScreen));
|
||||
}
|
||||
|
||||
function setTriggerPosition(x, y, width, section, targetScreen, barPosition, barThickness, barSpacing, barConfig) {
|
||||
|
||||
@@ -57,20 +57,11 @@ Item {
|
||||
property var screen: null
|
||||
// Connected resize uses one full-screen surface; body-sized regions are masks.
|
||||
readonly property bool useBackgroundWindow: false
|
||||
readonly property var effectivePopoutLayer: {
|
||||
switch (Quickshell.env("DMS_POPOUT_LAYER")) {
|
||||
case "bottom":
|
||||
log.warn("'bottom' layer is not valid for popouts. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
log.warn("'background' layer is not valid for popouts. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return root.triggerUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
readonly property var effectivePopoutLayer: LayerShell.fromEnv("DMS_POPOUT_LAYER", root.triggerUsesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
||||
"allow": ["top", "overlay"],
|
||||
"invalidLayer": WlrLayer.Top,
|
||||
"label": "popouts"
|
||||
})
|
||||
|
||||
readonly property real effectiveBarThickness: {
|
||||
if (root.usesConnectedSurfaceChrome)
|
||||
|
||||
@@ -65,20 +65,11 @@ Item {
|
||||
readonly property bool backgroundDismissWindowRequired: backgroundInteractive
|
||||
readonly property bool backgroundWindowRequired: backgroundDismissWindowRequired || root.overlayContent !== null
|
||||
readonly property bool _fullHeight: fullHeightSurface
|
||||
readonly property var effectivePopoutLayer: {
|
||||
switch (Quickshell.env("DMS_POPOUT_LAYER")) {
|
||||
case "bottom":
|
||||
root.log.warn("'bottom' layer is not valid for popouts. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
root.log.warn("'background' layer is not valid for popouts. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return root.triggerUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
readonly property var effectivePopoutLayer: LayerShell.fromEnv("DMS_POPOUT_LAYER", root.triggerUsesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
||||
"allow": ["top", "overlay"],
|
||||
"invalidLayer": WlrLayer.Top,
|
||||
"label": "popouts"
|
||||
})
|
||||
|
||||
function _frameEdgeInset(side) {
|
||||
if (!screen)
|
||||
|
||||
@@ -221,6 +221,7 @@ def main():
|
||||
|
||||
client = LspClient([qmlls])
|
||||
changed = 0
|
||||
skipped = 0
|
||||
unused_by_file = {}
|
||||
try:
|
||||
client.request("initialize", {
|
||||
@@ -252,10 +253,22 @@ def main():
|
||||
},
|
||||
})
|
||||
|
||||
try:
|
||||
edits = client.request("textDocument/formatting", {
|
||||
"textDocument": {"uri": uri},
|
||||
"options": {"tabSize": TAB_SIZE, "insertSpaces": True},
|
||||
})
|
||||
except RuntimeError as exc:
|
||||
# qmlls (qmlformat's DOM) refuses some valid files — notably
|
||||
# "Cannot format invalid documents!" on constructs qmllint
|
||||
# accepts. Don't let one file's formatter bug abort the commit.
|
||||
client.notify("textDocument/didClose", {"textDocument": {"uri": uri}})
|
||||
if "invalid document" in str(exc).lower():
|
||||
print("skipped (qmlls rejected as invalid;")
|
||||
else:
|
||||
print(f"skipped ({exc})")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
client.notify("textDocument/didClose", {"textDocument": {"uri": uri}})
|
||||
|
||||
@@ -276,6 +289,8 @@ def main():
|
||||
unused_by_file[file] = findings
|
||||
|
||||
print(f"\n{changed} of {len(files)} file(s) changed.")
|
||||
if skipped:
|
||||
print(f"{skipped} file(s) skipped (could not be formatted; see above).")
|
||||
|
||||
if unused_by_file:
|
||||
print("\nUnused import warnings (informational, not auto-removed):")
|
||||
|
||||
Reference in New Issue
Block a user