mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-24 12:07:47 -04:00
refactor(dot): major performance & stability improvements
- Hyprland & Mango users will benefit most while niri gains solid QOL updates - New icon picker in dot settings - Dragging, flinging and opening the dot now run on one static surface so they stay smooth on every compositor Port 2.0
This commit is contained in:
1 parent
ba7b3620a8
commit
0cd03e0180
17 files changed
+565
-107
No files matched your search
+1
-1
Submodule dux-qml-common updated: abd63f1d32...d2d3c22750.
Generated
+3
-3
@@ -3,11 +3,11 @@
|
||||
"dank-qml-common": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1792696287,
|
||||
"narHash": "sha256-htrNweCLuw+3TQA2Vm7b4ETCuypIx85Vb3jt6k/DdBw=",
|
||||
"lastModified": 1792748104,
|
||||
"narHash": "sha256-GmN+sj6kPFADdJsKjFu4Xr+w8+/1wPyDZ9dAlKRHvrc=",
|
||||
"owner": "duxlabs-dev",
|
||||
"repo": "dux-qml-common",
|
||||
"rev": "abd63f1d329cbffd33ad0d28420944353bbf50ed",
|
||||
"rev": "d2d3c22750d54a3715fce05688632ca2fabd2daf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -2845,6 +2845,9 @@ Singleton {
|
||||
id: "dot" + Date.now(),
|
||||
name: I18n.tr("Dot", "bar layout: free-floating dot that opens island activities"),
|
||||
enabled: true,
|
||||
// A hidden or auto-hiding base bar would switch off the dot's OSD and notifications, and bar IPC refuses dot configs.
|
||||
visible: true,
|
||||
autoHide: false,
|
||||
island: false,
|
||||
dot: true,
|
||||
screenPreferences: ["all"],
|
||||
|
||||
@@ -8,6 +8,9 @@ var ISLAND_DEFAULTS = {
|
||||
islandPlacement: "edge",
|
||||
islandFreeSize: 48,
|
||||
islandFreeIcon: "blur_on",
|
||||
islandFreeIconMode: "icon",
|
||||
islandFreeIconPath: "",
|
||||
islandFreeIconColor: "primary",
|
||||
islandFreeEdgeMargin: 2,
|
||||
islandFreeIdleDelay: 2500,
|
||||
islandFreeIdleOpacity: 0.45,
|
||||
|
||||
@@ -3,20 +3,52 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.DCommon.Widgets
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var controller
|
||||
property string iconName: "blur_on"
|
||||
property string iconMode: "icon"
|
||||
property string iconPath: ""
|
||||
property string iconColor: "primary"
|
||||
|
||||
readonly property real diameter: Math.min(width, height)
|
||||
readonly property real faceSize: Math.max(Theme.fontSizeSmall, Math.round(root.diameter * 0.46))
|
||||
readonly property color symbolColor: {
|
||||
switch (root.iconColor) {
|
||||
case "":
|
||||
case "primary":
|
||||
return Theme.primary;
|
||||
case "surface":
|
||||
return Theme.surfaceText;
|
||||
default:
|
||||
return root.iconColor;
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: logoLoader
|
||||
|
||||
anchors.centerIn: parent
|
||||
active: root.iconMode !== "icon"
|
||||
|
||||
sourceComponent: LauncherLogo {
|
||||
mode: root.iconMode
|
||||
size: root.faceSize
|
||||
colorOverride: root.iconColor
|
||||
customPath: root.iconPath
|
||||
}
|
||||
}
|
||||
|
||||
// A logo mode with nothing to show (unknown compositor, empty custom path) falls back to the symbol.
|
||||
DIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !(logoLoader.item as LauncherLogo)?.resolvedMode
|
||||
name: root.iconName
|
||||
size: Math.max(Theme.fontSizeSmall, Math.round(root.diameter * 0.46))
|
||||
color: Theme.primary
|
||||
size: root.faceSize
|
||||
color: root.symbolColor
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
||||
@@ -31,8 +31,9 @@ Item {
|
||||
property string requestedWindow: ""
|
||||
property bool freeMode: false
|
||||
property bool anchorSnaps: false
|
||||
property real anchorX: 0
|
||||
property real anchorY: 0
|
||||
property point anchor
|
||||
// Velocity the next applyTarget seeds the spring with; a released drag flings from it.
|
||||
property var flingSeed: null
|
||||
property real freeMargin: 8
|
||||
// Measured from the band centre, not the band start.
|
||||
property real anchorAlong: 0
|
||||
@@ -220,8 +221,8 @@ Item {
|
||||
}
|
||||
if (!root.freeMode)
|
||||
return target;
|
||||
const x = root.clampFree(root.anchorX - target.width / 2, target.width, root.width);
|
||||
const y = root.clampFree(root.anchorY - target.height / 2, target.height, root.height);
|
||||
const x = root.clampFree(root.anchor.x - target.width / 2, target.width, root.width);
|
||||
const y = root.clampFree(root.anchor.y - target.height / 2, target.height, root.height);
|
||||
return Object.assign({}, target, {
|
||||
"offsetAlong": root.isVertical ? y + target.height / 2 - root.height / 2 : x + target.width / 2 - root.width / 2,
|
||||
"offsetCross": root.isVertical ? x : y
|
||||
@@ -238,7 +239,9 @@ Item {
|
||||
fadeCompactCross = root.descriptorCross(controller.compactTarget);
|
||||
if (motion.running)
|
||||
root.unionMotionStartBounds();
|
||||
motion.setTarget(root.resolveTarget(controller.targetDescriptor), seedVelocity);
|
||||
const seed = seedVelocity ?? root.flingSeed;
|
||||
root.flingSeed = null;
|
||||
motion.setTarget(root.resolveTarget(controller.targetDescriptor), seed);
|
||||
if (!motion.running)
|
||||
root.controller.releaseIdleVisuals();
|
||||
}
|
||||
@@ -261,8 +264,7 @@ Item {
|
||||
root.applyTarget();
|
||||
}
|
||||
|
||||
onAnchorXChanged: root.syncAnchor()
|
||||
onAnchorYChanged: root.syncAnchor()
|
||||
onAnchorChanged: root.syncAnchor()
|
||||
onAnchorAlongChanged: root.syncAnchor()
|
||||
onAlongExtentChanged: root.syncAnchor()
|
||||
onBandAlongStartChanged: root.syncAnchor()
|
||||
|
||||
@@ -39,8 +39,7 @@ Item {
|
||||
property var centerSectionRect: null
|
||||
property var trailingSectionRect: null
|
||||
readonly property int hostLayer: root.hostWindow?.dBarLayer ?? WlrLayer.Top
|
||||
property real anchorX: 0
|
||||
property real anchorY: 0
|
||||
property point anchor
|
||||
property bool anchorSnaps: false
|
||||
property real freeScale: 1
|
||||
property real freeOpacity: 1
|
||||
@@ -60,6 +59,7 @@ Item {
|
||||
readonly property real currentVisualY: surface.currentVisualY
|
||||
readonly property real currentVisualWidth: surface.currentVisualWidth
|
||||
readonly property real currentVisualHeight: surface.currentVisualHeight
|
||||
readonly property real currentSurfaceRadius: surface.currentSurfaceRadius
|
||||
readonly property color surfaceColor: surface.surfaceColor
|
||||
readonly property real surfaceOpacity: surface.surfaceOpacity
|
||||
readonly property bool inputSuspended: controller.inputSuspended
|
||||
@@ -68,7 +68,9 @@ Item {
|
||||
// Held from expand until the collapse spring settles, so compact-to-compact morphs never resize the bar window.
|
||||
property bool sheetOut: false
|
||||
readonly property bool surfaceFits: (root.isVertical ? root.windowWidth : root.windowHeight) >= root.hostThickness - 1
|
||||
readonly property bool hostReady: !root.embedded || root.frameHosted || root.surfaceFits
|
||||
// A free host lifts to the whole output for the sheet; the expand waits for that size so no frame of the morph is clipped.
|
||||
property bool hostLifted: true
|
||||
readonly property bool hostReady: root.freeMode ? root.hostLifted : (!root.embedded || root.frameHosted || root.surfaceFits)
|
||||
// Layer surfaces glitch when they resize under a moving sheet: edit room waits for a frame at the grown size, and the grown size outlives the shrink spring.
|
||||
readonly property bool surfaceResizes: !root.freeMode && !root.frameHosted
|
||||
readonly property int editSurfaceHeight: controller.editSurfaceHeight
|
||||
@@ -433,7 +435,8 @@ Item {
|
||||
homeBrightnessDisplay: SettingsData.islandLevelDisplay(root.barConfig, "islandHomeBrightnessDisplay")
|
||||
batteryStyle: root.setting("islandBatteryStyle")
|
||||
mediaClockVisible: root.setting("islandMediaClockVisible")
|
||||
mediaAutoSwitch: root.setting("islandMediaAutoSwitch")
|
||||
// Home and media share the dot face; see mediaFaceToggleAvailable.
|
||||
mediaAutoSwitch: !root.dotMode && root.setting("islandMediaAutoSwitch")
|
||||
launcherCycleEnabled: SettingsData.launcherStyle === "island"
|
||||
dashboardAvailableWidth: Math.max(0, (root.screen?.width ?? root.referenceScreenWidth) - (root.isVertical ? root.crossInsetStart + root.crossInsetEnd + root.outerGap + root.nearInset : root.alongInset) - Theme.spacingL * 2)
|
||||
dashboardAvailableHeight: Math.max(0, (root.screen?.height ?? root.referenceScreenHeight) - (root.isVertical ? root.alongInset : root.crossInsetStart + root.crossInsetEnd + root.outerGap + root.nearInset) - Theme.spacingL * 2)
|
||||
@@ -493,8 +496,7 @@ Item {
|
||||
hostOriginX: root.hostOriginX
|
||||
hostOriginY: root.hostOriginY
|
||||
freeMode: root.freeMode
|
||||
anchorX: root.anchorX
|
||||
anchorY: root.anchorY
|
||||
anchor: root.anchor
|
||||
anchorSnaps: root.anchorSnaps
|
||||
anchorAlong: root.anchorAlong
|
||||
nearInset: root.nearInset
|
||||
@@ -534,6 +536,9 @@ Item {
|
||||
DotCompact {
|
||||
controller: controller
|
||||
iconName: root.setting("islandFreeIcon")
|
||||
iconMode: root.setting("islandFreeIconMode")
|
||||
iconPath: root.setting("islandFreeIconPath")
|
||||
iconColor: root.setting("islandFreeIconColor")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -189,10 +189,11 @@ Item {
|
||||
component CompactFace: Loader {
|
||||
required property string activity
|
||||
required property Component face
|
||||
readonly property var target: root.resolveTarget(root.controller.compactTargetFor(activity))
|
||||
// A free face only centres in the island, so it skips the anchor-resolved slot every drag event would rebuild.
|
||||
readonly property var target: root.freeMode ? root.controller.compactTargetFor(activity) : root.resolveTarget(root.controller.compactTargetFor(activity))
|
||||
readonly property bool isVertical: root.controller.isVertical
|
||||
readonly property real alongPos: !active ? 0 : Math.round(root.slotAlong(target)) - Math.round(isVertical ? root.islandY : root.islandX)
|
||||
readonly property real crossPos: !active ? 0 : Math.round(root.slotCross(target)) - Math.round(isVertical ? root.islandX : root.islandY)
|
||||
readonly property real alongPos: !active || root.freeMode ? 0 : Math.round(root.slotAlong(target)) - Math.round(isVertical ? root.islandY : root.islandX)
|
||||
readonly property real crossPos: !active || root.freeMode ? 0 : Math.round(root.slotCross(target)) - Math.round(isVertical ? root.islandX : root.islandY)
|
||||
|
||||
x: root.freeMode ? Math.round((parent.width - width) / 2) : (isVertical ? crossPos : alongPos)
|
||||
y: root.freeMode ? Math.round((parent.height - height) / 2) : (isVertical ? alongPos : crossPos)
|
||||
@@ -209,11 +210,13 @@ Item {
|
||||
component ExpandedFace: Loader {
|
||||
required property string activity
|
||||
readonly property var target: root.controller.expandedTargetFor(activity)
|
||||
readonly property var slot: root.resolveTarget(target)
|
||||
// A hidden face stays put, so a kept-alive sheet is not re-laid out on every move of the pill.
|
||||
readonly property bool placed: active && visible
|
||||
readonly property var slot: placed ? root.resolveTarget(target) : target
|
||||
readonly property bool isVertical: root.controller.isVertical
|
||||
readonly property real inset: root.controller.farEdge ? 0 : root.expandedInset
|
||||
readonly property real alongPos: !active ? 0 : Math.round(root.slotAlong(slot)) - Math.round(isVertical ? root.islandY : root.islandX)
|
||||
readonly property real crossPos: !active ? 0 : Math.round(root.slotCross(slot)) - Math.round(isVertical ? root.islandX : root.islandY) + inset
|
||||
readonly property real alongPos: !placed ? 0 : Math.round(root.slotAlong(slot)) - Math.round(isVertical ? root.islandY : root.islandX)
|
||||
readonly property real crossPos: !placed ? 0 : Math.round(root.slotCross(slot)) - Math.round(isVertical ? root.islandX : root.islandY) + inset
|
||||
|
||||
x: isVertical ? crossPos : alongPos
|
||||
y: isVertical ? alongPos : crossPos
|
||||
|
||||
@@ -618,7 +618,8 @@ QtObject {
|
||||
return mediaAvailable && mediaPreferred ? "media" : "home";
|
||||
}
|
||||
|
||||
readonly property bool mediaFaceToggleAvailable: !expanded && mediaAvailable && (activeActivity === "home" || activeActivity === "media")
|
||||
// A dot draws the same face for home and media, so a toggle would only change what the next click opens.
|
||||
readonly property bool mediaFaceToggleAvailable: !dotMode && !expanded && mediaAvailable && (activeActivity === "home" || activeActivity === "media")
|
||||
|
||||
function toggleMediaFace() {
|
||||
if (!mediaFaceToggleAvailable)
|
||||
|
||||
@@ -10,52 +10,126 @@ DragHandler {
|
||||
|
||||
readonly property real maxFlingSpeed: 4000
|
||||
readonly property real flingProjection: 0.09
|
||||
property real startAnchorX: 0
|
||||
property real startAnchorY: 0
|
||||
// Single events are too noisy on high-rate mice, so the fling reads the stroke's last stretch.
|
||||
readonly property int velocityWindow: 60
|
||||
property point startAnchor
|
||||
property point startPointer
|
||||
property point startOrigin
|
||||
property point lastPointer
|
||||
property bool tracking: false
|
||||
property real velocityX: 0
|
||||
property real velocityY: 0
|
||||
property bool movePending: false
|
||||
property bool flingPending: false
|
||||
property point releaseVelocity
|
||||
property var samples: []
|
||||
readonly property bool hostLifted: root.hostWindow.lifted
|
||||
|
||||
onHostLiftedChanged: {
|
||||
if (root.hostLifted && root.flingPending)
|
||||
root.fling();
|
||||
}
|
||||
|
||||
// Scene positions are surface-local; events just after the lift can still carry the pre-lift origin, so the reading that continues the stroke wins.
|
||||
function pointerPosition() {
|
||||
return root.surface.mapToGlobal(root.centroid.position.x, root.centroid.position.y);
|
||||
const p = root.centroid.scenePosition;
|
||||
const current = Qt.point(p.x + root.hostWindow.frameLeft, p.y + root.hostWindow.frameTop);
|
||||
if (!root.tracking)
|
||||
return current;
|
||||
const previous = Qt.point(p.x + root.startOrigin.x, p.y + root.startOrigin.y);
|
||||
const last = root.lastPointer;
|
||||
return Math.hypot(current.x - last.x, current.y - last.y) <= Math.hypot(previous.x - last.x, previous.y - last.y) ? current : previous;
|
||||
}
|
||||
|
||||
function anchorFor(pointer) {
|
||||
return Qt.point(root.startAnchor.x + pointer.x - root.startPointer.x, root.startAnchor.y + pointer.y - root.startPointer.y);
|
||||
}
|
||||
|
||||
function rememberPress() {
|
||||
root.startAnchorX = root.surface.currentVisualX + root.surface.currentVisualWidth / 2;
|
||||
root.startAnchorY = root.surface.currentVisualY + root.surface.currentVisualHeight / 2;
|
||||
root.startAnchor = Qt.point(root.surface.currentVisualX + root.surface.currentVisualWidth / 2, root.surface.currentVisualY + root.surface.currentVisualHeight / 2);
|
||||
root.startOrigin = Qt.point(root.hostWindow.frameLeft, root.hostWindow.frameTop);
|
||||
root.startPointer = root.pointerPosition();
|
||||
root.velocityX = 0;
|
||||
root.velocityY = 0;
|
||||
root.lastPointer = root.startPointer;
|
||||
root.samples = [];
|
||||
root.tracking = true;
|
||||
}
|
||||
|
||||
function beginDrag() {
|
||||
if (!root.tracking)
|
||||
root.rememberPress();
|
||||
root.hostWindow.dragging = true;
|
||||
root.hostWindow.wake();
|
||||
root.updateDrag();
|
||||
function recordPointer() {
|
||||
const now = Date.now();
|
||||
root.lastPointer = root.pointerPosition();
|
||||
while (root.samples.length > 1 && now - root.samples[0].time > root.velocityWindow)
|
||||
root.samples.shift();
|
||||
root.samples.push({
|
||||
"time": now,
|
||||
"x": root.lastPointer.x,
|
||||
"y": root.lastPointer.y
|
||||
});
|
||||
root.movePending = true;
|
||||
}
|
||||
|
||||
function updateDrag() {
|
||||
const point = root.pointerPosition();
|
||||
root.velocityX = Math.max(-root.maxFlingSpeed, Math.min(root.maxFlingSpeed, root.centroid.velocity.x));
|
||||
root.velocityY = Math.max(-root.maxFlingSpeed, Math.min(root.maxFlingSpeed, root.centroid.velocity.y));
|
||||
root.hostWindow.anchorX = root.startAnchorX + point.x - root.startPointer.x;
|
||||
root.hostWindow.anchorY = root.startAnchorY + point.y - root.startPointer.y;
|
||||
function flingVelocity() {
|
||||
const now = Date.now();
|
||||
const recent = root.samples.filter(sample => now - sample.time <= root.velocityWindow);
|
||||
if (recent.length < 2)
|
||||
return Qt.point(0, 0);
|
||||
const first = recent[0];
|
||||
const last = recent[recent.length - 1];
|
||||
const seconds = (last.time - first.time) / 1000;
|
||||
if (seconds < 0.008)
|
||||
return Qt.point(0, 0);
|
||||
const clamp = value => Math.max(-root.maxFlingSpeed, Math.min(root.maxFlingSpeed, value));
|
||||
return Qt.point(clamp((last.x - first.x) / seconds), clamp((last.y - first.y) / seconds));
|
||||
}
|
||||
|
||||
// Re-based at drag start: a press just after a settle can read the old origin, and a flying dot has moved since the press.
|
||||
function beginDrag() {
|
||||
root.flingPending = false;
|
||||
root.tracking = false;
|
||||
root.rememberPress();
|
||||
root.hostWindow.dragging = true;
|
||||
root.hostWindow.wake();
|
||||
root.recordPointer();
|
||||
}
|
||||
|
||||
function applyMove() {
|
||||
root.movePending = false;
|
||||
root.hostWindow.anchor = root.anchorFor(root.lastPointer);
|
||||
}
|
||||
|
||||
function finishDrag() {
|
||||
root.hostWindow.dragging = false;
|
||||
root.hostWindow.anchorX = root.hostWindow.clampAnchorX(root.hostWindow.anchorX + root.velocityX * root.flingProjection);
|
||||
root.hostWindow.anchorY = root.hostWindow.clampAnchorY(root.hostWindow.anchorY + root.velocityY * root.flingProjection);
|
||||
root.surface.applyTarget({
|
||||
"offsetAlong": root.surface.isVertical ? root.velocityY : root.velocityX,
|
||||
"offsetCross": root.surface.isVertical ? root.velocityX : root.velocityY
|
||||
});
|
||||
root.hostWindow.storeAnchor();
|
||||
root.hostWindow.wake();
|
||||
const host = root.hostWindow;
|
||||
root.releaseVelocity = root.flingVelocity();
|
||||
root.movePending = false;
|
||||
root.tracking = false;
|
||||
// Raised before dragging drops, so the lift holds through the fling.
|
||||
host.flinging = true;
|
||||
host.dragging = false;
|
||||
// A release before the lift lands waits for it, or the fling would spring out of the old buffer.
|
||||
if (host.lifted)
|
||||
root.fling();
|
||||
else
|
||||
root.flingPending = true;
|
||||
}
|
||||
|
||||
function fling() {
|
||||
const host = root.hostWindow;
|
||||
root.flingPending = false;
|
||||
// An expand requested mid-drag disables this handler; the sheet must not open with the release velocity.
|
||||
if (root.surface.controller.expanded) {
|
||||
host.flinging = false;
|
||||
host.wake();
|
||||
return;
|
||||
}
|
||||
const velocity = root.releaseVelocity;
|
||||
const anchor = root.anchorFor(root.lastPointer);
|
||||
root.surface.flingSeed = {
|
||||
"offsetAlong": root.surface.isVertical ? velocity.y : velocity.x,
|
||||
"offsetCross": root.surface.isVertical ? velocity.x : velocity.y
|
||||
};
|
||||
host.anchor = Qt.point(host.clampAnchorX(anchor.x + velocity.x * root.flingProjection), host.clampAnchorY(anchor.y + velocity.y * root.flingProjection));
|
||||
// An unchanged anchor never reaches applyTarget, so the seed is spent here.
|
||||
if (root.surface.flingSeed)
|
||||
root.surface.applyTarget();
|
||||
host.flinging = root.surface.motionRunning;
|
||||
host.wake();
|
||||
}
|
||||
|
||||
parent: root.surface
|
||||
@@ -76,6 +150,12 @@ DragHandler {
|
||||
if (!root.tracking)
|
||||
root.rememberPress();
|
||||
if (active)
|
||||
root.updateDrag();
|
||||
root.recordPointer();
|
||||
}
|
||||
|
||||
// One anchor write per frame however fast the mouse polls, and none before the lift lands.
|
||||
property FrameAnimation moveFrame: FrameAnimation {
|
||||
running: root.active && root.movePending && root.hostWindow.lifted
|
||||
onTriggered: root.applyMove()
|
||||
}
|
||||
}
|
||||
@@ -28,22 +28,43 @@ PanelWindow {
|
||||
readonly property real screenHeight: root.screen?.height ?? 1080
|
||||
readonly property string positionKey: IslandHostRegistry.key(root.screen?.name, root.barId)
|
||||
readonly property var storedAnchor: SessionData.islandFreePositions[root.positionKey] ?? null
|
||||
readonly property real storedAnchorX: root.clampAnchorX((typeof root.storedAnchor?.x === "number" ? root.storedAnchor.x : 0.5) * root.screenWidth)
|
||||
readonly property real storedAnchorY: root.clampAnchorY((typeof root.storedAnchor?.y === "number" ? root.storedAnchor.y : 0.5) * root.screenHeight)
|
||||
readonly property point storedPoint: Qt.point(root.clampAnchorX((typeof root.storedAnchor?.x === "number" ? root.storedAnchor.x : 0.5) * root.screenWidth), root.clampAnchorY((typeof root.storedAnchor?.y === "number" ? root.storedAnchor.y : 0.5) * root.screenHeight))
|
||||
readonly property real anchorX: root.anchor.x
|
||||
readonly property real anchorY: root.anchor.y
|
||||
// Sized from the visual's size alone, so a fractional spring position never resizes the surface.
|
||||
readonly property int surfaceX: Math.floor(body.currentVisualX - root.hitPadding - body.shadowPadding)
|
||||
readonly property int surfaceY: Math.floor(body.currentVisualY - root.hitPadding - body.shadowPadding)
|
||||
readonly property int surfaceWidth: Math.ceil(body.currentVisualWidth + 2 * (root.hitPadding + body.shadowPadding)) + 1
|
||||
readonly property int surfaceHeight: Math.ceil(body.currentVisualHeight + 2 * (root.hitPadding + body.shadowPadding)) + 1
|
||||
|
||||
// Gestures and morphs run on a surface lifted to the whole output: per-frame margin commits skew pointer coordinates and relayout Hyprland.
|
||||
property bool lifting: false
|
||||
property bool liftForced: false
|
||||
property point liftOrigin
|
||||
// True from the unlift until the tight configure lands: a re-grab in that window still reads the output size.
|
||||
property bool sizeStale: false
|
||||
readonly property bool outputSized: root.width >= root.screenWidth - 2 && root.height >= root.screenHeight - 2
|
||||
// Negative far margins grow the surface in place; the origin flips only once the buffer spans the output.
|
||||
readonly property bool lifted: root.lifting && (root.liftForced || (!root.sizeStale && root.outputSized))
|
||||
readonly property int frameLeft: root.lifted ? 0 : root.lifting ? root.liftOrigin.x : root.surfaceX
|
||||
readonly property int frameTop: root.lifted ? 0 : root.lifting ? root.liftOrigin.y : root.surfaceY
|
||||
readonly property int frameRight: root.lifting ? -root.frameLeft : Math.round(root.screenWidth) - root.surfaceX - root.surfaceWidth
|
||||
readonly property int frameBottom: root.lifting ? -root.frameTop : Math.round(root.screenHeight) - root.surfaceY - root.surfaceHeight
|
||||
|
||||
readonly property bool expanded: body.expanded
|
||||
readonly property bool pointerInside: body.islandController.pointerInside
|
||||
readonly property string activeActivity: body.islandController.activeActivity
|
||||
// An idle-shrunk nub keeps its full-size hit area; the drag area shares it or ring clicks would be lost.
|
||||
readonly property real hitPadding: root.idle ? Math.max(0, (root.dotSize - root.dotSize * root.idleScale) / 2) : 0
|
||||
readonly property bool motionRunning: body.motionRunning
|
||||
// A shrunk nub keeps the full dot's hit area and surface, so idling and waking never resize the window.
|
||||
readonly property real hitPadding: root.dot ? Math.max(0, (root.dotSize - body.currentVisualWidth) / 2) : 0
|
||||
// Blur regions are not antialiased and a small circle shows the jagged rim, so blur waits for the morph.
|
||||
readonly property bool blurWanted: !root.dot || Math.min(body.currentVisualWidth, body.currentVisualHeight) > root.dotSize * 1.5
|
||||
|
||||
property real anchorX: 0
|
||||
property real anchorY: 0
|
||||
property point anchor
|
||||
property bool dragging: false
|
||||
property bool flinging: false
|
||||
// sheetOut spans the expand through the collapse spring, so the whole morph draws into one static buffer.
|
||||
readonly property bool liftWanted: root.dragging || root.flinging || body.sheetOut
|
||||
property bool idle: false
|
||||
|
||||
function anchorLimit(size, extent) {
|
||||
@@ -64,16 +85,24 @@ PanelWindow {
|
||||
}
|
||||
|
||||
function storeAnchor() {
|
||||
SessionData.setIslandFreePosition(root.positionKey, root.anchorX / root.screenWidth, root.anchorY / root.screenHeight);
|
||||
SessionData.setIslandFreePosition(root.positionKey, root.anchor.x / root.screenWidth, root.anchor.y / root.screenHeight);
|
||||
}
|
||||
|
||||
function moveTo(x, y) {
|
||||
root.anchorX = root.clampAnchorX(x);
|
||||
root.anchorY = root.clampAnchorY(y);
|
||||
root.anchor = Qt.point(root.clampAnchorX(x), root.clampAnchorY(y));
|
||||
root.storeAnchor();
|
||||
root.wake();
|
||||
}
|
||||
|
||||
function beginLift() {
|
||||
if (root.lifting)
|
||||
return;
|
||||
root.liftOrigin = Qt.point(root.frameLeft, root.frameTop);
|
||||
root.liftForced = false;
|
||||
root.lifting = true;
|
||||
liftTimeout.restart();
|
||||
}
|
||||
|
||||
function wake() {
|
||||
root.idle = false;
|
||||
if (root.idleDelay > 0)
|
||||
@@ -90,17 +119,37 @@ PanelWindow {
|
||||
return body.containsGlobalPoint(gx, gy, padding);
|
||||
}
|
||||
|
||||
onStoredAnchorXChanged: {
|
||||
if (!root.dragging)
|
||||
root.anchorX = root.storedAnchorX;
|
||||
onStoredPointChanged: {
|
||||
if (!root.liftWanted)
|
||||
root.anchor = root.storedPoint;
|
||||
}
|
||||
onStoredAnchorYChanged: {
|
||||
if (!root.dragging)
|
||||
root.anchorY = root.storedAnchorY;
|
||||
// Stored once the fling settles; a save mid-gesture stalls the fling's first frame.
|
||||
onLiftWantedChanged: {
|
||||
if (root.liftWanted) {
|
||||
root.beginLift();
|
||||
return;
|
||||
}
|
||||
root.sizeStale = root.outputSized;
|
||||
root.lifting = false;
|
||||
liftTimeout.stop();
|
||||
root.storeAnchor();
|
||||
}
|
||||
onOutputSizedChanged: {
|
||||
if (!root.outputSized)
|
||||
root.sizeStale = false;
|
||||
}
|
||||
onLiftedChanged: {
|
||||
if (root.lifted)
|
||||
liftTimeout.stop();
|
||||
}
|
||||
onMotionRunningChanged: {
|
||||
if (!root.motionRunning)
|
||||
root.flinging = false;
|
||||
}
|
||||
onPointerInsideChanged: root.wake()
|
||||
onExpandedChanged: root.wake()
|
||||
onActiveActivityChanged: root.wake()
|
||||
onIdleDelayChanged: root.wake()
|
||||
|
||||
color: "transparent"
|
||||
exclusiveZone: -1
|
||||
@@ -122,16 +171,19 @@ PanelWindow {
|
||||
radius: body.currentSurfaceRadius > 0 ? Math.ceil(body.currentSurfaceRadius) + 1 : 0
|
||||
}
|
||||
|
||||
// All four edges are anchored so the margins alone place and size the surface, letting a lift grow it past the output.
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
WlrLayershell.margins {
|
||||
left: root.surfaceX
|
||||
top: root.surfaceY
|
||||
left: root.frameLeft
|
||||
top: root.frameTop
|
||||
right: root.frameRight
|
||||
bottom: root.frameBottom
|
||||
}
|
||||
implicitWidth: Math.ceil(body.currentVisualX + body.currentVisualWidth + root.hitPadding + body.shadowPadding) - root.surfaceX
|
||||
implicitHeight: Math.ceil(body.currentVisualY + body.currentVisualHeight + root.hitPadding + body.shadowPadding) - root.surfaceY
|
||||
|
||||
Component.onCompleted: {
|
||||
root.wake();
|
||||
@@ -150,24 +202,46 @@ PanelWindow {
|
||||
Timer {
|
||||
id: idleTimer
|
||||
|
||||
interval: Math.max(1000, root.idleDelay)
|
||||
interval: root.idleDelay
|
||||
onTriggered: {
|
||||
if (root.expanded || root.pointerInside || root.dragging || body.islandController.transientActive)
|
||||
if (root.expanded || root.pointerInside || hitHover.hovered || root.liftWanted || body.islandController.transientActive)
|
||||
return;
|
||||
root.idle = true;
|
||||
}
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
// A compositor that never sizes the lifted surface would leave every drag frozen.
|
||||
Timer {
|
||||
id: liftTimeout
|
||||
|
||||
interval: 250
|
||||
onTriggered: root.liftForced = true
|
||||
}
|
||||
|
||||
// A quick stroke outruns the dot's rectangle between frames and the pointer leave ends the drag, so a drag takes input everywhere.
|
||||
mask: root.dragging && root.lifted ? null : hitRegion
|
||||
Region {
|
||||
id: hitRegion
|
||||
|
||||
item: body.inputSuspended ? null : hitTarget
|
||||
}
|
||||
Item {
|
||||
id: hitTarget
|
||||
anchors.fill: parent
|
||||
anchors.margins: body.shadowPadding
|
||||
|
||||
x: body.currentVisualX - root.hitPadding - root.frameLeft
|
||||
y: body.currentVisualY - root.hitPadding - root.frameTop
|
||||
width: body.currentVisualWidth + 2 * root.hitPadding
|
||||
height: body.currentVisualHeight + 2 * root.hitPadding
|
||||
|
||||
// The idle ring around a shrunk nub takes clicks and drags, so hovering it wakes the dot too.
|
||||
HoverHandler {
|
||||
id: hitHover
|
||||
|
||||
onHoveredChanged: root.wake()
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the animated buffer tight; the unmoving input-only window catches outside clicks.
|
||||
// Catches outside clicks; the lifted surface's own mask stays the island rectangle.
|
||||
PanelWindow {
|
||||
id: dismissWindow
|
||||
screen: root.screen
|
||||
@@ -182,14 +256,17 @@ PanelWindow {
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
// The frozen spring envelope, so this full-output surface is not re-committed every frame of a morph.
|
||||
mask: Region {
|
||||
width: dismissWindow.width
|
||||
height: dismissWindow.height
|
||||
Region {
|
||||
x: root.surfaceX + hitTarget.x
|
||||
y: root.surfaceY + hitTarget.y
|
||||
width: hitTarget.width
|
||||
height: hitTarget.height
|
||||
readonly property Item envelope: body.inputMaskItem
|
||||
|
||||
x: dismissWindow.visible ? envelope.x : 0
|
||||
y: dismissWindow.visible ? envelope.y : 0
|
||||
width: dismissWindow.visible ? envelope.width : 0
|
||||
height: dismissWindow.visible ? envelope.height : 0
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
@@ -212,8 +289,8 @@ PanelWindow {
|
||||
IslandBarHost {
|
||||
id: body
|
||||
|
||||
x: -root.surfaceX
|
||||
y: -root.surfaceY
|
||||
x: -root.frameLeft
|
||||
y: -root.frameTop
|
||||
width: root.screenWidth
|
||||
height: root.screenHeight
|
||||
originOffsetX: x
|
||||
@@ -221,9 +298,9 @@ PanelWindow {
|
||||
barConfig: root.barConfig
|
||||
screen: root.screen
|
||||
hostWindow: root
|
||||
hostLifted: root.lifted
|
||||
barId: root.barId
|
||||
anchorX: root.anchorX
|
||||
anchorY: root.anchorY
|
||||
anchor: root.anchor
|
||||
anchorSnaps: root.dragging
|
||||
freeScale: root.idle ? root.idleScale : 1
|
||||
freeOpacity: root.idle ? root.idleOpacity : 1
|
||||
|
||||
@@ -153,6 +153,7 @@ Column {
|
||||
anchors.leftMargin: SettingsMetrics.rowPaddingH
|
||||
anchors.rightMargin: SettingsMetrics.rowPaddingH
|
||||
spacing: Theme.spacingM
|
||||
opacity: root.enabled ? 1 : SettingsMetrics.disabledOpacity
|
||||
|
||||
DColorSwatch {
|
||||
width: Theme.avatarSize
|
||||
|
||||
@@ -173,7 +173,7 @@ Item {
|
||||
maximum: 64
|
||||
step: 1
|
||||
value: bar.islandSetting("islandFreeEdgeMargin")
|
||||
onSliderValueChanged: value => bar.apply("islandFreeEdgeMargin", value)
|
||||
onSliderDragFinished: value => bar.apply("islandFreeEdgeMargin", value)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
@@ -188,7 +188,7 @@ Item {
|
||||
maximum: 48
|
||||
step: 1
|
||||
value: bar.islandSetting("islandOuterGap")
|
||||
onSliderValueChanged: value => bar.apply("islandOuterGap", value)
|
||||
onSliderDragFinished: value => bar.apply("islandOuterGap", value)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
@@ -203,7 +203,7 @@ Item {
|
||||
maximum: 600
|
||||
step: 1
|
||||
value: bar.islandSetting("islandAlongOffset")
|
||||
onSliderValueChanged: value => bar.apply("islandAlongOffset", value)
|
||||
onSliderDragFinished: value => bar.apply("islandAlongOffset", value)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
@@ -219,7 +219,7 @@ Item {
|
||||
step: 1
|
||||
value: bar.islandSetting("islandReserveThickness")
|
||||
enabled: !bar.islandSetting("islandFloating")
|
||||
onSliderValueChanged: value => bar.apply("islandReserveThickness", value)
|
||||
onSliderDragFinished: value => bar.apply("islandReserveThickness", value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ Item {
|
||||
});
|
||||
bar.notifyHorizontalBarChange();
|
||||
}
|
||||
onSliderValueChanged: newValue => SettingsData.updateBarConfig(bar.selectedBarId, {
|
||||
onSliderDragFinished: newValue => SettingsData.updateBarConfig(bar.selectedBarId, {
|
||||
autoHideDelay: newValue
|
||||
})
|
||||
}
|
||||
@@ -457,7 +457,7 @@ Item {
|
||||
minimum: 0
|
||||
maximum: 1000
|
||||
unit: "ms"
|
||||
onSliderValueChanged: newValue => {
|
||||
onSliderDragFinished: newValue => {
|
||||
SettingsData.updateBarConfig(bar.selectedBarId, {
|
||||
hoverPopoutDelay: newValue
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.DCommon.Widgets
|
||||
import qs.Modals.FileBrowser
|
||||
import qs.Modules.DIsland
|
||||
import qs.Services
|
||||
import qs.Modules.Settings.Widgets
|
||||
|
||||
@@ -15,6 +19,32 @@ Item {
|
||||
barId: SettingsData.dotBarConfig?.id ?? ""
|
||||
}
|
||||
|
||||
readonly property bool highContrast: dot.setting("islandHighContrast")
|
||||
readonly property bool paletteTinted: ["bright", "dim"].includes(dot.setting("islandPalette"))
|
||||
readonly property var iconModes: ["icon", "os", "dank", "compositor", "custom"]
|
||||
readonly property string iconMode: dot.setting("islandFreeIconMode")
|
||||
readonly property string iconColor: dot.setting("islandFreeIconColor")
|
||||
readonly property bool iconColorCustom: iconColor !== "" && iconColor !== "primary" && iconColor !== "surface"
|
||||
readonly property string compositorLabel: CompositorService.displayName || I18n.tr("Compositor")
|
||||
|
||||
// A screen with no stored entry already sits at the default centre.
|
||||
function resetDotPosition() {
|
||||
for (const screen of Quickshell.screens) {
|
||||
const key = IslandHostRegistry.key(screen.name, root.dot.barId);
|
||||
if (SessionData.islandFreePositions[key])
|
||||
SessionData.setIslandFreePosition(key, 0.5, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: iconFileBrowser
|
||||
|
||||
browserTitle: I18n.tr("Select Dot Icon")
|
||||
bucket: "logo"
|
||||
filters: ["*.svg", "*.png", "*.jpg", "*.jpeg", "*.webp"]
|
||||
onAccepted: paths => root.dot.apply("islandFreeIconPath", paths[0])
|
||||
}
|
||||
|
||||
SettingsPage {
|
||||
SettingsCard {
|
||||
settingKey: "dotSize"
|
||||
@@ -33,11 +63,93 @@ Item {
|
||||
maximum: 160
|
||||
step: 1
|
||||
value: dot.setting("islandFreeSize")
|
||||
onSliderValueChanged: value => dot.apply("islandFreeSize", value)
|
||||
onSliderDragFinished: finalValue => dot.apply("islandFreeSize", finalValue)
|
||||
}
|
||||
|
||||
SettingsButtonGroupRow {
|
||||
settingKey: "dotIcon"
|
||||
tags: ["dot", "icon", "symbol", "logo", "face"]
|
||||
resetStore: root.dot
|
||||
resetKeys: ["islandFreeIconMode"]
|
||||
text: I18n.tr("Icon")
|
||||
buttonPadding: Theme.spacingS
|
||||
minButtonWidth: 44
|
||||
textSize: Theme.fontSizeSmall
|
||||
model: [I18n.tr("Symbol"), I18n.tr("OS Logo"), "Dux", root.compositorLabel, I18n.tr("Custom")]
|
||||
values: root.iconModes
|
||||
value: root.iconMode
|
||||
fallbackValue: "icon"
|
||||
onValueSelected: value => root.dot.apply("islandFreeIconMode", value)
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
settingKey: "dotIconSymbol"
|
||||
tags: ["dot", "icon", "symbol", "glyph"]
|
||||
resetStore: root.dot
|
||||
resetKeys: ["islandFreeIcon"]
|
||||
title: I18n.tr("Symbol")
|
||||
visible: root.iconMode === "icon"
|
||||
|
||||
DIconPicker {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
currentIcon: root.dot.setting("islandFreeIcon")
|
||||
onIconSelected: iconName => root.dot.apply("islandFreeIcon", iconName)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsTextFieldRow {
|
||||
settingKey: "dotIconImage"
|
||||
tags: ["dot", "icon", "image", "custom", "file", "logo"]
|
||||
resetStore: root.dot
|
||||
resetKeys: ["islandFreeIconPath"]
|
||||
text: I18n.tr("Custom")
|
||||
visible: root.iconMode === "custom"
|
||||
placeholderText: I18n.tr("Select an image file...")
|
||||
value: root.dot.setting("islandFreeIconPath")
|
||||
onEditingFinished: value => root.dot.apply("islandFreeIconPath", value.trim())
|
||||
|
||||
actions: DActionButton {
|
||||
iconName: "folder_open"
|
||||
Accessible.name: I18n.tr("Select Dot Icon")
|
||||
onClicked: iconFileBrowser.open()
|
||||
}
|
||||
}
|
||||
|
||||
ColorDropdownRow {
|
||||
settingKey: "dotIconColor"
|
||||
tags: ["dot", "icon", "color", "tint", "logo"]
|
||||
resetStore: root.dot
|
||||
resetKeys: ["islandFreeIconColor"]
|
||||
text: I18n.tr("Color override")
|
||||
options: [({
|
||||
"value": "",
|
||||
"label": I18n.tr("Default")
|
||||
}), ({
|
||||
"value": "primary",
|
||||
"label": I18n.tr("Primary")
|
||||
}), ({
|
||||
"value": "surface",
|
||||
"label": I18n.tr("Surface", "color option")
|
||||
}), ({
|
||||
"value": "custom",
|
||||
"label": I18n.tr("Custom")
|
||||
})]
|
||||
currentMode: root.iconColorCustom ? "custom" : root.iconColor
|
||||
customColor: root.iconColorCustom ? root.iconColor : Theme.primary
|
||||
pickerTitle: I18n.tr("Color override")
|
||||
onModeSelected: mode => {
|
||||
if (mode !== "custom") {
|
||||
root.dot.apply("islandFreeIconColor", mode);
|
||||
return;
|
||||
}
|
||||
if (!root.iconColorCustom)
|
||||
root.dot.apply("islandFreeIconColor", Theme.primary.toString());
|
||||
}
|
||||
onCustomColorSelected: selectedColor => root.dot.apply("islandFreeIconColor", selectedColor.toString())
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
settingKey: "islandFreeEdgeMargin"
|
||||
settingKey: "dotEdgeMargin"
|
||||
tags: ["dot", "edge", "margin", "gap"]
|
||||
resetStore: dot
|
||||
resetKeys: ["islandFreeEdgeMargin"]
|
||||
@@ -47,7 +159,7 @@ Item {
|
||||
maximum: 64
|
||||
step: 1
|
||||
value: dot.setting("islandFreeEdgeMargin")
|
||||
onSliderValueChanged: value => dot.apply("islandFreeEdgeMargin", value)
|
||||
onSliderDragFinished: finalValue => dot.apply("islandFreeEdgeMargin", finalValue)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
@@ -57,11 +169,12 @@ Item {
|
||||
resetKeys: ["islandFreeIdleDelay"]
|
||||
text: I18n.tr("Idle delay", "island settings: time before the dot fades, 0 disables idling")
|
||||
unit: "ms"
|
||||
minimumLabel: I18n.tr("Off")
|
||||
minimum: 0
|
||||
maximum: 30000
|
||||
step: 500
|
||||
value: dot.setting("islandFreeIdleDelay")
|
||||
onSliderValueChanged: value => dot.apply("islandFreeIdleDelay", value)
|
||||
onSliderDragFinished: finalValue => dot.apply("islandFreeIdleDelay", finalValue)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
@@ -76,7 +189,7 @@ Item {
|
||||
step: 1
|
||||
value: Math.round(dot.setting("islandFreeIdleScale") * 100)
|
||||
enabled: dot.setting("islandFreeIdleDelay") > 0
|
||||
onSliderValueChanged: value => dot.apply("islandFreeIdleScale", value / 100)
|
||||
onSliderDragFinished: finalValue => dot.apply("islandFreeIdleScale", finalValue / 100)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
@@ -91,7 +204,16 @@ Item {
|
||||
step: 1
|
||||
value: Math.round(dot.setting("islandFreeIdleOpacity") * 100)
|
||||
enabled: dot.setting("islandFreeIdleDelay") > 0
|
||||
onSliderValueChanged: value => dot.apply("islandFreeIdleOpacity", value / 100)
|
||||
onSliderDragFinished: finalValue => dot.apply("islandFreeIdleOpacity", finalValue / 100)
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
settingKey: "dotResetPosition"
|
||||
tags: ["dot", "position", "center", "reset", "move"]
|
||||
iconName: "drag_pan"
|
||||
title: I18n.tr("Reset Position")
|
||||
clickable: true
|
||||
onClicked: root.resetDotPosition()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +229,7 @@ Item {
|
||||
resetStore: dot
|
||||
resetKeys: ["surfaceColor", "surfaceCustomColor"]
|
||||
text: I18n.tr("Background")
|
||||
enabled: !root.highContrast && !root.paletteTinted
|
||||
defaultColor: Theme.hostSurface
|
||||
currentMode: dot.config?.surfaceColor ?? "default"
|
||||
customColor: dot.config?.surfaceCustomColor ?? SettingsData.barConfigDefault("surfaceCustomColor")
|
||||
@@ -121,6 +244,7 @@ Item {
|
||||
resetStore: dot
|
||||
resetKeys: ["transparency"]
|
||||
text: I18n.tr("Opacity")
|
||||
enabled: !root.highContrast
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
step: 1
|
||||
@@ -139,6 +263,7 @@ Item {
|
||||
text: I18n.tr("Palette", "island settings: surface tone choice")
|
||||
model: [I18n.tr("Default", "island settings: default surface tone"), I18n.tr("Bright", "island settings: bright surface tone"), I18n.tr("Dim", "island settings: dim surface tone")]
|
||||
values: root.paletteValues
|
||||
enabled: !root.highContrast
|
||||
value: dot.setting("islandPalette")
|
||||
fallbackValue: "default"
|
||||
onValueSelected: value => dot.apply("islandPalette", value)
|
||||
@@ -147,6 +272,8 @@ Item {
|
||||
SettingsToggleRow {
|
||||
settingKey: "dotHighContrast"
|
||||
tags: ["dot", "contrast", "accessibility", "outline"]
|
||||
resetStore: root.dot
|
||||
resetKeys: ["islandHighContrast"]
|
||||
text: I18n.tr("High contrast", "island settings: high contrast toggle")
|
||||
checked: dot.setting("islandHighContrast")
|
||||
onToggled: checked => dot.apply("islandHighContrast", checked)
|
||||
|
||||
@@ -156,7 +156,7 @@ SettingsCard {
|
||||
step: 10
|
||||
value: root.store.setting("islandHoverOpenDelay")
|
||||
visible: root.docked && root.store.setting("islandInteractionMode") !== "click"
|
||||
onSliderValueChanged: value => root.store.apply("islandHoverOpenDelay", value)
|
||||
onSliderDragFinished: value => root.store.apply("islandHoverOpenDelay", value)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
@@ -171,7 +171,7 @@ SettingsCard {
|
||||
step: 10
|
||||
value: root.store.setting("islandHoverCloseDelay")
|
||||
visible: root.docked && root.store.setting("islandInteractionMode") !== "click"
|
||||
onSliderValueChanged: value => root.store.apply("islandHoverCloseDelay", value)
|
||||
onSliderDragFinished: value => root.store.apply("islandHoverCloseDelay", value)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
|
||||
@@ -24,7 +24,7 @@ SettingsCard {
|
||||
step: 10
|
||||
value: Math.round(root.store.setting("islandSpringStiffness"))
|
||||
enabled: !SettingsData.reduceMotion
|
||||
onSliderValueChanged: value => root.store.apply("islandSpringStiffness", value)
|
||||
onSliderDragFinished: finalValue => root.store.apply("islandSpringStiffness", finalValue)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
@@ -39,7 +39,7 @@ SettingsCard {
|
||||
step: 1
|
||||
value: Math.round(root.store.setting("islandSpringDamping"))
|
||||
enabled: !SettingsData.reduceMotion
|
||||
onSliderValueChanged: value => root.store.apply("islandSpringDamping", value)
|
||||
onSliderDragFinished: finalValue => root.store.apply("islandSpringDamping", finalValue)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
@@ -55,6 +55,6 @@ SettingsCard {
|
||||
decimals: 2
|
||||
value: Math.round(root.store.setting("islandSpringMass") * 100)
|
||||
enabled: !SettingsData.reduceMotion
|
||||
onSliderValueChanged: value => root.store.apply("islandSpringMass", value / 100)
|
||||
onSliderDragFinished: finalValue => root.store.apply("islandSpringMass", finalValue / 100)
|
||||
}
|
||||
}
|
||||
@@ -17443,6 +17443,29 @@
|
||||
"parentLabel": "Bar",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotIconColor",
|
||||
"label": "Color override",
|
||||
"tabIndex": 65,
|
||||
"category": "Dot",
|
||||
"keywords": [
|
||||
"bar",
|
||||
"color",
|
||||
"colour",
|
||||
"dot",
|
||||
"hue",
|
||||
"icon",
|
||||
"logo",
|
||||
"override",
|
||||
"panel",
|
||||
"statusbar",
|
||||
"taskbar",
|
||||
"tint",
|
||||
"topbar"
|
||||
],
|
||||
"parentLabel": "Bar",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotRouteControlCenter",
|
||||
"label": "Control Center",
|
||||
@@ -17465,6 +17488,28 @@
|
||||
"parentLabel": "Bar",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotIconImage",
|
||||
"label": "Custom",
|
||||
"tabIndex": 65,
|
||||
"category": "Dot",
|
||||
"keywords": [
|
||||
"bar",
|
||||
"custom",
|
||||
"dot",
|
||||
"file",
|
||||
"icon",
|
||||
"image",
|
||||
"logo",
|
||||
"panel",
|
||||
"statusbar",
|
||||
"taskbar",
|
||||
"topbar"
|
||||
],
|
||||
"parentLabel": "Bar",
|
||||
"icon": "folder_open",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotRouteDash",
|
||||
"label": "Dashboard",
|
||||
@@ -17533,6 +17578,25 @@
|
||||
"parentLabel": "Bar",
|
||||
"description": "A floating companion that works alongside any bar layout"
|
||||
},
|
||||
{
|
||||
"section": "dotEdgeMargin",
|
||||
"label": "Edge margin",
|
||||
"tabIndex": 65,
|
||||
"category": "Dot",
|
||||
"keywords": [
|
||||
"bar",
|
||||
"dot",
|
||||
"edge",
|
||||
"gap",
|
||||
"margin",
|
||||
"panel",
|
||||
"statusbar",
|
||||
"taskbar",
|
||||
"topbar"
|
||||
],
|
||||
"parentLabel": "Bar",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotNotificationExpand",
|
||||
"label": "Expand by default",
|
||||
@@ -17598,6 +17662,26 @@
|
||||
"parentLabel": "Bar",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotIcon",
|
||||
"label": "Icon",
|
||||
"tabIndex": 65,
|
||||
"category": "Dot",
|
||||
"keywords": [
|
||||
"bar",
|
||||
"dot",
|
||||
"face",
|
||||
"icon",
|
||||
"logo",
|
||||
"panel",
|
||||
"statusbar",
|
||||
"symbol",
|
||||
"taskbar",
|
||||
"topbar"
|
||||
],
|
||||
"parentLabel": "Bar",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "islandFreeIdleDelay",
|
||||
"label": "Idle delay",
|
||||
@@ -17883,6 +17967,27 @@
|
||||
"parentLabel": "Bar",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotResetPosition",
|
||||
"label": "Reset Position",
|
||||
"tabIndex": 65,
|
||||
"category": "Dot",
|
||||
"keywords": [
|
||||
"bar",
|
||||
"center",
|
||||
"dot",
|
||||
"move",
|
||||
"panel",
|
||||
"position",
|
||||
"reset",
|
||||
"statusbar",
|
||||
"taskbar",
|
||||
"topbar"
|
||||
],
|
||||
"parentLabel": "Bar",
|
||||
"icon": "drag_pan",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotSharedRouting",
|
||||
"label": "Shared shortcuts",
|
||||
@@ -18025,6 +18130,25 @@
|
||||
"icon": "palette",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotIconSymbol",
|
||||
"label": "Symbol",
|
||||
"tabIndex": 65,
|
||||
"category": "Dot",
|
||||
"keywords": [
|
||||
"bar",
|
||||
"dot",
|
||||
"glyph",
|
||||
"icon",
|
||||
"panel",
|
||||
"statusbar",
|
||||
"symbol",
|
||||
"taskbar",
|
||||
"topbar"
|
||||
],
|
||||
"parentLabel": "Bar",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotSystemOsd",
|
||||
"label": "Use standard OSDs",
|
||||
|
||||
Reference in new issue
Block a user