Files
DankMaterialShell/quickshell/Modules/Settings/DDotTab.qml
T
purian23 0cd03e0180 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
2026-10-23 05:38:46 -04:00

319 lines
13 KiB
QML

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
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
readonly property var paletteValues: ["default", "bright", "dim"]
readonly property IslandSettingsStore dot: IslandSettingsStore {
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"
tags: ["dot", "dankdot", "size", "diameter", "idle", "fade"]
iconName: "blur_on"
title: I18n.tr("General")
SettingsSliderRow {
settingKey: "islandFreeSize"
tags: ["dot", "size", "diameter"]
resetStore: dot
resetKeys: ["islandFreeSize"]
text: I18n.tr("Size")
unit: "px"
minimum: 24
maximum: 160
step: 1
value: dot.setting("islandFreeSize")
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: "dotEdgeMargin"
tags: ["dot", "edge", "margin", "gap"]
resetStore: dot
resetKeys: ["islandFreeEdgeMargin"]
text: I18n.tr("Edge margin", "island settings: gap kept from the display edge")
unit: "px"
minimum: 0
maximum: 64
step: 1
value: dot.setting("islandFreeEdgeMargin")
onSliderDragFinished: finalValue => dot.apply("islandFreeEdgeMargin", finalValue)
}
SettingsSliderRow {
settingKey: "islandFreeIdleDelay"
tags: ["dot", "idle", "delay", "fade", "timeout"]
resetStore: dot
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")
onSliderDragFinished: finalValue => dot.apply("islandFreeIdleDelay", finalValue)
}
SettingsSliderRow {
settingKey: "islandFreeIdleScale"
tags: ["dot", "idle", "shrink", "scale"]
resetStore: dot
resetKeys: ["islandFreeIdleScale"]
text: I18n.tr("Idle size", "island settings: how far the dot shrinks while idle")
unit: "%"
minimum: 20
maximum: 100
step: 1
value: Math.round(dot.setting("islandFreeIdleScale") * 100)
enabled: dot.setting("islandFreeIdleDelay") > 0
onSliderDragFinished: finalValue => dot.apply("islandFreeIdleScale", finalValue / 100)
}
SettingsSliderRow {
settingKey: "islandFreeIdleOpacity"
tags: ["dot", "idle", "opacity", "fade"]
resetStore: dot
resetKeys: ["islandFreeIdleOpacity"]
text: I18n.tr("Idle opacity", "island settings: dot opacity while idle")
unit: "%"
minimum: 5
maximum: 100
step: 1
value: Math.round(dot.setting("islandFreeIdleOpacity") * 100)
enabled: dot.setting("islandFreeIdleDelay") > 0
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()
}
}
SettingsCard {
iconName: "palette"
title: I18n.tr("Surface")
settingKey: "dotSurface"
tags: ["dot", "background", "color", "opacity", "palette", "contrast"]
SurfaceColorRow {
settingKey: "dotSurfaceColor"
tags: ["dot", "background", "color", "surface"]
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")
pickerTitle: I18n.tr("Background")
onModeSelected: mode => dot.apply("surfaceColor", mode)
onCustomColorSelected: selectedColor => dot.apply("surfaceCustomColor", selectedColor.toString())
}
SettingsSliderRow {
settingKey: "dotOpacity"
tags: ["dot", "opacity", "transparency", "background"]
resetStore: dot
resetKeys: ["transparency"]
text: I18n.tr("Opacity")
enabled: !root.highContrast
minimum: 0
maximum: 100
step: 1
value: Math.round(SettingsData.barTransparency(dot.config) * 100)
onSliderDragFinished: finalValue => SettingsData.updateBarConfig(dot.config.id, {
followInterfaceStyle: false,
transparency: finalValue / 100
})
}
SettingsButtonGroupRow {
settingKey: "dotPalette"
tags: ["dot", "palette", "surface", "bright", "dim"]
resetStore: dot
resetKeys: ["islandPalette"]
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)
}
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)
}
}
SettingsCard {
iconName: "display_settings"
title: I18n.tr("Displays")
settingKey: "dotDisplays"
tags: ["dot", "display", "monitor", "screen"]
SettingsDisplayPicker {
displayPreferences: dot.config?.screenPreferences || ["all"]
emptyMeansAll: false
allowEmpty: true
showLastDisplay: true
showOnLastDisplay: dot.config?.showOnLastDisplay ?? true
preferencesHandler: prefs => dot.apply("screenPreferences", prefs)
onLastDisplayToggled: checked => dot.apply("showOnLastDisplay", checked)
}
}
IslandBehaviorCard {
store: dot
keyPrefix: "dot"
docked: false
isDot: true
}
IslandNotificationsCard {
store: dot
keyPrefix: "dot"
badgeGated: false
}
IslandMotionCard {
store: dot
keyPrefix: "dot"
}
}
}