1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-08 04:09:15 -04:00

feat(settings): add compositor section & restructured settings

- add dedicated Compositor pages for comp specifc features
- add Dank Bar Appearance subsection
- improve lazy loading, caching, search routing, & IPC navigation
- standardized responsive Setting categories from global animations
This commit is contained in:
purian23
2026-06-07 03:52:00 -04:00
parent 8155970ba2
commit 69f3dee25a
19 changed files with 2355 additions and 1503 deletions
+4
View File
@@ -11,6 +11,10 @@ Singleton {
readonly property int durMed: 450
readonly property int durLong: 600
// Navigation feedback stays responsive even when ambient shell motion is slow.
readonly property int settingsNavigationStateDuration: 180
readonly property int settingsNavigationRippleDuration: 200
readonly property int slidePx: 80
readonly property var emphasized: [0.05, 0.00, 0.133333, 0.06, 0.166667, 0.40, 0.208333, 0.82, 0.25, 1.00, 1.00, 1.00]
+25
View File
@@ -0,0 +1,25 @@
pragma Singleton
import QtQuick
import Quickshell
import qs.Common
Singleton {
id: root
property string selectedBarId: "default"
function normalizeSelectedBar() {
if (SettingsData.getBarConfig(selectedBarId))
return;
selectedBarId = SettingsData.barConfigs[0]?.id ?? "default";
}
Connections {
target: SettingsData
function onBarConfigsChanged() {
root.normalizeSelectedBar();
}
}
}
+1 -1
View File
@@ -947,7 +947,7 @@ Item {
function tabs(): string {
if (!PopoutService.settingsModal)
return "wallpaper\ntheme\ntypography\ntime_weather\nsounds\ndankbar\ndankbar_settings\ndankbar_widgets\nworkspaces\nmedia_player\nnotifications\nosd\nrunning_apps\nupdater\ndock\nlauncher\nkeybinds\ndisplays\nnetwork\nprinters\nlock_screen\npower_sleep\nplugins\nabout";
return "wallpaper\ntheme\ntypography\ntime_weather\nsounds\ndankbar\ndankbar_settings\ndankbar_appearance\ndankbar_widgets\nframe\nworkspaces\ncompositor\nmedia_player\nnotifications\nosd\nrunning_apps\nupdater\ndock\nlauncher\nkeybinds\ndisplays\nnetwork\nprinters\nlock_screen\npower_sleep\nplugins\nabout";
var modal = PopoutService.settingsModal;
var ids = [];
var structure = modal.sidebar?.categoryStructure ?? [];
+41 -23
View File
@@ -1,6 +1,7 @@
import QtQuick
import qs.Common
import qs.Modules.Settings
import qs.Widgets
FocusScope {
id: root
@@ -97,7 +98,24 @@ FocusScope {
visible: active
focus: active
sourceComponent: WorkspacesTab {}
sourceComponent: CompositorTab {}
onActiveChanged: {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
Loader {
id: dankBarAppearanceLoader
anchors.fill: parent
active: root.currentIndex === 6
visible: active
focus: active
sourceComponent: DankBarAppearanceTab {
parentModal: root.parentModal
}
onActiveChanged: {
if (active && item)
@@ -432,19 +450,36 @@ FocusScope {
Loader {
id: widgetsLoader
property bool loadedOnce: false
anchors.fill: parent
active: root.currentIndex === 22
visible: active
focus: active
active: root.currentIndex === 22 || loadedOnce
visible: root.currentIndex === 22 && status === Loader.Ready
focus: visible
asynchronous: true
sourceComponent: WidgetsTab {
parentModal: root.parentModal
}
onActiveChanged: {
if (active && item)
onLoaded: {
loadedOnce = true;
if (visible && item)
Qt.callLater(() => item.forceActiveFocus());
}
onVisibleChanged: {
if (visible && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
StyledText {
anchors.centerIn: parent
visible: root.currentIndex === 22 && widgetsLoader.status === Loader.Loading
text: I18n.tr("Loading...", "loading indicator")
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium
}
Loader {
@@ -479,23 +514,6 @@ FocusScope {
}
}
Loader {
id: windowRulesLoader
anchors.fill: parent
active: root.currentIndex === 28
visible: active
focus: active
sourceComponent: WindowRulesTab {
parentModal: root.parentModal
}
onActiveChanged: {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
Loader {
id: audioLoader
anchors.fill: parent
+29 -21
View File
@@ -23,6 +23,7 @@ Rectangle {
property bool searchActive: searchField.text.length > 0
property int searchSelectedIndex: 0
property int keyboardHighlightIndex: -1
readonly property int navigationStateDuration: Theme.currentAnimationSpeed === SettingsData.AnimationSpeed.None ? 0 : Anims.settingsNavigationStateDuration
function focusSearch() {
searchField.forceActiveFocus();
@@ -115,6 +116,12 @@ Rectangle {
"icon": "tune",
"tabIndex": 3
},
{
"id": "dankbar_appearance",
"text": I18n.tr("Appearance"),
"icon": "palette",
"tabIndex": 6
},
{
"id": "dankbar_widgets",
"text": I18n.tr("Widgets"),
@@ -131,16 +138,10 @@ Rectangle {
},
{
"id": "workspaces_widgets",
"text": I18n.tr("Workspaces & Widgets"),
"text": I18n.tr("Widgets & Notifications"),
"icon": "dashboard",
"collapsedByDefault": true,
"children": [
{
"id": "workspaces",
"text": I18n.tr("Workspaces"),
"icon": "view_module",
"tabIndex": 4
},
{
"id": "media_player",
"text": I18n.tr("Media Player"),
@@ -187,6 +188,12 @@ Rectangle {
}
]
},
{
"id": "compositor",
"text": I18n.tr("Compositor"),
"icon": "layers",
"tabIndex": 4
},
{
"id": "keybinds",
"text": I18n.tr("Keyboard Shortcuts"),
@@ -305,13 +312,6 @@ Rectangle {
"text": I18n.tr("Users"),
"icon": "manage_accounts",
"tabIndex": 35
},
{
"id": "window_rules",
"text": I18n.tr("Window Rules"),
"icon": "select_window",
"tabIndex": 28,
"windowRulesCapable": true
}
]
},
@@ -544,6 +544,8 @@ Rectangle {
return -1;
var normalized = name.toLowerCase().replace(/[_\-\s]/g, "");
if (normalized === "workspaces")
normalized = "compositor";
for (var i = 0; i < categoryStructure.length; i++) {
var cat = categoryStructure[i];
@@ -588,7 +590,7 @@ Rectangle {
id: __m1
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
text: I18n.tr("Workspaces & Widgets")
text: I18n.tr("Widgets & Notifications")
}
StyledTextMetrics {
id: __m2
@@ -782,6 +784,7 @@ Rectangle {
id: resultRipple
rippleColor: root.searchSelectedIndex === resultDelegate.index ? Theme.buttonText : Theme.surfaceText
cornerRadius: resultDelegate.radius
animationDuration: Anims.settingsNavigationRippleDuration
}
Row {
@@ -837,8 +840,9 @@ Rectangle {
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
duration: root.navigationStateDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.expressiveEffects
}
}
}
@@ -912,6 +916,7 @@ Rectangle {
id: categoryRipple
rippleColor: categoryRow.isActive ? Theme.buttonText : Theme.surfaceText
cornerRadius: categoryRow.radius
animationDuration: Anims.settingsNavigationRippleDuration
}
Row {
@@ -967,8 +972,9 @@ Rectangle {
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
duration: root.navigationStateDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.expressiveEffects
}
}
}
@@ -1009,6 +1015,7 @@ Rectangle {
id: childRipple
rippleColor: childDelegate.isActive ? Theme.buttonText : Theme.surfaceText
cornerRadius: childDelegate.radius
animationDuration: Anims.settingsNavigationRippleDuration
}
Row {
@@ -1049,8 +1056,9 @@ Rectangle {
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
duration: root.navigationStateDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.expressiveEffects
}
}
}
@@ -0,0 +1,324 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
Item {
DankFlickable {
anchors.fill: parent
clip: true
contentHeight: layoutColumn.height + Theme.spacingXL
contentWidth: width
Column {
id: layoutColumn
topPadding: Theme.spacingXL
bottomPadding: Theme.spacingXL
width: Math.min(550, parent.width - Theme.spacingL * 2)
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXL
SettingsCard {
width: parent.width
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
title: I18n.tr("Niri Layout Overrides").replace("Niri", "niri")
settingKey: "niriLayout"
iconName: "crop_square"
visible: CompositorService.isNiri
SettingsToggleRow {
tags: ["niri", "gaps", "override"]
settingKey: "niriLayoutGapsOverrideEnabled"
text: I18n.tr("Override Gaps")
description: I18n.tr("Use custom gaps instead of bar spacing")
checked: SettingsData.niriLayoutGapsOverride >= 0
onToggled: checked => {
if (checked) {
const currentGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4));
SettingsData.set("niriLayoutGapsOverride", currentGaps);
return;
}
SettingsData.set("niriLayoutGapsOverride", -1);
}
}
SettingsSliderRow {
tags: ["niri", "gaps", "override"]
settingKey: "niriLayoutGapsOverride"
text: I18n.tr("Window Gaps")
description: I18n.tr("Space between windows")
visible: SettingsData.niriLayoutGapsOverride >= 0
value: Math.max(0, SettingsData.niriLayoutGapsOverride)
minimum: 0
maximum: 50
unit: "px"
defaultValue: Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4))
onSliderValueChanged: newValue => SettingsData.set("niriLayoutGapsOverride", newValue)
}
SettingsToggleRow {
tags: ["niri", "radius", "override"]
settingKey: "niriLayoutRadiusOverrideEnabled"
text: I18n.tr("Override Corner Radius")
description: I18n.tr("Use custom window radius instead of theme radius")
checked: SettingsData.niriLayoutRadiusOverride >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("niriLayoutRadiusOverride", SettingsData.cornerRadius);
return;
}
SettingsData.set("niriLayoutRadiusOverride", -1);
}
}
SettingsSliderRow {
tags: ["niri", "radius", "override"]
settingKey: "niriLayoutRadiusOverride"
text: I18n.tr("Window Corner Radius")
description: I18n.tr("Rounded corners for windows")
visible: SettingsData.niriLayoutRadiusOverride >= 0
value: Math.max(0, SettingsData.niriLayoutRadiusOverride)
minimum: 0
maximum: 100
unit: "px"
defaultValue: SettingsData.cornerRadius
onSliderValueChanged: newValue => SettingsData.set("niriLayoutRadiusOverride", newValue)
}
SettingsToggleRow {
tags: ["niri", "border", "override"]
settingKey: "niriLayoutBorderSizeEnabled"
text: I18n.tr("Override Border Size")
description: I18n.tr("Use custom border/focus-ring width")
checked: SettingsData.niriLayoutBorderSize >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("niriLayoutBorderSize", 2);
return;
}
SettingsData.set("niriLayoutBorderSize", -1);
}
}
SettingsSliderRow {
tags: ["niri", "border", "override"]
settingKey: "niriLayoutBorderSize"
text: I18n.tr("Border Size")
description: I18n.tr("Width of window border and focus ring")
visible: SettingsData.niriLayoutBorderSize >= 0
value: Math.max(0, SettingsData.niriLayoutBorderSize)
minimum: 0
maximum: 10
unit: "px"
defaultValue: 2
onSliderValueChanged: newValue => SettingsData.set("niriLayoutBorderSize", newValue)
}
}
SettingsCard {
width: parent.width
tags: ["hyprland", "layout", "gaps", "radius", "window", "border", "rounding"]
title: I18n.tr("Hyprland Layout Overrides")
settingKey: "hyprlandLayout"
iconName: "crop_square"
visible: CompositorService.isHyprland
SettingsToggleRow {
tags: ["hyprland", "gaps", "override"]
settingKey: "hyprlandLayoutGapsOverrideEnabled"
text: I18n.tr("Override Gaps")
description: I18n.tr("Use custom gaps instead of bar spacing")
checked: SettingsData.hyprlandLayoutGapsOverride >= 0
onToggled: checked => {
if (checked) {
const currentGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4));
SettingsData.set("hyprlandLayoutGapsOverride", currentGaps);
return;
}
SettingsData.set("hyprlandLayoutGapsOverride", -1);
}
}
SettingsSliderRow {
tags: ["hyprland", "gaps", "override"]
settingKey: "hyprlandLayoutGapsOverride"
text: I18n.tr("Window Gaps")
description: I18n.tr("Space between windows (gaps_in and gaps_out)")
visible: SettingsData.hyprlandLayoutGapsOverride >= 0
value: Math.max(0, SettingsData.hyprlandLayoutGapsOverride)
minimum: 0
maximum: 50
unit: "px"
defaultValue: Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4))
onSliderValueChanged: newValue => SettingsData.set("hyprlandLayoutGapsOverride", newValue)
}
SettingsToggleRow {
tags: ["hyprland", "radius", "override", "rounding"]
settingKey: "hyprlandLayoutRadiusOverrideEnabled"
text: I18n.tr("Override Corner Radius")
description: I18n.tr("Use custom window rounding instead of theme radius")
checked: SettingsData.hyprlandLayoutRadiusOverride >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("hyprlandLayoutRadiusOverride", SettingsData.cornerRadius);
return;
}
SettingsData.set("hyprlandLayoutRadiusOverride", -1);
}
}
SettingsSliderRow {
tags: ["hyprland", "radius", "override", "rounding"]
settingKey: "hyprlandLayoutRadiusOverride"
text: I18n.tr("Window Rounding")
description: I18n.tr("Rounded corners for windows (decoration.rounding)")
visible: SettingsData.hyprlandLayoutRadiusOverride >= 0
value: Math.max(0, SettingsData.hyprlandLayoutRadiusOverride)
minimum: 0
maximum: 100
unit: "px"
defaultValue: SettingsData.cornerRadius
onSliderValueChanged: newValue => SettingsData.set("hyprlandLayoutRadiusOverride", newValue)
}
SettingsToggleRow {
tags: ["hyprland", "border", "override"]
settingKey: "hyprlandLayoutBorderSizeEnabled"
text: I18n.tr("Override Border Size")
description: I18n.tr("Use custom border size")
checked: SettingsData.hyprlandLayoutBorderSize >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("hyprlandLayoutBorderSize", 2);
return;
}
SettingsData.set("hyprlandLayoutBorderSize", -1);
}
}
SettingsSliderRow {
tags: ["hyprland", "border", "override"]
settingKey: "hyprlandLayoutBorderSize"
text: I18n.tr("Border Size")
description: I18n.tr("Width of window border (general.border_size)")
visible: SettingsData.hyprlandLayoutBorderSize >= 0
value: Math.max(0, SettingsData.hyprlandLayoutBorderSize)
minimum: 0
maximum: 10
unit: "px"
defaultValue: 2
onSliderValueChanged: newValue => SettingsData.set("hyprlandLayoutBorderSize", newValue)
}
SettingsToggleRow {
tags: ["hyprland", "resize", "border", "mouse", "drag"]
settingKey: "hyprlandResizeOnBorder"
text: I18n.tr("Resize on Border")
description: I18n.tr("Resize windows by dragging their edges with the mouse")
checked: SettingsData.hyprlandResizeOnBorder
onToggled: checked => SettingsData.set("hyprlandResizeOnBorder", checked)
}
}
SettingsCard {
width: parent.width
tags: ["mangowc", "mango", "dwl", "layout", "gaps", "radius", "window", "border"]
title: I18n.tr("MangoWC Layout Overrides")
settingKey: "mangoLayout"
iconName: "crop_square"
visible: CompositorService.isDwl || CompositorService.isMango
SettingsToggleRow {
tags: ["mangowc", "mango", "gaps", "override"]
settingKey: "mangoLayoutGapsOverrideEnabled"
text: I18n.tr("Override Gaps")
description: I18n.tr("Use custom gaps instead of bar spacing")
checked: SettingsData.mangoLayoutGapsOverride >= 0
onToggled: checked => {
if (checked) {
const currentGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4));
SettingsData.set("mangoLayoutGapsOverride", currentGaps);
return;
}
SettingsData.set("mangoLayoutGapsOverride", -1);
}
}
SettingsSliderRow {
tags: ["mangowc", "mango", "gaps", "override"]
settingKey: "mangoLayoutGapsOverride"
text: I18n.tr("Window Gaps")
description: I18n.tr("Space between windows (gappih/gappiv/gappoh/gappov)")
visible: SettingsData.mangoLayoutGapsOverride >= 0
value: Math.max(0, SettingsData.mangoLayoutGapsOverride)
minimum: 0
maximum: 50
unit: "px"
defaultValue: Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4))
onSliderValueChanged: newValue => SettingsData.set("mangoLayoutGapsOverride", newValue)
}
SettingsToggleRow {
tags: ["mangowc", "mango", "radius", "override"]
settingKey: "mangoLayoutRadiusOverrideEnabled"
text: I18n.tr("Override Corner Radius")
description: I18n.tr("Use custom window radius instead of theme radius")
checked: SettingsData.mangoLayoutRadiusOverride >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("mangoLayoutRadiusOverride", SettingsData.cornerRadius);
return;
}
SettingsData.set("mangoLayoutRadiusOverride", -1);
}
}
SettingsSliderRow {
tags: ["mangowc", "mango", "radius", "override"]
settingKey: "mangoLayoutRadiusOverride"
text: I18n.tr("Window Corner Radius")
description: I18n.tr("Rounded corners for windows (border_radius)")
visible: SettingsData.mangoLayoutRadiusOverride >= 0
value: Math.max(0, SettingsData.mangoLayoutRadiusOverride)
minimum: 0
maximum: 100
unit: "px"
defaultValue: SettingsData.cornerRadius
onSliderValueChanged: newValue => SettingsData.set("mangoLayoutRadiusOverride", newValue)
}
SettingsToggleRow {
tags: ["mangowc", "mango", "border", "override"]
settingKey: "mangoLayoutBorderSizeEnabled"
text: I18n.tr("Override Border Size")
description: I18n.tr("Use custom border size")
checked: SettingsData.mangoLayoutBorderSize >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("mangoLayoutBorderSize", 2);
return;
}
SettingsData.set("mangoLayoutBorderSize", -1);
}
}
SettingsSliderRow {
tags: ["mangowc", "mango", "border", "override"]
settingKey: "mangoLayoutBorderSize"
text: I18n.tr("Border Size")
description: I18n.tr("Width of window border (borderpx)")
visible: SettingsData.mangoLayoutBorderSize >= 0
value: Math.max(0, SettingsData.mangoLayoutBorderSize)
minimum: 0
maximum: 10
unit: "px"
defaultValue: 2
onSliderValueChanged: newValue => SettingsData.set("mangoLayoutBorderSize", newValue)
}
}
}
}
}
@@ -0,0 +1,167 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property int subTabIndex: 0
readonly property var workspaceSections: ({
"workspaceSettings": true,
"showWorkspaceIndex": true,
"showWorkspaceName": true,
"showWorkspacePadding": true,
"showWorkspaceApps": true,
"groupWorkspaceApps": true,
"groupActiveWorkspaceApps": true,
"workspaceActiveAppHighlightEnabled": true,
"workspaceFollowFocus": true,
"showOccupiedWorkspacesOnly": true,
"reverseScrolling": true,
"workspaceDragReorder": true,
"dwlShowAllTags": true,
"workspaceIcons": true
})
readonly property var layoutSections: ({
"niriLayout": true,
"niriLayoutGapsOverrideEnabled": true,
"niriLayoutGapsOverride": true,
"niriLayoutRadiusOverrideEnabled": true,
"niriLayoutRadiusOverride": true,
"niriLayoutBorderSizeEnabled": true,
"niriLayoutBorderSize": true,
"hyprlandLayout": true,
"hyprlandLayoutGapsOverrideEnabled": true,
"hyprlandLayoutGapsOverride": true,
"hyprlandLayoutRadiusOverrideEnabled": true,
"hyprlandLayoutRadiusOverride": true,
"hyprlandLayoutBorderSizeEnabled": true,
"hyprlandLayoutBorderSize": true,
"hyprlandResizeOnBorder": true,
"mangoLayout": true,
"mangoLayoutGapsOverrideEnabled": true,
"mangoLayoutGapsOverride": true,
"mangoLayoutRadiusOverrideEnabled": true,
"mangoLayoutRadiusOverride": true,
"mangoLayoutBorderSizeEnabled": true,
"mangoLayoutBorderSize": true
})
function routeSearchTarget(target) {
if (!target)
return;
if (workspaceSections[target]) {
subTabIndex = 0;
} else if (layoutSections[target]) {
subTabIndex = 1;
} else if (target === "windowRules" || target.startsWith("windowRule")) {
subTabIndex = 2;
}
}
Component.onCompleted: routeSearchTarget(SettingsSearchService.targetSection)
Connections {
target: SettingsSearchService
function onTargetSectionChanged() {
root.routeSearchTarget(SettingsSearchService.targetSection);
}
}
ColumnLayout {
anchors.fill: parent
spacing: 0
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 60
color: "transparent"
DankTabBar {
id: compositorTabBar
width: Math.min(500, parent.width - Theme.spacingL * 2)
height: 45
anchors.centerIn: parent
model: [
{
"text": I18n.tr("Workspaces"),
"icon": "view_module"
},
{
"text": I18n.tr("Window Layout"),
"icon": "crop_square"
},
{
"text": I18n.tr("Window Rules"),
"icon": "select_window"
}
]
currentIndex: root.subTabIndex
onTabClicked: index => root.subTabIndex = index
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
y: compositorTabBar.y + compositorTabBar.height + 10
width: compositorTabBar.width
height: 1
color: Theme.surface
opacity: 0.56
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
Loader {
anchors.fill: parent
active: root.subTabIndex === 0
visible: active
sourceComponent: WorkspacesTab {}
}
Loader {
anchors.fill: parent
active: root.subTabIndex === 1
visible: active
sourceComponent: CompositorLayoutTab {}
}
Loader {
id: windowRulesLoader
property bool loadedOnce: false
anchors.fill: parent
active: root.subTabIndex === 2 || loadedOnce
visible: root.subTabIndex === 2 && status === Loader.Ready
asynchronous: true
sourceComponent: WindowRulesTab {
pageActive: root.subTabIndex === 2
}
onLoaded: loadedOnce = true
}
StyledText {
anchors.centerIn: parent
visible: root.subTabIndex === 2 && windowRulesLoader.status === Loader.Loading
text: I18n.tr("Loading...", "loading indicator")
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium
}
}
}
}
@@ -0,0 +1,5 @@
import QtQuick
DankBarTab {
appearanceOnly: true
}
+85 -21
View File
@@ -13,7 +13,22 @@ Item {
LayoutMirroring.childrenInherit: true
property var parentModal: null
property string selectedBarId: "default"
property bool appearanceOnly: false
property string selectedBarId: SettingsUiState.selectedBarId
onSelectedBarIdChanged: {
if (SettingsUiState.selectedBarId !== selectedBarId)
SettingsUiState.selectedBarId = selectedBarId;
}
Connections {
target: SettingsUiState
function onSelectedBarIdChanged() {
if (dankBarTab.selectedBarId !== SettingsUiState.selectedBarId)
dankBarTab.selectedBarId = SettingsUiState.selectedBarId;
}
}
property var selectedBarConfig: {
selectedBarId;
@@ -21,6 +36,14 @@ Item {
const index = SettingsData.barConfigs.findIndex(cfg => cfg.id === selectedBarId);
return index !== -1 ? SettingsData.barConfigs[index] : SettingsData.barConfigs[0];
}
readonly property string selectedBarName: {
selectedBarId;
SettingsData.barConfigs;
const index = SettingsData.barConfigs.findIndex(config => config.id === selectedBarId);
if (index < 0)
return I18n.tr("Bar");
return SettingsData.barConfigs[index].name || I18n.tr("Bar %1").arg(index + 1);
}
property bool selectedBarIsVertical: {
selectedBarId;
@@ -210,10 +233,33 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXL
SettingsCard {
tab: "appearance"
iconName: "toolbar"
title: I18n.tr("Dank Bar")
settingKey: "barAppearance"
visible: dankBarTab.appearanceOnly
SettingsButtonGroupRow {
text: I18n.tr("Editing changes on %1").arg(dankBarTab.selectedBarName)
model: SettingsData.barConfigs.map((config, index) => config.name || I18n.tr("Bar %1").arg(index + 1))
currentIndex: {
const index = SettingsData.barConfigs.findIndex(config => config.id === dankBarTab.selectedBarId);
return Math.max(0, index);
}
onSelectionChanged: (index, selected) => {
if (!selected || index < 0 || index >= SettingsData.barConfigs.length)
return;
dankBarTab.selectedBarId = SettingsData.barConfigs[index].id;
}
}
}
SettingsCard {
iconName: "dashboard"
title: I18n.tr("Bar Configurations")
settingKey: "barConfigurations"
visible: !dankBarTab.appearanceOnly
RowLayout {
width: parent.width
@@ -410,7 +456,7 @@ Item {
SettingsCard {
iconName: selectedBarConfig?.enabled ? "visibility" : "visibility_off"
title: I18n.tr("Enable Bar")
visible: selectedBarId !== "default"
visible: !dankBarTab.appearanceOnly && selectedBarId !== "default"
SettingsToggleRow {
text: I18n.tr("Toggle visibility of this bar configuration")
@@ -426,7 +472,7 @@ Item {
iconName: "vertical_align_center"
title: I18n.tr("Position")
settingKey: "barPosition"
visible: selectedBarConfig?.enabled
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
Item {
width: parent.width
@@ -486,7 +532,7 @@ Item {
settingKey: "barDisplay"
collapsible: true
expanded: false
visible: selectedBarConfig?.enabled
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
StyledText {
width: parent.width
@@ -588,12 +634,12 @@ Item {
}
SettingsCard {
iconName: "visibility_off"
iconName: "visibility"
title: I18n.tr("Visibility")
settingKey: "barVisibility"
collapsible: true
expanded: false
visible: selectedBarConfig?.enabled
expanded: true
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
SettingsToggleRow {
text: I18n.tr("Auto-hide")
@@ -751,7 +797,7 @@ Item {
}
SettingsControlledByFrame {
visible: SettingsData.frameEnabled
visible: !dankBarTab.appearanceOnly && SettingsData.frameEnabled
parentModal: dankBarTab.parentModal
settingLabel: I18n.tr("Bar spacing and size")
reason: I18n.tr("Managed by Frame")
@@ -761,7 +807,7 @@ Item {
iconName: "space_bar"
title: I18n.tr("Spacing")
settingKey: "barSpacing"
visible: (selectedBarConfig?.enabled ?? false) && !SettingsData.frameEnabled
visible: !dankBarTab.appearanceOnly && (selectedBarConfig?.enabled ?? false) && !SettingsData.frameEnabled
SettingsSliderRow {
id: edgeSpacingSlider
@@ -911,10 +957,11 @@ Item {
}
SettingsCard {
tab: "appearance"
iconName: "opacity"
title: I18n.tr("Transparency")
settingKey: "barTransparency"
visible: selectedBarConfig?.enabled
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled
SettingsSliderRow {
id: barTransparencySlider
@@ -973,10 +1020,12 @@ Item {
SettingsSliderCard {
id: fontScaleSliderCard
tab: "appearance"
settingKey: "barFontScale"
iconName: "text_fields"
title: I18n.tr("Font Scale")
description: I18n.tr("Scale DankBar font sizes independently")
visible: selectedBarConfig?.enabled
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled
minimum: 50
maximum: 200
value: Math.round((selectedBarConfig?.fontScale ?? 1.0) * 100)
@@ -998,10 +1047,12 @@ Item {
SettingsSliderCard {
id: iconScaleSliderCard
tab: "appearance"
settingKey: "barIconScale"
iconName: "interests"
title: I18n.tr("Icon Scale")
description: I18n.tr("Scale DankBar icon sizes independently")
visible: selectedBarConfig?.enabled
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled
minimum: 50
maximum: 200
value: Math.round((selectedBarConfig?.iconScale ?? 1.0) * 100)
@@ -1021,13 +1072,18 @@ Item {
}
}
WorkspaceAppearanceCard {
visible: dankBarTab.appearanceOnly
}
SettingsCard {
tab: "appearance"
iconName: "rounded_corner"
title: I18n.tr("Corners & Background")
settingKey: "barCorners"
collapsible: true
expanded: false
visible: selectedBarConfig?.enabled
expanded: true
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled
SettingsControlledByFrame {
visible: SettingsData.frameEnabled
@@ -1144,7 +1200,7 @@ Item {
iconName: "fit_screen"
title: I18n.tr("Maximize Detection")
description: I18n.tr("Remove gaps and border when windows are maximized")
visible: selectedBarConfig?.enabled && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango)
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango)
checked: selectedBarConfig?.maximizeDetection ?? true
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
maximizeDetection: checked
@@ -1152,10 +1208,11 @@ Item {
}
SettingsCard {
tab: "appearance"
iconName: "filter_b_and_w"
title: I18n.tr("System Tray Icon Tint")
settingKey: "trayIconTint"
visible: selectedBarConfig?.enabled
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled
StyledText {
text: I18n.tr("Choose monochrome or a theme color tint for system tray icons")
@@ -1251,9 +1308,11 @@ Item {
}
SettingsToggleCard {
tab: "appearance"
settingKey: "barBorder"
iconName: "border_style"
title: I18n.tr("Border")
visible: selectedBarConfig?.enabled
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled && !dankBarTab.connectedFrameModeActive
checked: selectedBarConfig?.borderEnabled ?? false
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
borderEnabled: checked
@@ -1344,9 +1403,11 @@ Item {
}
SettingsToggleCard {
tab: "appearance"
settingKey: "barWidgetOutline"
iconName: "highlight"
title: I18n.tr("Widget Outline")
visible: selectedBarConfig?.enabled
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled
checked: selectedBarConfig?.widgetOutlineEnabled ?? false
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
widgetOutlineEnabled: checked
@@ -1437,7 +1498,7 @@ Item {
}
SettingsControlledByFrame {
visible: dankBarTab.connectedFrameModeActive
visible: dankBarTab.appearanceOnly && dankBarTab.connectedFrameModeActive
parentModal: dankBarTab.parentModal
settingLabel: I18n.tr("Bar shadow, border, and corners")
reason: I18n.tr("Managed by Frame in Connected Mode")
@@ -1445,12 +1506,13 @@ Item {
SettingsCard {
id: shadowCard
tab: "appearance"
iconName: "layers"
title: I18n.tr("Shadow Override", "bar shadow settings card")
settingKey: "barShadow"
collapsible: true
expanded: false
visible: (selectedBarConfig?.enabled ?? false) && !dankBarTab.connectedFrameModeActive
visible: dankBarTab.appearanceOnly && (selectedBarConfig?.enabled ?? false) && !dankBarTab.connectedFrameModeActive
readonly property bool shadowActive: (selectedBarConfig?.shadowIntensity ?? 0) > 0
readonly property bool isCustomColor: (selectedBarConfig?.shadowColorMode ?? "default") === "custom"
@@ -1512,6 +1574,7 @@ Item {
}
SettingsDropdownRow {
tab: "appearance"
visible: shadowCard.shadowActive
text: I18n.tr("Direction Source", "bar shadow direction source")
description: I18n.tr("Choose how this bar resolves shadow direction")
@@ -1545,6 +1608,7 @@ Item {
}
SettingsDropdownRow {
tab: "appearance"
visible: shadowCard.shadowActive && shadowCard.directionSource === "manual"
text: I18n.tr("Manual Direction", "bar manual shadow direction")
description: I18n.tr("Use a fixed shadow direction for this bar")
@@ -1680,7 +1744,7 @@ Item {
iconName: "mouse"
title: I18n.tr("Scroll Wheel")
description: I18n.tr("Control workspaces and columns by scrolling on the bar")
visible: selectedBarConfig?.enabled
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
checked: selectedBarConfig?.scrollEnabled ?? true
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
scrollEnabled: checked
+2 -321
View File
@@ -1640,7 +1640,7 @@ Item {
SettingsControlledByFrame {
visible: themeColorsTab.connectedFrameModeActive
parentModal: themeColorsTab.parentModal
settingLabel: I18n.tr("Surface Opacity")
settingLabel: I18n.tr("Transparency")
reason: I18n.tr("Managed by Frame in Connected Mode")
}
@@ -1648,7 +1648,7 @@ Item {
tab: "theme"
tags: ["surface", "popup", "transparency", "opacity", "modal"]
settingKey: "popupTransparency"
text: I18n.tr("Surface Opacity")
text: I18n.tr("Transparency")
description: I18n.tr("Controls opacity of all popouts, modals, and their content layers")
visible: !themeColorsTab.connectedFrameModeActive
value: Math.round(SettingsData.popupTransparency * 100)
@@ -1956,325 +1956,6 @@ Item {
}
}
SettingsCard {
tab: "theme"
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
title: I18n.tr("Niri Layout Overrides").replace("Niri", "niri")
settingKey: "niriLayout"
iconName: "crop_square"
visible: CompositorService.isNiri
SettingsToggleRow {
tab: "theme"
tags: ["niri", "gaps", "override"]
settingKey: "niriLayoutGapsOverrideEnabled"
text: I18n.tr("Override Gaps")
description: I18n.tr("Use custom gaps instead of bar spacing")
checked: SettingsData.niriLayoutGapsOverride >= 0
onToggled: checked => {
if (checked) {
const currentGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4));
SettingsData.set("niriLayoutGapsOverride", currentGaps);
return;
}
SettingsData.set("niriLayoutGapsOverride", -1);
}
}
SettingsSliderRow {
tab: "theme"
tags: ["niri", "gaps", "override"]
settingKey: "niriLayoutGapsOverride"
text: I18n.tr("Window Gaps")
description: I18n.tr("Space between windows")
visible: SettingsData.niriLayoutGapsOverride >= 0
value: Math.max(0, SettingsData.niriLayoutGapsOverride)
minimum: 0
maximum: 50
unit: "px"
defaultValue: Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4))
onSliderValueChanged: newValue => SettingsData.set("niriLayoutGapsOverride", newValue)
}
SettingsToggleRow {
tab: "theme"
tags: ["niri", "radius", "override"]
settingKey: "niriLayoutRadiusOverrideEnabled"
text: I18n.tr("Override Corner Radius")
description: I18n.tr("Use custom window radius instead of theme radius")
checked: SettingsData.niriLayoutRadiusOverride >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("niriLayoutRadiusOverride", SettingsData.cornerRadius);
return;
}
SettingsData.set("niriLayoutRadiusOverride", -1);
}
}
SettingsSliderRow {
tab: "theme"
tags: ["niri", "radius", "override"]
settingKey: "niriLayoutRadiusOverride"
text: I18n.tr("Window Corner Radius")
description: I18n.tr("Rounded corners for windows")
visible: SettingsData.niriLayoutRadiusOverride >= 0
value: Math.max(0, SettingsData.niriLayoutRadiusOverride)
minimum: 0
maximum: 100
unit: "px"
defaultValue: SettingsData.cornerRadius
onSliderValueChanged: newValue => SettingsData.set("niriLayoutRadiusOverride", newValue)
}
SettingsToggleRow {
tab: "theme"
tags: ["niri", "border", "override"]
settingKey: "niriLayoutBorderSizeEnabled"
text: I18n.tr("Override Border Size")
description: I18n.tr("Use custom border/focus-ring width")
checked: SettingsData.niriLayoutBorderSize >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("niriLayoutBorderSize", 2);
return;
}
SettingsData.set("niriLayoutBorderSize", -1);
}
}
SettingsSliderRow {
tab: "theme"
tags: ["niri", "border", "override"]
settingKey: "niriLayoutBorderSize"
text: I18n.tr("Border Size")
description: I18n.tr("Width of window border and focus ring")
visible: SettingsData.niriLayoutBorderSize >= 0
value: Math.max(0, SettingsData.niriLayoutBorderSize)
minimum: 0
maximum: 10
unit: "px"
defaultValue: 2
onSliderValueChanged: newValue => SettingsData.set("niriLayoutBorderSize", newValue)
}
}
SettingsCard {
tab: "theme"
tags: ["hyprland", "layout", "gaps", "radius", "window", "border", "rounding"]
title: I18n.tr("Hyprland Layout Overrides")
settingKey: "hyprlandLayout"
iconName: "crop_square"
visible: CompositorService.isHyprland
SettingsToggleRow {
tab: "theme"
tags: ["hyprland", "gaps", "override"]
settingKey: "hyprlandLayoutGapsOverrideEnabled"
text: I18n.tr("Override Gaps")
description: I18n.tr("Use custom gaps instead of bar spacing")
checked: SettingsData.hyprlandLayoutGapsOverride >= 0
onToggled: checked => {
if (checked) {
const currentGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4));
SettingsData.set("hyprlandLayoutGapsOverride", currentGaps);
return;
}
SettingsData.set("hyprlandLayoutGapsOverride", -1);
}
}
SettingsSliderRow {
tab: "theme"
tags: ["hyprland", "gaps", "override"]
settingKey: "hyprlandLayoutGapsOverride"
text: I18n.tr("Window Gaps")
description: I18n.tr("Space between windows (gaps_in and gaps_out)")
visible: SettingsData.hyprlandLayoutGapsOverride >= 0
value: Math.max(0, SettingsData.hyprlandLayoutGapsOverride)
minimum: 0
maximum: 50
unit: "px"
defaultValue: Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4))
onSliderValueChanged: newValue => SettingsData.set("hyprlandLayoutGapsOverride", newValue)
}
SettingsToggleRow {
tab: "theme"
tags: ["hyprland", "radius", "override", "rounding"]
settingKey: "hyprlandLayoutRadiusOverrideEnabled"
text: I18n.tr("Override Corner Radius")
description: I18n.tr("Use custom window rounding instead of theme radius")
checked: SettingsData.hyprlandLayoutRadiusOverride >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("hyprlandLayoutRadiusOverride", SettingsData.cornerRadius);
return;
}
SettingsData.set("hyprlandLayoutRadiusOverride", -1);
}
}
SettingsSliderRow {
tab: "theme"
tags: ["hyprland", "radius", "override", "rounding"]
settingKey: "hyprlandLayoutRadiusOverride"
text: I18n.tr("Window Rounding")
description: I18n.tr("Rounded corners for windows (decoration.rounding)")
visible: SettingsData.hyprlandLayoutRadiusOverride >= 0
value: Math.max(0, SettingsData.hyprlandLayoutRadiusOverride)
minimum: 0
maximum: 100
unit: "px"
defaultValue: SettingsData.cornerRadius
onSliderValueChanged: newValue => SettingsData.set("hyprlandLayoutRadiusOverride", newValue)
}
SettingsToggleRow {
tab: "theme"
tags: ["hyprland", "border", "override"]
settingKey: "hyprlandLayoutBorderSizeEnabled"
text: I18n.tr("Override Border Size")
description: I18n.tr("Use custom border size")
checked: SettingsData.hyprlandLayoutBorderSize >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("hyprlandLayoutBorderSize", 2);
return;
}
SettingsData.set("hyprlandLayoutBorderSize", -1);
}
}
SettingsSliderRow {
tab: "theme"
tags: ["hyprland", "border", "override"]
settingKey: "hyprlandLayoutBorderSize"
text: I18n.tr("Border Size")
description: I18n.tr("Width of window border (general.border_size)")
visible: SettingsData.hyprlandLayoutBorderSize >= 0
value: Math.max(0, SettingsData.hyprlandLayoutBorderSize)
minimum: 0
maximum: 10
unit: "px"
defaultValue: 2
onSliderValueChanged: newValue => SettingsData.set("hyprlandLayoutBorderSize", newValue)
}
SettingsToggleRow {
tab: "theme"
tags: ["hyprland", "resize", "border", "mouse", "drag"]
settingKey: "hyprlandResizeOnBorder"
text: I18n.tr("Resize on Border")
description: I18n.tr("Resize windows by dragging their edges with the mouse")
checked: SettingsData.hyprlandResizeOnBorder
onToggled: checked => SettingsData.set("hyprlandResizeOnBorder", checked)
}
}
SettingsCard {
tab: "theme"
tags: ["mangowc", "mango", "dwl", "layout", "gaps", "radius", "window", "border"]
title: I18n.tr("MangoWC Layout Overrides")
settingKey: "mangoLayout"
iconName: "crop_square"
visible: CompositorService.isDwl || CompositorService.isMango
SettingsToggleRow {
tab: "theme"
tags: ["mangowc", "mango", "gaps", "override"]
settingKey: "mangoLayoutGapsOverrideEnabled"
text: I18n.tr("Override Gaps")
description: I18n.tr("Use custom gaps instead of bar spacing")
checked: SettingsData.mangoLayoutGapsOverride >= 0
onToggled: checked => {
if (checked) {
const currentGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4));
SettingsData.set("mangoLayoutGapsOverride", currentGaps);
return;
}
SettingsData.set("mangoLayoutGapsOverride", -1);
}
}
SettingsSliderRow {
tab: "theme"
tags: ["mangowc", "mango", "gaps", "override"]
settingKey: "mangoLayoutGapsOverride"
text: I18n.tr("Window Gaps")
description: I18n.tr("Space between windows (gappih/gappiv/gappoh/gappov)")
visible: SettingsData.mangoLayoutGapsOverride >= 0
value: Math.max(0, SettingsData.mangoLayoutGapsOverride)
minimum: 0
maximum: 50
unit: "px"
defaultValue: Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4))
onSliderValueChanged: newValue => SettingsData.set("mangoLayoutGapsOverride", newValue)
}
SettingsToggleRow {
tab: "theme"
tags: ["mangowc", "mango", "radius", "override"]
settingKey: "mangoLayoutRadiusOverrideEnabled"
text: I18n.tr("Override Corner Radius")
description: I18n.tr("Use custom window radius instead of theme radius")
checked: SettingsData.mangoLayoutRadiusOverride >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("mangoLayoutRadiusOverride", SettingsData.cornerRadius);
return;
}
SettingsData.set("mangoLayoutRadiusOverride", -1);
}
}
SettingsSliderRow {
tab: "theme"
tags: ["mangowc", "mango", "radius", "override"]
settingKey: "mangoLayoutRadiusOverride"
text: I18n.tr("Window Corner Radius")
description: I18n.tr("Rounded corners for windows (border_radius)")
visible: SettingsData.mangoLayoutRadiusOverride >= 0
value: Math.max(0, SettingsData.mangoLayoutRadiusOverride)
minimum: 0
maximum: 100
unit: "px"
defaultValue: SettingsData.cornerRadius
onSliderValueChanged: newValue => SettingsData.set("mangoLayoutRadiusOverride", newValue)
}
SettingsToggleRow {
tab: "theme"
tags: ["mangowc", "mango", "border", "override"]
settingKey: "mangoLayoutBorderSizeEnabled"
text: I18n.tr("Override Border Size")
description: I18n.tr("Use custom border size")
checked: SettingsData.mangoLayoutBorderSize >= 0
onToggled: checked => {
if (checked) {
SettingsData.set("mangoLayoutBorderSize", 2);
return;
}
SettingsData.set("mangoLayoutBorderSize", -1);
}
}
SettingsSliderRow {
tab: "theme"
tags: ["mangowc", "mango", "border", "override"]
settingKey: "mangoLayoutBorderSize"
text: I18n.tr("Border Size")
description: I18n.tr("Width of window border (borderpx)")
visible: SettingsData.mangoLayoutBorderSize >= 0
value: Math.max(0, SettingsData.mangoLayoutBorderSize)
minimum: 0
maximum: 10
unit: "px"
defaultValue: 2
onSliderValueChanged: newValue => SettingsData.set("mangoLayoutBorderSize", newValue)
}
}
SettingsCard {
tab: "theme"
tags: ["modal", "darken", "background", "overlay"]
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
StyledRect {
@@ -12,6 +13,7 @@ StyledRect {
property string tab: ""
property var tags: []
property string settingKey: ""
property string title: ""
property string description: ""
@@ -29,6 +31,34 @@ StyledRect {
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
function findParentFlickable() {
let p = root.parent;
while (p) {
if (p.hasOwnProperty("contentY") && p.hasOwnProperty("contentItem"))
return p;
p = p.parent;
}
return null;
}
Component.onCompleted: {
if (!settingKey)
return;
const key = settingKey;
Qt.callLater(() => {
if (!root.parent)
return;
const flickable = findParentFlickable();
if (flickable)
SettingsSearchService.registerCard(key, root, flickable);
});
}
Component.onDestruction: {
if (settingKey)
SettingsSearchService.unregisterCard(settingKey);
}
Column {
id: contentColumn
anchors.left: parent.left
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
StyledRect {
@@ -12,6 +13,7 @@ StyledRect {
property string tab: ""
property var tags: []
property string settingKey: ""
property string title: ""
property string description: ""
@@ -28,6 +30,34 @@ StyledRect {
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
function findParentFlickable() {
let p = root.parent;
while (p) {
if (p.hasOwnProperty("contentY") && p.hasOwnProperty("contentItem"))
return p;
p = p.parent;
}
return null;
}
Component.onCompleted: {
if (!settingKey)
return;
const key = settingKey;
Qt.callLater(() => {
if (!root.parent)
return;
const flickable = findParentFlickable();
if (flickable)
SettingsSearchService.registerCard(key, root, flickable);
});
}
Component.onDestruction: {
if (settingKey)
SettingsSearchService.unregisterCard(settingKey);
}
Column {
id: mainColumn
anchors.left: parent.left
@@ -25,12 +25,14 @@ Item {
}
property bool hasMultipleBars: SettingsData.barConfigs.length > 1
property int pluginCatalogRevision: 0
DankTooltipV2 {
id: sharedTooltip
}
property var baseWidgetDefinitions: {
pluginCatalogRevision;
var coreWidgets = [
{
"id": "layout",
@@ -274,6 +276,30 @@ Item {
return coreWidgets;
}
Connections {
target: PluginService
function onPluginDataChanged() {
widgetsTab.pluginCatalogRevision++;
}
function onPluginListUpdated() {
widgetsTab.pluginCatalogRevision++;
}
function onPluginLoaded() {
widgetsTab.pluginCatalogRevision++;
}
function onPluginStateChanged() {
widgetsTab.pluginCatalogRevision++;
}
function onPluginUnloaded() {
widgetsTab.pluginCatalogRevision++;
}
}
property var defaultLeftWidgets: [
{
"id": "launcherButton",
+17 -42
View File
@@ -17,6 +17,8 @@ Item {
LayoutMirroring.childrenInherit: true
property var parentModal: null
property bool pageActive: true
property bool componentReady: false
property var windowRulesIncludeStatus: ({
"exists": false,
"included": false,
@@ -32,6 +34,13 @@ Item {
property string expandedExternalId: ""
readonly property string dmsRulesFileName: CompositorService.isNiri ? "dms/windowrules.kdl" : CompositorService.isMango ? "dms/windowrules.conf" : "dms/windowrules.lua"
Component.onDestruction: SettingsSearchService.unregisterCard("windowRules")
onPageActiveChanged: {
if (componentReady && pageActive)
loadWindowRules();
}
readonly property var matchLabels: ({
"appId": I18n.tr("App ID"),
"title": I18n.tr("Title"),
@@ -182,12 +191,15 @@ Item {
function loadWindowRules() {
const compositor = CompositorService.compositor;
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango") {
checkingInclude = false;
windowRules = [];
externalRules = [];
return;
}
checkingInclude = true;
Proc.runCommand("load-windowrules", ["dms", "config", "windowrules", "list", compositor], (output, exitCode) => {
checkingInclude = false;
if (exitCode !== 0) {
windowRules = [];
externalRules = [];
@@ -258,44 +270,6 @@ Item {
});
}
function checkWindowRulesIncludeStatus() {
const compositor = CompositorService.compositor;
if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "mango") {
windowRulesIncludeStatus = {
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
};
return;
}
const filename = (compositor === "niri") ? "windowrules.kdl" : (compositor === "mango") ? "windowrules.conf" : "windowrules.lua";
checkingInclude = true;
Proc.runCommand("check-windowrules-include", ["dms", "config", "resolve-include", compositor, filename], (output, exitCode) => {
checkingInclude = false;
if (exitCode !== 0) {
windowRulesIncludeStatus = {
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
};
return;
}
try {
windowRulesIncludeStatus = JSON.parse(output.trim());
} catch (e) {
windowRulesIncludeStatus = {
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
};
}
});
}
function fixWindowRulesInclude() {
if (readOnly) {
showHyprlandReadOnlyWarning();
@@ -320,7 +294,6 @@ Item {
return;
if (CompositorService.isMango)
MangoService.reloadConfig();
checkWindowRulesIncludeStatus();
loadWindowRules();
});
}
@@ -372,10 +345,12 @@ Item {
}
Component.onCompleted: {
if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango) {
checkWindowRulesIncludeStatus();
componentReady = true;
Qt.callLater(() => {
SettingsSearchService.registerCard("windowRules", headerSection, flickable);
if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango)
loadWindowRules();
}
});
}
DankFlickable {
@@ -0,0 +1,230 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Modules.Settings.Widgets
SettingsCard {
iconName: "palette"
title: I18n.tr("Workspace Appearance")
settingKey: "workspaceAppearance"
collapsible: true
expanded: false
SettingsButtonGroupRow {
text: I18n.tr("Focused Color")
model: ["pri", "s", "sc", "sch", "none"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceColorMode) {
case "s":
return 1;
case "sc":
return 2;
case "sch":
return 3;
case "none":
return 4;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "s", "sc", "sch", "none"];
SettingsData.set("workspaceColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
SettingsButtonGroupRow {
text: I18n.tr("Occupied Color")
model: ["none", "sec", "s", "sc", "sch", "schh"]
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceOccupiedColorMode) {
case "sec":
return 1;
case "s":
return 2;
case "sc":
return 3;
case "sch":
return 4;
case "schh":
return 5;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["none", "sec", "s", "sc", "sch", "schh"];
SettingsData.set("workspaceOccupiedColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
}
SettingsButtonGroupRow {
text: I18n.tr("Unfocused Color")
model: ["def", "s", "sc", "sch"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceUnfocusedColorMode) {
case "s":
return 1;
case "sc":
return 2;
case "sch":
return 3;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "s", "sc", "sch"];
SettingsData.set("workspaceUnfocusedColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
}
SettingsButtonGroupRow {
text: I18n.tr("Urgent Color")
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
model: ["err", "pri", "sec", "s", "sc"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceUrgentColorMode) {
case "primary":
return 1;
case "secondary":
return 2;
case "s":
return 3;
case "sc":
return 4;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "primary", "secondary", "s", "sc"];
SettingsData.set("workspaceUrgentColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
SettingsToggleRow {
settingKey: "workspaceFocusedBorderEnabled"
tags: ["workspace", "border", "outline", "focused", "ring"]
text: I18n.tr("Focused Border")
description: I18n.tr("Show an outline ring around the focused workspace indicator")
checked: SettingsData.workspaceFocusedBorderEnabled
onToggled: checked => SettingsData.set("workspaceFocusedBorderEnabled", checked)
}
Column {
width: parent.width
spacing: Theme.spacingS
visible: SettingsData.workspaceFocusedBorderEnabled
leftPadding: Theme.spacingM
SettingsButtonGroupRow {
width: parent.width - parent.leftPadding
text: I18n.tr("Border Color")
model: [I18n.tr("Surface"), I18n.tr("Secondary"), I18n.tr("Primary")]
currentIndex: {
switch (SettingsData.workspaceFocusedBorderColor) {
case "surfaceText":
return 0;
case "secondary":
return 1;
case "primary":
return 2;
default:
return 2;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
let newColor = "primary";
switch (index) {
case 0:
newColor = "surfaceText";
break;
case 1:
newColor = "secondary";
break;
case 2:
newColor = "primary";
break;
}
SettingsData.set("workspaceFocusedBorderColor", newColor);
}
}
SettingsSliderRow {
width: parent.width - parent.leftPadding
text: I18n.tr("Thickness")
value: SettingsData.workspaceFocusedBorderThickness
minimum: 1
maximum: 6
unit: "px"
defaultValue: 2
onSliderValueChanged: newValue => SettingsData.set("workspaceFocusedBorderThickness", newValue)
}
}
}
+3 -226
View File
@@ -15,7 +15,9 @@ Item {
Column {
id: mainColumn
topPadding: 4
topPadding: Theme.spacingXL
bottomPadding: Theme.spacingXL
width: Math.min(550, parent.width - Theme.spacingL * 2)
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXL
@@ -196,231 +198,6 @@ Item {
}
}
SettingsCard {
width: parent.width
iconName: "palette"
title: I18n.tr("Workspace Appearance")
settingKey: "workspaceAppearance"
SettingsButtonGroupRow {
text: I18n.tr("Focused Color")
model: ["pri", "s", "sc", "sch", "none"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceColorMode) {
case "s":
return 1;
case "sc":
return 2;
case "sch":
return 3;
case "none":
return 4;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "s", "sc", "sch", "none"];
SettingsData.set("workspaceColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
SettingsButtonGroupRow {
text: I18n.tr("Occupied Color")
model: ["none", "sec", "s", "sc", "sch", "schh"]
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceOccupiedColorMode) {
case "sec":
return 1;
case "s":
return 2;
case "sc":
return 3;
case "sch":
return 4;
case "schh":
return 5;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["none", "sec", "s", "sc", "sch", "schh"];
SettingsData.set("workspaceOccupiedColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango
}
SettingsButtonGroupRow {
text: I18n.tr("Unfocused Color")
model: ["def", "s", "sc", "sch"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceUnfocusedColorMode) {
case "s":
return 1;
case "sc":
return 2;
case "sch":
return 3;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "s", "sc", "sch"];
SettingsData.set("workspaceUnfocusedColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
}
SettingsButtonGroupRow {
text: I18n.tr("Urgent Color")
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
model: ["err", "pri", "sec", "s", "sc"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceUrgentColorMode) {
case "primary":
return 1;
case "secondary":
return 2;
case "s":
return 3;
case "sc":
return 4;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "primary", "secondary", "s", "sc"];
SettingsData.set("workspaceUrgentColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
SettingsToggleRow {
settingKey: "workspaceFocusedBorderEnabled"
tags: ["workspace", "border", "outline", "focused", "ring"]
text: I18n.tr("Focused Border")
description: I18n.tr("Show an outline ring around the focused workspace indicator")
checked: SettingsData.workspaceFocusedBorderEnabled
onToggled: checked => SettingsData.set("workspaceFocusedBorderEnabled", checked)
}
Column {
width: parent.width
spacing: Theme.spacingS
visible: SettingsData.workspaceFocusedBorderEnabled
leftPadding: Theme.spacingM
SettingsButtonGroupRow {
width: parent.width - parent.leftPadding
text: I18n.tr("Border Color")
model: [I18n.tr("Surface"), I18n.tr("Secondary"), I18n.tr("Primary")]
currentIndex: {
switch (SettingsData.workspaceFocusedBorderColor) {
case "surfaceText":
return 0;
case "secondary":
return 1;
case "primary":
return 2;
default:
return 2;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
let newColor = "primary";
switch (index) {
case 0:
newColor = "surfaceText";
break;
case 1:
newColor = "secondary";
break;
case 2:
newColor = "primary";
break;
}
SettingsData.set("workspaceFocusedBorderColor", newColor);
}
}
SettingsSliderRow {
width: parent.width - parent.leftPadding
text: I18n.tr("Thickness")
value: SettingsData.workspaceFocusedBorderThickness
minimum: 1
maximum: 6
unit: "px"
defaultValue: 2
onSliderValueChanged: newValue => SettingsData.set("workspaceFocusedBorderThickness", newValue)
}
}
}
SettingsCard {
width: parent.width
iconName: "label"
+4 -3
View File
@@ -7,6 +7,7 @@ Item {
property color rippleColor: Theme.primary
property real cornerRadius: 0
property bool enableRipple: typeof SettingsData !== "undefined" ? (SettingsData.enableRippleEffects ?? true) : true
property int animationDuration: Theme.expressiveDurations.expressiveDefaultSpatial
property real _rippleX: 0
property real _rippleY: 0
@@ -58,18 +59,18 @@ Item {
property: "rippleRadius"
from: 0
to: root._rippleMaxRadius
duration: Theme.expressiveDurations.expressiveDefaultSpatial
duration: root.animationDuration
easing.bezierCurve: Theme.expressiveCurves.standardDecel
}
SequentialAnimation {
PauseAnimation {
duration: Math.round(Theme.expressiveDurations.expressiveDefaultSpatial * 0.6)
duration: Math.round(root.animationDuration * 0.6)
}
DankAnim {
target: rippleFx
property: "rippleOpacity"
to: 0
duration: Theme.expressiveDurations.expressiveDefaultSpatial
duration: root.animationDuration
easing.bezierCurve: Theme.expressiveCurves.standard
}
}
@@ -62,7 +62,20 @@ CATEGORY_KEYWORDS = {
"Time & Weather": ["clock", "forecast", "date"],
"Keyboard Shortcuts": ["keys", "bindings", "hotkey"],
"Dank Bar": ["panel", "topbar", "statusbar"],
"Workspaces": ["virtual desktops", "spaces"],
"Compositor": [
"virtual desktops",
"spaces",
"window",
"rules",
"matching",
"floating",
"fullscreen",
"opacity",
"gaps",
"borders",
"corner rounding",
"overrides",
],
"Dock": ["taskbar", "launcher bar"],
"Network": ["connectivity", "online"],
"System": ["os", "linux"],
@@ -82,14 +95,6 @@ CATEGORY_KEYWORDS = {
"Displays": ["monitor", "screen", "resolution"],
"Desktop Widgets": ["conky", "desktop clock"],
"Audio": ["sound", "volume", "speaker", "microphone", "headphones", "pipewire"],
"Window Rules": [
"window",
"rules",
"matching",
"floating",
"fullscreen",
"opacity",
],
"Locale": ["locale", "language", "country"],
"Greeter": ["login", "greetd", "display manager"],
"Multiplexers": ["tmux", "zellij", "terminal"],
@@ -104,8 +109,13 @@ TAB_INDEX_MAP = {
"TimeWeatherTab.qml": 1,
"KeybindsTab.qml": 2,
"DankBarTab.qml": 3,
"CompositorTab.qml": 4,
"CompositorLayoutTab.qml": 4,
"WorkspacesTab.qml": 4,
"WindowRulesTab.qml": 4,
"DockTab.qml": 5,
"DankBarAppearanceTab.qml": 6,
"WorkspaceAppearanceCard.qml": 6,
"NetworkTab.qml": 7,
"PrinterTab.qml": 8,
"LauncherTab.qml": 9,
@@ -127,7 +137,6 @@ TAB_INDEX_MAP = {
"GammaControlTab.qml": 25,
"DisplayWidgetsTab.qml": 26,
"DesktopWidgetsTab.qml": 27,
"WindowRulesTab.qml": 28,
"AudioTab.qml": 29,
"LocaleTab.qml": 30,
"GreeterTab.qml": 31,
@@ -143,8 +152,9 @@ TAB_CATEGORY_MAP = {
1: "Time & Weather",
2: "Keyboard Shortcuts",
3: "Dank Bar",
4: "Workspaces",
4: "Compositor",
5: "Dock",
6: "Dank Bar",
7: "Network",
8: "System",
9: "Launcher",
@@ -166,7 +176,6 @@ TAB_CATEGORY_MAP = {
25: "Displays",
26: "Displays",
27: "Desktop Widgets",
28: "Window Rules",
29: "Audio",
30: "Locale",
31: "Greeter",
@@ -302,9 +311,9 @@ def extract_property(block, prop_name):
def find_settings_components(content, filename):
results = []
tab_index = TAB_INDEX_MAP.get(filename, -1)
file_tab_index = TAB_INDEX_MAP.get(filename, -1)
if tab_index == -1:
if file_tab_index == -1:
return results
for component in SEARCHABLE_COMPONENTS:
@@ -321,6 +330,11 @@ def find_settings_components(content, filename):
if not setting_key:
continue
tab_index = file_tab_index
tab_raw = extract_property(block, "tab")
if tab_raw and tab_raw.strip("\"'") == "appearance":
tab_index = 6
title_raw = extract_property(block, "title")
text_raw = extract_property(block, "text")
label = None
@@ -489,7 +503,7 @@ def extract_settings_index(root_dir):
seen_keys = set()
for qml_file in settings_dir.glob("*.qml"):
if not qml_file.name.endswith("Tab.qml"):
if qml_file.name not in TAB_INDEX_MAP:
continue
with open(qml_file, "r", encoding="utf-8") as f:
@@ -502,6 +516,24 @@ def extract_settings_index(root_dir):
seen_keys.add(key)
all_entries.append(entry)
if "windowRules" not in seen_keys:
all_entries.append(
{
"section": "windowRules",
"label": "Window Rules",
"tabIndex": 4,
"category": "Compositor",
"keywords": enrich_keywords(
"Window Rules",
"Define compositor rules for window behavior",
"Compositor",
["matching", "floating", "fullscreen", "opacity"],
),
"icon": "select_window",
"description": "Define compositor rules for window behavior",
}
)
return all_entries
File diff suppressed because it is too large Load Diff