diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index e76d5f47..d63c9800 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -164,6 +164,8 @@ Singleton { property real popupTransparency: 1.0 property real dockTransparency: 1 property string widgetBackgroundColor: "sch" + property string widgetBackgroundCustomColor: "#6750A4" + property real widgetBackgroundCustomStrength: 0.50 property string widgetColorMode: "default" property string controlCenterTileColorMode: "primary" property string buttonColorMode: "primary" @@ -385,11 +387,16 @@ Singleton { property bool dwlShowAllTags: false property bool workspaceActiveAppHighlightEnabled: false property string workspaceColorMode: "default" + property string workspaceFocusedCustomColor: "#6750A4" property string workspaceOccupiedColorMode: "none" + property string workspaceOccupiedCustomColor: "#625B71" property string workspaceUnfocusedColorMode: "default" + property string workspaceUnfocusedCustomColor: "#49454E" property string workspaceUrgentColorMode: "default" + property string workspaceUrgentCustomColor: "#B3261E" property bool workspaceFocusedBorderEnabled: false property string workspaceFocusedBorderColor: "primary" + property string workspaceFocusedBorderCustomColor: "#6750A4" property int workspaceFocusedBorderThickness: 2 property var workspaceNameIcons: ({}) property bool waveProgressEnabled: true diff --git a/quickshell/Common/Theme.qml b/quickshell/Common/Theme.qml index fd03fed8..16c0ad4b 100644 --- a/quickshell/Common/Theme.qml +++ b/quickshell/Common/Theme.qml @@ -450,7 +450,9 @@ Singleton { "primaryText": getMatugenColor("on_primary", "#ffffff"), "primaryContainer": getMatugenColor("primary_container", "#1976d2"), "secondary": getMatugenColor("secondary", "#8ab4f8"), + "secondaryContainer": getMatugenColor("secondary_container", getMatugenColor("surface_container_high", "#292b2f")), "tertiary": getMatugenColor("tertiary", "#efb8c8"), + "tertiaryContainer": getMatugenColor("tertiary_container", getMatugenColor("surface_container_high", "#292b2f")), "surface": getMatugenColor("surface", "#1a1c1e"), "surfaceText": getMatugenColor("on_background", "#e3e8ef"), "surfaceVariant": getMatugenColor("surface_variant", "#44464f"), @@ -521,7 +523,6 @@ Singleton { property color primary: currentThemeData.primary property color primaryText: currentThemeData.primaryText - property color primaryContainer: currentThemeData.primaryContainer property color secondary: currentThemeData.secondary property color tertiary: currentThemeData.tertiary || currentThemeData.secondary property color surface: currentThemeData.surface @@ -536,6 +537,9 @@ Singleton { property color surfaceContainer: currentThemeData.surfaceContainer property color surfaceContainerHigh: currentThemeData.surfaceContainerHigh property color surfaceContainerHighest: currentThemeData.surfaceContainerHighest || surfaceContainerHigh + property color primaryContainer: currentThemeData.primaryContainer || blend(surfaceContainerHigh, primary, 0.45) + property color secondaryContainer: currentThemeData.secondaryContainer || blend(surfaceContainerHigh, secondary, 0.35) + property color tertiaryContainer: currentThemeData.tertiaryContainer || blend(surfaceContainerHigh, tertiary, 0.35) property color onSurface: surfaceText property color onSurfaceVariant: surfaceVariantText @@ -1430,9 +1434,22 @@ Singleton { property bool widgetBackgroundHasAlpha: { const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch"; - return colorMode === "sth"; + return colorMode === "sth" || colorMode === "custom"; } + function safeColor(value, fallback) { + try { + if (value === undefined || value === null || value === "") + return fallback; + return Qt.color(value); + } catch (e) { + return fallback; + } + } + + readonly property color widgetBackgroundCustomBaseColor: safeColor(typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundCustomColor : "#6750A4", primaryContainer) + readonly property real widgetBackgroundCustomStrength: Math.max(0, Math.min(1, typeof SettingsData !== "undefined" ? (SettingsData.widgetBackgroundCustomStrength ?? 0.4) : 0.4)) + property var widgetBaseBackgroundColor: { const colorMode = typeof SettingsData !== "undefined" ? SettingsData.widgetBackgroundColor : "sch"; switch (colorMode) { @@ -1442,6 +1459,14 @@ Singleton { return surfaceContainer; case "sch": return surfaceContainerHigh; + case "primaryContainer": + return primaryContainer; + case "secondaryContainer": + return secondaryContainer; + case "tertiaryContainer": + return tertiaryContainer; + case "custom": + return blend(surfaceContainerHigh, widgetBackgroundCustomBaseColor, widgetBackgroundCustomStrength); case "sth": default: return surfaceTextHover; diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 65deac09..ac3bd13c 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -19,6 +19,8 @@ var SPEC = { dockTransparency: { def: 1.0, coerce: percentToUnit }, widgetBackgroundColor: { def: "sch" }, + widgetBackgroundCustomColor: { def: "#6750A4" }, + widgetBackgroundCustomStrength: { def: 0.50, coerce: percentToUnit }, widgetColorMode: { def: "default" }, controlCenterTileColorMode: { def: "primary" }, buttonColorMode: { def: "primary" }, @@ -144,11 +146,16 @@ var SPEC = { dwlShowAllTags: { def: false }, workspaceActiveAppHighlightEnabled: { def: false }, workspaceColorMode: { def: "default" }, + workspaceFocusedCustomColor: { def: "#6750A4" }, workspaceOccupiedColorMode: { def: "none" }, + workspaceOccupiedCustomColor: { def: "#625B71" }, workspaceUnfocusedColorMode: { def: "default" }, + workspaceUnfocusedCustomColor: { def: "#49454E" }, workspaceUrgentColorMode: { def: "default" }, + workspaceUrgentCustomColor: { def: "#B3261E" }, workspaceFocusedBorderEnabled: { def: false }, workspaceFocusedBorderColor: { def: "primary" }, + workspaceFocusedBorderCustomColor: { def: "#6750A4" }, workspaceFocusedBorderThickness: { def: 2 }, workspaceNameIcons: { def: {} }, waveProgressEnabled: { def: true }, diff --git a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml index 73a256a1..74a6bb1d 100644 --- a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml +++ b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml @@ -1192,38 +1192,25 @@ Item { return Math.max(baseHeight + iconsExtraHeight, contentImplicitHeight + padding); } - readonly property color unfocusedColor: { - switch (SettingsData.workspaceUnfocusedColorMode) { - case "s": - return Theme.surface; - case "sc": - return Theme.surfaceContainer; - case "sch": - return Theme.surfaceContainerHigh; - default: - return Theme.surfaceTextAlpha; - } - } - - readonly property color activeColor: { - switch (SettingsData.workspaceColorMode) { - case "s": - return Theme.surface; - case "sc": - return Theme.surfaceContainer; - case "sch": - return Theme.surfaceContainerHigh; - case "none": - return unfocusedColor; - default: + function colorFromMode(mode, fallbackColor, customColor, customFallbackColor) { + switch (mode) { + case "primary": + case "pri": return Theme.primary; - } - } - - readonly property color occupiedColor: { - switch (SettingsData.workspaceOccupiedColorMode) { + case "primaryContainer": + return Theme.primaryContainer; + case "secondary": case "sec": return Theme.secondary; + case "secondaryContainer": + return Theme.secondaryContainer; + case "tertiary": + case "ter": + return Theme.tertiary; + case "tertiaryContainer": + return Theme.tertiaryContainer; + case "surfaceText": + return Theme.surfaceText; case "s": return Theme.surface; case "sc": @@ -1232,37 +1219,34 @@ Item { return Theme.surfaceContainerHigh; case "schh": return Theme.surfaceContainerHighest; - default: - return unfocusedColor; - } - } - - readonly property color urgentColor: { - switch (SettingsData.workspaceUrgentColorMode) { - case "primary": - return Theme.primary; - case "secondary": - return Theme.secondary; - case "s": - return Theme.surface; - case "sc": - return Theme.surfaceContainer; - default: + case "error": + case "err": return Theme.error; + case "custom": + return Theme.safeColor(customColor, customFallbackColor); + default: + return fallbackColor; } } - readonly property color focusedBorderColor: { - switch (SettingsData.workspaceFocusedBorderColor) { - case "surfaceText": - return Theme.surfaceText; - case "secondary": - return Theme.secondary; - default: - return Theme.primary; - } + readonly property color unfocusedColor: colorFromMode(SettingsData.workspaceUnfocusedColorMode, Theme.surfaceTextAlpha, SettingsData.workspaceUnfocusedCustomColor, Theme.surfaceTextAlpha) + + readonly property color activeColor: { + if (SettingsData.workspaceColorMode === "none") + return unfocusedColor; + return colorFromMode(SettingsData.workspaceColorMode, Theme.primary, SettingsData.workspaceFocusedCustomColor, Theme.primary); } + readonly property color occupiedColor: { + if (SettingsData.workspaceOccupiedColorMode === "none") + return unfocusedColor; + return colorFromMode(SettingsData.workspaceOccupiedColorMode, unfocusedColor, SettingsData.workspaceOccupiedCustomColor, Theme.secondary); + } + + readonly property color urgentColor: colorFromMode(SettingsData.workspaceUrgentColorMode, Theme.error, SettingsData.workspaceUrgentCustomColor, Theme.error) + + readonly property color focusedBorderColor: colorFromMode(SettingsData.workspaceFocusedBorderColor, Theme.primary, SettingsData.workspaceFocusedBorderCustomColor, Theme.primary) + function getContrastingIconColor(bgColor) { const luminance = 0.299 * bgColor.r + 0.587 * bgColor.g + 0.114 * bgColor.b; return luminance > 0.4 ? Qt.rgba(0.15, 0.15, 0.15, 1) : Qt.rgba(0.8, 0.8, 0.8, 1); diff --git a/quickshell/Modules/Settings/ThemeColorsTab.qml b/quickshell/Modules/Settings/ThemeColorsTab.qml index 9e9dc84c..7e5fb0d5 100644 --- a/quickshell/Modules/Settings/ThemeColorsTab.qml +++ b/quickshell/Modules/Settings/ThemeColorsTab.qml @@ -20,6 +20,31 @@ Item { property var cachedMatugenSchemes: Theme.availableMatugenSchemes.map(option => option.label) property var installedRegistryThemes: [] property var templateDetection: [] + readonly property var widgetBackgroundOptions: [({ + "value": "sth", + "label": I18n.tr("Subtle Overlay", "widget background color option") + }), ({ + "value": "s", + "label": I18n.tr("Surface", "widget background color option") + }), ({ + "value": "sc", + "label": I18n.tr("Surface Container", "widget background color option") + }), ({ + "value": "sch", + "label": I18n.tr("Surface High", "widget background color option") + }), ({ + "value": "primaryContainer", + "label": I18n.tr("Primary Container", "widget background color option") + }), ({ + "value": "secondaryContainer", + "label": I18n.tr("Secondary Container", "widget background color option") + }), ({ + "value": "tertiaryContainer", + "label": I18n.tr("Tertiary Container", "widget background color option") + }), ({ + "value": "custom", + "label": I18n.tr("Custom", "widget background color option") + })] property var cursorIncludeStatus: ({ "exists": false, @@ -1524,10 +1549,10 @@ Item { SettingsButtonGroupRow { tab: "theme" - tags: ["widget", "style", "colorful", "default"] + tags: ["widget", "text", "style", "colorful", "default"] settingKey: "widgetColorMode" - text: I18n.tr("Widget Style") - description: I18n.tr("Change bar appearance") + text: I18n.tr("Widget Text Style") + description: I18n.tr("Choose neutral or accent-colored widget text") model: [I18n.tr("Default", "widget style option"), I18n.tr("Colorful", "widget style option")] currentIndex: SettingsData.widgetColorMode === "colorful" ? 1 : 0 onSelectionChanged: (index, selected) => { @@ -1537,38 +1562,41 @@ Item { } } - SettingsButtonGroupRow { + WorkspaceColorRow { tab: "theme" - tags: ["widget", "background", "color"] + tags: ["widget", "background", "color", "surface", "material"] settingKey: "widgetBackgroundColor" text: I18n.tr("Widget Background Color") description: I18n.tr("Choose the background color for widgets") - model: ["sth", "s", "sc", "sch"] - buttonHeight: 20 - minButtonWidth: 32 - buttonPadding: Theme.spacingS - checkIconSize: Theme.iconSizeSmall - 2 - textSize: Theme.fontSizeSmall - 2 - spacing: 1 - currentIndex: { - switch (SettingsData.widgetBackgroundColor) { - case "sth": - return 0; - case "s": - return 1; - case "sc": - return 2; - case "sch": - return 3; - default: - return 0; - } - } - onSelectionChanged: (index, selected) => { - if (!selected) - return; - const colorOptions = ["sth", "s", "sc", "sch"]; - SettingsData.set("widgetBackgroundColor", colorOptions[index]); + dropdownWidth: 220 + options: themeColorsTab.widgetBackgroundOptions + currentMode: SettingsData.widgetBackgroundColor + customColor: SettingsData.widgetBackgroundCustomColor || "#6750A4" + pickerTitle: I18n.tr("Widget Background Color") + onModeSelected: mode => SettingsData.set("widgetBackgroundColor", mode) + onCustomColorSelected: selectedColor => SettingsData.set("widgetBackgroundCustomColor", selectedColor.toString()) + } + + SettingsSliderRow { + id: widgetBackgroundCustomStrengthSlider + visible: SettingsData.widgetBackgroundColor === "custom" + tab: "theme" + tags: ["widget", "background", "color", "custom", "blend"] + settingKey: "widgetBackgroundCustomStrength" + text: I18n.tr("Custom Blend") + description: I18n.tr("Blend between Surface High and the selected custom color") + value: Math.round(SettingsData.widgetBackgroundCustomStrength * 100) + minimum: 0 + maximum: 100 + unit: "%" + defaultValue: 40 + onSliderValueChanged: newValue => SettingsData.set("widgetBackgroundCustomStrength", newValue / 100) + + Binding { + target: widgetBackgroundCustomStrengthSlider + property: "value" + value: Math.round(SettingsData.widgetBackgroundCustomStrength * 100) + restoreMode: Binding.RestoreBinding } } diff --git a/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml b/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml index d0a99d33..ed44040c 100644 --- a/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml +++ b/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml @@ -4,41 +4,195 @@ import qs.Services import qs.Modules.Settings.Widgets SettingsCard { + id: root + iconName: "palette" title: I18n.tr("Workspace Appearance") settingKey: "workspaceAppearance" collapsible: true expanded: false - SettingsButtonGroupRow { + readonly property var focusedColorOptions: [({ + "value": "default", + "label": I18n.tr("Primary", "workspace color option") + }), ({ + "value": "primaryContainer", + "label": I18n.tr("Primary Container", "workspace color option") + }), ({ + "value": "secondary", + "label": I18n.tr("Secondary", "workspace color option") + }), ({ + "value": "secondaryContainer", + "label": I18n.tr("Secondary Container", "workspace color option") + }), ({ + "value": "tertiary", + "label": I18n.tr("Tertiary", "workspace color option") + }), ({ + "value": "tertiaryContainer", + "label": I18n.tr("Tertiary Container", "workspace color option") + }), ({ + "value": "s", + "label": I18n.tr("Surface", "workspace color option") + }), ({ + "value": "sc", + "label": I18n.tr("Surface Container", "workspace color option") + }), ({ + "value": "sch", + "label": I18n.tr("Surface High", "workspace color option") + }), ({ + "value": "schh", + "label": I18n.tr("Surface Highest", "workspace color option") + }), ({ + "value": "none", + "label": I18n.tr("None", "workspace color option") + }), ({ + "value": "custom", + "label": I18n.tr("Custom", "workspace color option") + })] + + readonly property var occupiedColorOptions: [({ + "value": "none", + "label": I18n.tr("None", "workspace color option") + }), ({ + "value": "primary", + "label": I18n.tr("Primary", "workspace color option") + }), ({ + "value": "primaryContainer", + "label": I18n.tr("Primary Container", "workspace color option") + }), ({ + "value": "sec", + "label": I18n.tr("Secondary", "workspace color option") + }), ({ + "value": "secondaryContainer", + "label": I18n.tr("Secondary Container", "workspace color option") + }), ({ + "value": "tertiary", + "label": I18n.tr("Tertiary", "workspace color option") + }), ({ + "value": "tertiaryContainer", + "label": I18n.tr("Tertiary Container", "workspace color option") + }), ({ + "value": "s", + "label": I18n.tr("Surface", "workspace color option") + }), ({ + "value": "sc", + "label": I18n.tr("Surface Container", "workspace color option") + }), ({ + "value": "sch", + "label": I18n.tr("Surface High", "workspace color option") + }), ({ + "value": "schh", + "label": I18n.tr("Surface Highest", "workspace color option") + }), ({ + "value": "custom", + "label": I18n.tr("Custom", "workspace color option") + })] + + readonly property var unfocusedColorOptions: [({ + "value": "default", + "label": I18n.tr("Default", "workspace color option") + }), ({ + "value": "surfaceText", + "label": I18n.tr("Surface Text", "workspace color option") + }), ({ + "value": "primary", + "label": I18n.tr("Primary", "workspace color option") + }), ({ + "value": "secondary", + "label": I18n.tr("Secondary", "workspace color option") + }), ({ + "value": "tertiary", + "label": I18n.tr("Tertiary", "workspace color option") + }), ({ + "value": "s", + "label": I18n.tr("Surface", "workspace color option") + }), ({ + "value": "sc", + "label": I18n.tr("Surface Container", "workspace color option") + }), ({ + "value": "sch", + "label": I18n.tr("Surface High", "workspace color option") + }), ({ + "value": "schh", + "label": I18n.tr("Surface Highest", "workspace color option") + }), ({ + "value": "custom", + "label": I18n.tr("Custom", "workspace color option") + })] + + readonly property var urgentColorOptions: [({ + "value": "default", + "label": I18n.tr("Error", "workspace color option") + }), ({ + "value": "primary", + "label": I18n.tr("Primary", "workspace color option") + }), ({ + "value": "primaryContainer", + "label": I18n.tr("Primary Container", "workspace color option") + }), ({ + "value": "secondary", + "label": I18n.tr("Secondary", "workspace color option") + }), ({ + "value": "secondaryContainer", + "label": I18n.tr("Secondary Container", "workspace color option") + }), ({ + "value": "tertiary", + "label": I18n.tr("Tertiary", "workspace color option") + }), ({ + "value": "tertiaryContainer", + "label": I18n.tr("Tertiary Container", "workspace color option") + }), ({ + "value": "s", + "label": I18n.tr("Surface", "workspace color option") + }), ({ + "value": "sc", + "label": I18n.tr("Surface Container", "workspace color option") + }), ({ + "value": "sch", + "label": I18n.tr("Surface High", "workspace color option") + }), ({ + "value": "custom", + "label": I18n.tr("Custom", "workspace color option") + })] + + readonly property var borderColorOptions: [({ + "value": "surfaceText", + "label": I18n.tr("Surface Text", "workspace color option") + }), ({ + "value": "primary", + "label": I18n.tr("Primary", "workspace color option") + }), ({ + "value": "primaryContainer", + "label": I18n.tr("Primary Container", "workspace color option") + }), ({ + "value": "secondary", + "label": I18n.tr("Secondary", "workspace color option") + }), ({ + "value": "secondaryContainer", + "label": I18n.tr("Secondary Container", "workspace color option") + }), ({ + "value": "tertiary", + "label": I18n.tr("Tertiary", "workspace color option") + }), ({ + "value": "tertiaryContainer", + "label": I18n.tr("Tertiary Container", "workspace color option") + }), ({ + "value": "custom", + "label": I18n.tr("Custom", "workspace color option") + })] + + readonly property bool workspaceStateColorsVisible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango + readonly property bool urgentWorkspaceColorsVisible: workspaceStateColorsVisible || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle + + WorkspaceColorRow { 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]); - } + settingKey: "workspaceColorMode" + tags: ["workspace", "focused", "color", "custom"] + options: root.focusedColorOptions + currentMode: SettingsData.workspaceColorMode + customColor: SettingsData.workspaceFocusedCustomColor || "#6750A4" + onModeSelected: mode => SettingsData.set("workspaceColorMode", mode) + onCustomColorSelected: selectedColor => SettingsData.set("workspaceFocusedCustomColor", selectedColor.toString()) } Rectangle { @@ -48,38 +202,16 @@ SettingsCard { opacity: 0.15 } - SettingsButtonGroupRow { + WorkspaceColorRow { text: I18n.tr("Occupied Color") - model: ["none", "sec", "s", "sc", "sch", "schh"] - visible: CompositorService.isNiri || CompositorService.isHyprland || 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]); - } + settingKey: "workspaceOccupiedColorMode" + tags: ["workspace", "occupied", "color", "custom"] + visible: root.workspaceStateColorsVisible + options: root.occupiedColorOptions + currentMode: SettingsData.workspaceOccupiedColorMode + customColor: SettingsData.workspaceOccupiedCustomColor || "#625B71" + onModeSelected: mode => SettingsData.set("workspaceOccupiedColorMode", mode) + onCustomColorSelected: selectedColor => SettingsData.set("workspaceOccupiedCustomColor", selectedColor.toString()) } Rectangle { @@ -90,33 +222,16 @@ SettingsCard { visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango } - SettingsButtonGroupRow { + WorkspaceColorRow { 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]); - } + settingKey: "workspaceUnfocusedColorMode" + tags: ["workspace", "unfocused", "color", "custom"] + options: root.unfocusedColorOptions + defaultColor: Theme.surfaceText + currentMode: SettingsData.workspaceUnfocusedColorMode + customColor: SettingsData.workspaceUnfocusedCustomColor || "#49454E" + onModeSelected: mode => SettingsData.set("workspaceUnfocusedColorMode", mode) + onCustomColorSelected: selectedColor => SettingsData.set("workspaceUnfocusedCustomColor", selectedColor.toString()) } Rectangle { @@ -127,36 +242,17 @@ SettingsCard { visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle } - SettingsButtonGroupRow { + WorkspaceColorRow { text: I18n.tr("Urgent Color") - visible: CompositorService.isNiri || CompositorService.isHyprland || 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]); - } + settingKey: "workspaceUrgentColorMode" + tags: ["workspace", "urgent", "color", "custom"] + visible: root.urgentWorkspaceColorsVisible + options: root.urgentColorOptions + defaultColor: Theme.error + currentMode: SettingsData.workspaceUrgentColorMode + customColor: SettingsData.workspaceUrgentCustomColor || "#B3261E" + onModeSelected: mode => SettingsData.set("workspaceUrgentColorMode", mode) + onCustomColorSelected: selectedColor => SettingsData.set("workspaceUrgentCustomColor", selectedColor.toString()) } Rectangle { @@ -181,39 +277,16 @@ SettingsCard { visible: SettingsData.workspaceFocusedBorderEnabled leftPadding: Theme.spacingM - SettingsButtonGroupRow { + WorkspaceColorRow { 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); - } + settingKey: "workspaceFocusedBorderColor" + tags: ["workspace", "focused", "border", "color", "custom"] + options: root.borderColorOptions + currentMode: SettingsData.workspaceFocusedBorderColor + customColor: SettingsData.workspaceFocusedBorderCustomColor || "#6750A4" + onModeSelected: mode => SettingsData.set("workspaceFocusedBorderColor", mode) + onCustomColorSelected: selectedColor => SettingsData.set("workspaceFocusedBorderCustomColor", selectedColor.toString()) } SettingsSliderRow { diff --git a/quickshell/Modules/Settings/WorkspaceColorRow.qml b/quickshell/Modules/Settings/WorkspaceColorRow.qml new file mode 100644 index 00000000..8f802560 --- /dev/null +++ b/quickshell/Modules/Settings/WorkspaceColorRow.qml @@ -0,0 +1,211 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets +import qs.Modules.Settings.Widgets + +Column { + id: root + + property string text: "" + property string description: "" + property string settingKey: "" + property string tab: "" + property var tags: [] + property var options: [] + property string currentMode: "default" + property color customColor: "#6750A4" + property string pickerTitle: text + property int dropdownWidth: 230 + property color defaultColor: Theme.primary + + readonly property var optionColorMap: { + var map = {}; + for (var i = 0; i < options.length; i++) + map[options[i].label] = root.colorForValue(options[i].value); + return map; + } + + function colorForValue(value) { + switch (value) { + case "primary": + case "pri": + return Theme.primary; + case "primaryContainer": + return Theme.primaryContainer; + case "secondary": + case "sec": + return Theme.secondary; + case "secondaryContainer": + return Theme.secondaryContainer; + case "tertiary": + case "ter": + return Theme.tertiary; + case "tertiaryContainer": + return Theme.tertiaryContainer; + case "surfaceText": + return Theme.surfaceText; + case "s": + return Theme.surface; + case "sc": + return Theme.surfaceContainer; + case "sch": + return Theme.surfaceContainerHigh; + case "schh": + return Theme.surfaceContainerHighest; + case "sth": + return Theme.surfaceTextHover; + case "error": + case "err": + return Theme.error; + case "custom": + return root.customColor; + case "none": + return "transparent"; + default: + return root.defaultColor; + } + } + + signal modeSelected(string mode) + signal customColorSelected(color selectedColor) + + width: parent?.width ?? 0 + spacing: Theme.spacingS + + function optionLabels() { + return options.map(option => option.label); + } + + function optionLabel(value) { + for (var i = 0; i < options.length; i++) { + if (options[i].value === value) + return options[i].label; + } + return options.length > 0 ? options[0].label : ""; + } + + function optionValue(label) { + for (var i = 0; i < options.length; i++) { + if (options[i].label === label) + return options[i].value; + } + return options.length > 0 ? options[0].value : "default"; + } + + function openCustomColorPicker() { + PopoutService.colorPickerModal.selectedColor = root.customColor; + PopoutService.colorPickerModal.pickerTitle = root.pickerTitle; + PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) { + root.customColorSelected(selectedColor); + root.modeSelected("custom"); + }; + PopoutService.colorPickerModal.show(); + } + + SettingsDropdownRow { + text: root.text + description: root.description + tab: root.tab + settingKey: root.settingKey + tags: root.tags + options: root.optionLabels() + optionColorMap: root.optionColorMap + currentValue: root.optionLabel(root.currentMode) + dropdownWidth: root.dropdownWidth + onValueChanged: value => root.modeSelected(root.optionValue(value)) + } + + Item { + width: parent.width + height: root.currentMode === "custom" ? customChip.height : 0 + opacity: root.currentMode === "custom" ? 1 : 0 + clip: true + + Behavior on height { + NumberAnimation { + duration: Theme.mediumDuration + easing.type: Theme.emphasizedEasing + } + } + + Behavior on opacity { + NumberAnimation { + duration: Theme.mediumDuration + easing.type: Theme.emphasizedEasing + } + } + + Rectangle { + id: customChip + + width: parent.width + height: 56 + radius: Theme.cornerRadius + color: Theme.surfaceContainerHigh + + Row { + anchors.fill: parent + anchors.leftMargin: Theme.spacingM + anchors.rightMargin: Theme.spacingM + spacing: Theme.spacingM + + Rectangle { + width: 36 + height: 36 + radius: 18 + color: root.customColor + border.color: Theme.outline + border.width: 1 + anchors.verticalCenter: parent.verticalCenter + + DankIcon { + anchors.centerIn: parent + name: "colorize" + size: 16 + color: (root.customColor.r * 0.299 + root.customColor.g * 0.587 + root.customColor.b * 0.114) > 0.5 ? "#000000" : "#ffffff" + } + } + + Column { + width: parent.width - 36 - editIcon.width - Theme.spacingM * 2 + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingXS + + StyledText { + text: I18n.tr("Custom Color") + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + width: parent.width + horizontalAlignment: Text.AlignLeft + } + + StyledText { + text: root.customColor.toString() + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + } + } + + DankIcon { + id: editIcon + name: "edit" + size: Theme.iconSizeSmall + color: Theme.surfaceVariantText + anchors.verticalCenter: parent.verticalCenter + } + } + + StateLayer { + stateColor: Theme.surfaceText + onClicked: root.openCustomColorPicker() + } + } + } +} diff --git a/quickshell/Widgets/DankDropdown.qml b/quickshell/Widgets/DankDropdown.qml index acb8db0f..e04051f6 100644 --- a/quickshell/Widgets/DankDropdown.qml +++ b/quickshell/Widgets/DankDropdown.qml @@ -28,6 +28,7 @@ Item { property var optionIcons: [] property bool enableFuzzySearch: false property var optionIconMap: ({}) + property var optionColorMap: ({}) function rebuildIconMap() { const map = {}; @@ -160,7 +161,24 @@ Item { anchors.rightMargin: Theme.spacingS spacing: Theme.spacingS + Rectangle { + id: triggerSwatch + + property var swatchColor: root.optionColorMap[root.currentValue] + + width: 16 + height: 16 + radius: 8 + color: swatchColor !== undefined ? swatchColor : "transparent" + border.color: Theme.outline + border.width: 1 + anchors.verticalCenter: parent.verticalCenter + visible: swatchColor !== undefined + } + DankIcon { + id: triggerIcon + name: root.optionIconMap[root.currentValue] ?? "" size: 18 color: Theme.surfaceText @@ -173,7 +191,7 @@ Item { text: root.currentValue !== "" ? root.currentValue : root.emptyText font.pixelSize: Theme.fontSizeMedium color: root.currentValue !== "" ? Theme.surfaceText : Theme.outline - width: contentRow.width - (contentRow.children[0].visible ? contentRow.children[0].width + contentRow.spacing : 0) + width: contentRow.width - (triggerSwatch.visible ? triggerSwatch.width + contentRow.spacing : 0) - (triggerIcon.visible ? triggerIcon.width + contentRow.spacing : 0) elide: Text.ElideRight wrapMode: Text.NoWrap horizontalAlignment: Text.AlignLeft @@ -406,6 +424,7 @@ Item { property bool isSelected: dropdownMenu.selectedIndex === index property bool isCurrentValue: root.currentValue === modelData property string iconName: root.optionIconMap[modelData] ?? "" + property var swatchColor: root.optionColorMap[modelData] width: ListView.view.width height: 32 @@ -420,6 +439,19 @@ Item { anchors.verticalCenter: parent.verticalCenter spacing: Theme.spacingS + Rectangle { + id: optionSwatch + + width: 16 + height: 16 + radius: 8 + color: delegateRoot.swatchColor !== undefined ? delegateRoot.swatchColor : "transparent" + border.color: delegateRoot.isCurrentValue ? Theme.primary : Theme.outline + border.width: 1 + anchors.verticalCenter: parent.verticalCenter + visible: delegateRoot.swatchColor !== undefined + } + DankIcon { name: delegateRoot.iconName size: 18 @@ -433,7 +465,7 @@ Item { font.pixelSize: Theme.fontSizeMedium color: delegateRoot.isCurrentValue ? Theme.primary : Theme.surfaceText font.weight: delegateRoot.isCurrentValue ? Font.Medium : Font.Normal - width: root.popupWidth > 0 ? undefined : (delegateRoot.width - parent.x - Theme.spacingS * 2) + width: root.popupWidth > 0 ? undefined : (delegateRoot.width - parent.x - Theme.spacingS * 2 - (optionSwatch.visible ? optionSwatch.width + parent.spacing : 0)) elide: root.popupWidth > 0 ? Text.ElideNone : Text.ElideRight wrapMode: Text.NoWrap horizontalAlignment: Text.AlignLeft