1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-04 11:32:12 -04:00

feat(DMS FrameMode): A New Connected Unified Surface & Animation Overhaul

- Introduces Standalone & Connected Modes
- Updated Animations & Motion effects for both modes
- Numerous QOL tweaks and updates throughout the system
- Highly inspired to the OG Caelestia Shell / @Soramanew
This commit is contained in:
purian23
2026-05-03 15:38:30 -04:00
parent dd668469d7
commit fd24b4a36d
82 changed files with 12026 additions and 2905 deletions

View File

@@ -27,6 +27,7 @@ Item {
const pos = selectedBarConfig?.position ?? SettingsData.Position.Top;
return pos === SettingsData.Position.Left || pos === SettingsData.Position.Right;
}
readonly property bool connectedFrameModeActive: SettingsData.connectedFrameModeActive
Timer {
id: horizontalBarChangeDebounce
@@ -695,6 +696,8 @@ Item {
SettingsToggleRow {
visible: CompositorService.isNiri
enabled: !SettingsData.frameEnabled
opacity: SettingsData.frameEnabled ? 0.5 : 1.0
text: I18n.tr("Show on Overview")
checked: selectedBarConfig?.openOnOverview ?? false
onToggled: toggled => {
@@ -705,11 +708,18 @@ Item {
}
}
SettingsControlledByFrame {
visible: SettingsData.frameEnabled
parentModal: dankBarTab.parentModal
settingLabel: I18n.tr("Bar spacing and size")
reason: I18n.tr("Managed by Frame")
}
SettingsCard {
iconName: "space_bar"
title: I18n.tr("Spacing")
settingKey: "barSpacing"
visible: selectedBarConfig?.enabled
visible: (selectedBarConfig?.enabled ?? false) && !SettingsData.frameEnabled
SettingsSliderRow {
id: edgeSpacingSlider
@@ -860,6 +870,7 @@ Item {
SettingsSliderRow {
id: barTransparencySlider
visible: !SettingsData.frameEnabled
text: I18n.tr("Bar Transparency")
value: (selectedBarConfig?.transparency ?? 1.0) * 100
minimum: 0
@@ -901,6 +912,13 @@ Item {
restoreMode: Binding.RestoreBinding
}
}
SettingsControlledByFrame {
visible: SettingsData.frameEnabled
parentModal: dankBarTab.parentModal
settingLabel: I18n.tr("Bar transparency")
reason: I18n.tr("Managed by Frame")
}
}
SettingsSliderCard {
@@ -961,8 +979,16 @@ Item {
expanded: false
visible: selectedBarConfig?.enabled
SettingsControlledByFrame {
visible: SettingsData.frameEnabled
parentModal: dankBarTab.parentModal
settingLabel: I18n.tr("Bar corners and background")
reason: I18n.tr("Managed by Frame")
}
SettingsToggleRow {
text: I18n.tr("Square Corners")
visible: !SettingsData.frameEnabled
checked: selectedBarConfig?.squareCorners ?? false
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
squareCorners: checked
@@ -971,6 +997,7 @@ Item {
SettingsToggleRow {
text: I18n.tr("No Background")
visible: !SettingsData.frameEnabled
checked: selectedBarConfig?.noBackground ?? false
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
noBackground: checked
@@ -1010,6 +1037,7 @@ Item {
SettingsToggleRow {
text: I18n.tr("Goth Corners")
visible: !SettingsData.frameEnabled
checked: selectedBarConfig?.gothCornersEnabled ?? false
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
gothCornersEnabled: checked
@@ -1345,6 +1373,13 @@ Item {
}
}
SettingsControlledByFrame {
visible: dankBarTab.connectedFrameModeActive
parentModal: dankBarTab.parentModal
settingLabel: I18n.tr("Bar shadow, border, and corners")
reason: I18n.tr("Managed by Frame in Connected Mode")
}
SettingsCard {
id: shadowCard
iconName: "layers"
@@ -1352,7 +1387,7 @@ Item {
settingKey: "barShadow"
collapsible: true
expanded: false
visible: selectedBarConfig?.enabled
visible: (selectedBarConfig?.enabled ?? false) && !dankBarTab.connectedFrameModeActive
readonly property bool shadowActive: (selectedBarConfig?.shadowIntensity ?? 0) > 0
readonly property bool isCustomColor: (selectedBarConfig?.shadowColorMode ?? "default") === "custom"

View File

@@ -8,6 +8,9 @@ import qs.Modules.Settings.Widgets
Item {
id: root
property var parentModal: null
readonly property bool connectedFrameModeActive: SettingsData.connectedFrameModeActive
FileBrowserModal {
id: dockLogoFileBrowser
browserTitle: I18n.tr("Select Dock Launcher Logo")
@@ -604,6 +607,7 @@ Item {
SettingsSliderRow {
text: I18n.tr("Exclusive Zone Offset")
visible: !root.connectedFrameModeActive
value: SettingsData.dockBottomGap
minimum: -100
maximum: 100
@@ -613,6 +617,7 @@ Item {
SettingsSliderRow {
text: I18n.tr("Margin")
visible: !root.connectedFrameModeActive
value: SettingsData.dockMargin
minimum: 0
maximum: 100
@@ -621,11 +626,19 @@ Item {
}
}
SettingsControlledByFrame {
visible: root.connectedFrameModeActive
parentModal: root.parentModal
settingLabel: I18n.tr("Dock spacing, transparency, and border")
reason: I18n.tr("Managed by Frame in Connected Mode")
}
SettingsCard {
width: parent.width
iconName: "opacity"
title: I18n.tr("Transparency")
settingKey: "dockTransparency"
visible: !root.connectedFrameModeActive
SettingsSliderRow {
text: I18n.tr("Dock Transparency")
@@ -645,6 +658,7 @@ Item {
settingKey: "dockBorder"
collapsible: true
expanded: false
visible: !root.connectedFrameModeActive
SettingsToggleRow {
text: I18n.tr("Border")

View File

@@ -0,0 +1,370 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
DankFlickable {
anchors.fill: parent
clip: true
contentHeight: mainColumn.height + Theme.spacingXL
contentWidth: width
Column {
id: mainColumn
topPadding: 4
width: Math.min(550, parent.width - Theme.spacingL * 2)
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXL
SettingsCard {
width: parent.width
iconName: "frame_source"
title: I18n.tr("Frame")
settingKey: "frameEnabled"
SettingsToggleRow {
settingKey: "frameEnable"
tags: ["frame", "border", "outline", "display"]
text: I18n.tr("Enable Frame")
description: I18n.tr("Draw a connected picture-frame border around the entire display")
checked: SettingsData.frameEnabled
onToggled: checked => SettingsData.set("frameEnabled", checked)
}
}
SettingsCard {
width: parent.width
iconName: "tune"
title: I18n.tr("Mode")
settingKey: "frameMode"
visible: SettingsData.frameEnabled
SettingsButtonGroupRow {
settingKey: "frameModeSelector"
tags: ["frame", "mode", "connected", "separate", "popout"]
text: I18n.tr("Surface Behavior")
description: SettingsData.frameMode === "connected" ? I18n.tr("Surfaces emerge flush from the bar") : I18n.tr("Surfaces float independently of the frame")
model: [I18n.tr("Separate"), I18n.tr("Connected")]
currentIndex: SettingsData.frameMode === "connected" ? 1 : 0
onSelectionChanged: (index, selected) => {
if (!selected)
return;
switch (index) {
case 1:
SettingsData.set("frameMode", "connected");
break;
default:
SettingsData.set("frameMode", "separate");
break;
}
}
}
}
SettingsCard {
width: parent.width
iconName: "border_outer"
title: I18n.tr("Border")
settingKey: "frameBorder"
collapsible: true
visible: SettingsData.frameEnabled
SettingsSliderRow {
id: roundingSlider
settingKey: "frameRounding"
tags: ["frame", "border", "rounding", "radius", "corner"]
text: I18n.tr("Border Radius")
unit: "px"
minimum: 0
maximum: 100
step: 1
defaultValue: 23
value: SettingsData.frameRounding
onSliderDragFinished: v => SettingsData.set("frameRounding", v)
Binding {
target: roundingSlider
property: "value"
value: SettingsData.frameRounding
}
}
SettingsSliderRow {
id: thicknessSlider
settingKey: "frameThickness"
tags: ["frame", "border", "thickness", "size", "width"]
text: I18n.tr("Border Width")
unit: "px"
minimum: 2
maximum: 100
step: 1
defaultValue: 16
value: SettingsData.frameThickness
onSliderDragFinished: v => SettingsData.set("frameThickness", v)
Binding {
target: thicknessSlider
property: "value"
value: SettingsData.frameThickness
}
}
SettingsSliderRow {
id: barThicknessSlider
settingKey: "frameBarSize"
tags: ["frame", "bar", "thickness", "size", "height", "width"]
text: I18n.tr("Size")
description: I18n.tr("Horizontal and vertical bar thickness")
unit: "px"
minimum: 24
maximum: 100
step: 1
defaultValue: 40
value: SettingsData.frameBarSize
onSliderDragFinished: v => SettingsData.set("frameBarSize", v)
Binding {
target: barThicknessSlider
property: "value"
value: SettingsData.frameBarSize
}
}
SettingsSliderRow {
id: opacitySlider
settingKey: "frameOpacity"
tags: ["frame", "border", "surface", "popup", "opacity", "transparency"]
text: I18n.tr("Surface Opacity")
unit: "%"
minimum: 0
maximum: 100
defaultValue: 100
value: SettingsData.frameOpacity * 100
onSliderDragFinished: v => SettingsData.set("frameOpacity", v / 100)
Binding {
target: opacitySlider
property: "value"
value: SettingsData.frameOpacity * 100
}
}
SettingsToggleRow {
id: frameBlurToggle
settingKey: "frameBlurEnabled"
tags: ["frame", "blur", "background", "glass", "transparency", "frosted"]
text: I18n.tr("Frame Blur")
description: !BlurService.available ? I18n.tr("Requires a newer version of Quickshell") : I18n.tr("Apply compositor blur behind the frame border")
checked: SettingsData.frameBlurEnabled
onToggled: checked => SettingsData.set("frameBlurEnabled", checked)
enabled: BlurService.available && SettingsData.blurEnabled
opacity: enabled ? 1.0 : 0.5
visible: BlurService.available
}
Item {
visible: BlurService.available && !SettingsData.blurEnabled
width: parent.width
height: blurToggleNote.height + Theme.spacingM * 2
Row {
id: blurToggleNote
x: Theme.spacingM
width: parent.width - Theme.spacingM * 2
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "blur_on"
size: Theme.fontSizeMedium
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Frame Blur follows Background Blur in Theme & Colors")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width - Theme.fontSizeMedium - Theme.spacingS
}
}
}
SettingsButtonGroupRow {
settingKey: "frameColor"
tags: ["frame", "border", "color", "theme", "primary", "surface", "default"]
text: I18n.tr("Border Color")
model: [I18n.tr("Default"), I18n.tr("Primary"), I18n.tr("Surface"), I18n.tr("Custom")]
currentIndex: {
const fc = SettingsData.frameColor;
if (!fc || fc === "default")
return 0;
switch (fc) {
case "primary":
return 1;
case "surface":
return 2;
default:
return 3;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
switch (index) {
case 0:
SettingsData.set("frameColor", "");
break;
case 1:
SettingsData.set("frameColor", "primary");
break;
case 2:
SettingsData.set("frameColor", "surface");
break;
case 3:
const cur = SettingsData.frameColor;
const isPreset = !cur || cur === "primary" || cur === "surface";
if (isPreset)
SettingsData.set("frameColor", "#2a2a2a");
break;
}
}
}
Item {
visible: {
const fc = SettingsData.frameColor;
return !!(fc && fc !== "primary" && fc !== "surface");
}
width: parent.width
height: customColorRow.height + Theme.spacingM * 2
Row {
id: customColorRow
width: parent.width - Theme.spacingM * 2
x: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Custom Color")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 32
height: 32
radius: 16
color: SettingsData.effectiveFrameColor
border.color: Theme.outline
border.width: 1
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
PopoutService.colorPickerModal.selectedColor = SettingsData.effectiveFrameColor;
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Frame Border Color");
PopoutService.colorPickerModal.onColorSelectedCallback = function (color) {
SettingsData.set("frameColor", color.toString());
};
PopoutService.colorPickerModal.show();
}
}
}
}
}
}
SettingsCard {
width: parent.width
iconName: "blur_linear"
title: I18n.tr("Connected Options")
settingKey: "frameConnectedOptions"
collapsible: true
expanded: true
visible: SettingsData.frameEnabled && SettingsData.frameMode === "connected"
SettingsToggleRow {
settingKey: "frameCloseGaps"
tags: ["frame", "connected", "gap", "edge", "curves", "arcs", "expose", "popout", "notification"]
text: I18n.tr("Expose the Arcs")
description: I18n.tr("Reveal the arcs where surfaces meet the frame")
checked: !SettingsData.frameCloseGaps
onToggled: checked => SettingsData.set("frameCloseGaps", !checked)
}
SettingsButtonGroupRow {
settingKey: "frameLauncherEmergeSide"
tags: ["frame", "connected", "launcher", "modal", "emerge", "direction", "bottom", "top"]
text: I18n.tr("Launcher Emerge Side")
description: I18n.tr("Edge the launcher slides from")
model: [I18n.tr("Bottom"), I18n.tr("Top")]
currentIndex: SettingsData.frameLauncherEmergeSide === "top" ? 1 : 0
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.set("frameLauncherEmergeSide", index === 1 ? "top" : "bottom");
}
}
SettingsToggleRow {
settingKey: "frameLauncherArcExtender"
tags: ["frame", "connected", "launcher", "arc", "extender", "center"]
text: I18n.tr("Arc Extender")
description: I18n.tr("Use the extended surface for launcher content")
checked: SettingsData.frameLauncherArcExtender
onToggled: checked => SettingsData.set("frameLauncherArcExtender", checked)
}
}
SettingsCard {
width: parent.width
iconName: "toolbar"
title: I18n.tr("Integrations")
settingKey: "frameBarIntegration"
collapsible: true
expanded: true
visible: SettingsData.frameEnabled && CompositorService.isNiri
SettingsToggleRow {
settingKey: "frameShowOnOverview"
tags: ["frame", "overview", "show", "hide", "niri"]
text: I18n.tr("Show on Overview")
description: I18n.tr("Show during Niri overview")
checked: SettingsData.frameShowOnOverview
onToggled: checked => SettingsData.set("frameShowOnOverview", checked)
}
}
SettingsCard {
width: parent.width
iconName: "monitor"
title: I18n.tr("Display Assignment")
settingKey: "frameDisplays"
collapsible: true
expanded: false
visible: SettingsData.frameEnabled
SettingsDisplayPicker {
displayPreferences: SettingsData.frameScreenPreferences
onPreferencesChanged: prefs => SettingsData.set("frameScreenPreferences", prefs)
}
}
}
}
}

View File

@@ -344,11 +344,7 @@ Item {
return I18n.tr("%1 exists but is not included in config. Custom keybinds will not work until this is fixed.").arg(bindsFile);
if (warningBox.showWarning) {
const count = warningBox.status.overriddenBy;
return I18n.ntr(
"%1 DMS bind may be overridden by config binds that come after the include.",
"%1 DMS binds may be overridden by config binds that come after the include.",
count
).arg(count);
return I18n.ntr("%1 DMS bind may be overridden by config binds that come after the include.", "%1 DMS binds may be overridden by config binds that come after the include.", count).arg(count);
}
return "";
}
@@ -543,13 +539,11 @@ Item {
StyledText {
text: {
if (KeybindsService.loading)
return I18n.tr("Shortcuts");
const count = keybindsTab._filteredBinds.length;
return count === 1
? I18n.tr("Shortcut (%1)").arg(count)
: I18n.tr("Shortcuts (%1)").arg(count);
}
if (KeybindsService.loading)
return I18n.tr("Shortcuts");
const count = keybindsTab._filteredBinds.length;
return count === 1 ? I18n.tr("Shortcut (%1)").arg(count) : I18n.tr("Shortcuts (%1)").arg(count);
}
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
@@ -569,7 +563,7 @@ Item {
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
RotationAnimation on rotation {
RotationAnimator on rotation {
from: 0
to: 360
duration: 1000

View File

@@ -36,36 +36,36 @@ Item {
function normalizePinList(value) {
if (Array.isArray(value))
return value.filter(v => v)
return value.filter(v => v);
if (typeof value === "string" && value.length > 0)
return [value]
return []
return [value];
return [];
}
function getPinnedWifiNetworks() {
const pins = SettingsData.wifiNetworkPins || {}
return normalizePinList(pins["preferredWifi"])
const pins = SettingsData.wifiNetworkPins || {};
return normalizePinList(pins["preferredWifi"]);
}
function toggleWifiPin(ssid) {
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}))
let pinnedList = normalizePinList(pins["preferredWifi"])
const pinIndex = pinnedList.indexOf(ssid)
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}));
let pinnedList = normalizePinList(pins["preferredWifi"]);
const pinIndex = pinnedList.indexOf(ssid);
if (pinIndex !== -1) {
pinnedList.splice(pinIndex, 1)
pinnedList.splice(pinIndex, 1);
} else {
pinnedList.unshift(ssid)
pinnedList.unshift(ssid);
if (pinnedList.length > maxPinnedWifiNetworks)
pinnedList = pinnedList.slice(0, maxPinnedWifiNetworks)
pinnedList = pinnedList.slice(0, maxPinnedWifiNetworks);
}
if (pinnedList.length > 0)
pins["preferredWifi"] = pinnedList
pins["preferredWifi"] = pinnedList;
else
delete pins["preferredWifi"]
delete pins["preferredWifi"];
SettingsData.set("wifiNetworkPins", pins)
SettingsData.set("wifiNetworkPins", pins);
}
LazyLoader {
@@ -340,9 +340,7 @@ Item {
if (devices.length === 0)
return I18n.tr("No adapters");
if (connected === 0)
return devices.length === 1
? I18n.tr("%1 adapter, none connected").arg(devices.length)
: I18n.tr("%1 adapters, none connected").arg(devices.length);
return devices.length === 1 ? I18n.tr("%1 adapter, none connected").arg(devices.length) : I18n.tr("%1 adapters, none connected").arg(devices.length);
return I18n.tr("%1 connected").arg(connected);
}
font.pixelSize: Theme.fontSizeSmall
@@ -658,16 +656,14 @@ Item {
SequentialAnimation {
running: NetworkService.networkWiredInfoLoading
loops: Animation.Infinite
NumberAnimation {
OpacityAnimator {
target: wiredLoadIcon
property: "opacity"
to: 0.3
duration: 400
easing.type: Easing.InOutQuad
}
NumberAnimation {
OpacityAnimator {
target: wiredLoadIcon
property: "opacity"
to: 1.0
duration: 400
easing.type: Easing.InOutQuad
@@ -1046,16 +1042,14 @@ Item {
SequentialAnimation {
running: NetworkService.isScanning
loops: Animation.Infinite
NumberAnimation {
OpacityAnimator {
target: scanningIcon
property: "opacity"
to: 0.3
duration: 400
easing.type: Easing.InOutQuad
}
NumberAnimation {
OpacityAnimator {
target: scanningIcon
property: "opacity"
to: 1.0
duration: 400
easing.type: Easing.InOutQuad
@@ -1087,14 +1081,14 @@ Item {
let sorted = [...networks];
sorted.sort((a, b) => {
const aPinnedIndex = pinnedList.indexOf(a.ssid)
const bPinnedIndex = pinnedList.indexOf(b.ssid)
const aPinnedIndex = pinnedList.indexOf(a.ssid);
const bPinnedIndex = pinnedList.indexOf(b.ssid);
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
if (aPinnedIndex === -1)
return 1
return 1;
if (bPinnedIndex === -1)
return -1
return aPinnedIndex - bPinnedIndex
return -1;
return aPinnedIndex - bPinnedIndex;
}
if (a.ssid === ssid)
return -1;
@@ -1297,7 +1291,7 @@ Item {
buttonSize: 28
iconColor: isPinned ? Theme.primary : Theme.surfaceVariantText
onClicked: {
networkTab.toggleWifiPin(modelData.ssid)
networkTab.toggleWifiPin(modelData.ssid);
}
}
@@ -1375,16 +1369,14 @@ Item {
SequentialAnimation {
running: NetworkService.networkInfoLoading
loops: Animation.Infinite
NumberAnimation {
OpacityAnimator {
target: wifiInfoLoadIcon
property: "opacity"
to: 0.3
duration: 400
easing.type: Easing.InOutQuad
}
NumberAnimation {
OpacityAnimator {
target: wifiInfoLoadIcon
property: "opacity"
to: 1.0
duration: 400
easing.type: Easing.InOutQuad
@@ -1866,16 +1858,14 @@ Item {
SequentialAnimation {
running: VPNService.configLoading
loops: Animation.Infinite
NumberAnimation {
OpacityAnimator {
target: vpnLoadIcon
property: "opacity"
to: 0.3
duration: 400
easing.type: Easing.InOutQuad
}
NumberAnimation {
OpacityAnimator {
target: vpnLoadIcon
property: "opacity"
to: 1.0
duration: 400
easing.type: Easing.InOutQuad
@@ -1984,7 +1974,9 @@ Item {
checked: configData ? (configData.autoconnect || false) : false
visible: !VPNService.configLoading && configData !== null
onToggled: checked => {
VPNService.updateConfig(modelData.uuid, {autoconnect: checked});
VPNService.updateConfig(modelData.uuid, {
autoconnect: checked
});
}
}

View File

@@ -458,7 +458,7 @@ Item {
enabled: !CupsService.loadingDevices
onClicked: CupsService.getDevices()
RotationAnimation on rotation {
RotationAnimator on rotation {
running: CupsService.loadingDevices
loops: Animation.Infinite
from: 0
@@ -736,7 +736,7 @@ Item {
enabled: !CupsService.loadingPPDs
onClicked: CupsService.getPPDs()
RotationAnimation on rotation {
RotationAnimator on rotation {
running: CupsService.loadingPPDs
loops: Animation.Infinite
from: 0

View File

@@ -11,6 +11,8 @@ import qs.Modules.Settings.Widgets
Item {
id: themeColorsTab
property var parentModal: null
readonly property bool connectedFrameModeActive: SettingsData.connectedFrameModeActive
property var cachedIconThemes: SettingsData.availableIconThemes
property var cachedCursorThemes: SettingsData.availableCursorThemes
property var cachedMatugenSchemes: Theme.availableMatugenSchemes.map(option => option.label)
@@ -1613,12 +1615,20 @@ Item {
}
}
SettingsControlledByFrame {
visible: themeColorsTab.connectedFrameModeActive
parentModal: themeColorsTab.parentModal
settingLabel: I18n.tr("Surface Opacity")
reason: I18n.tr("Managed by Frame in Connected Mode")
}
SettingsSliderRow {
tab: "theme"
tags: ["popup", "transparency", "opacity", "modal"]
tags: ["surface", "popup", "transparency", "opacity", "modal"]
settingKey: "popupTransparency"
text: I18n.tr("Popup Transparency")
text: I18n.tr("Surface Opacity")
description: I18n.tr("Controls opacity of all popouts, modals, and their content layers")
visible: !themeColorsTab.connectedFrameModeActive
value: Math.round(SettingsData.popupTransparency * 100)
minimum: 0
maximum: 100
@@ -1837,7 +1847,7 @@ Item {
tags: ["blur", "background", "transparency", "glass", "frosted"]
settingKey: "blurEnabled"
text: I18n.tr("Background Blur")
description: BlurService.available ? I18n.tr("Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration.") : I18n.tr("Requires a newer version of Quickshell")
description: !BlurService.available ? I18n.tr("Requires a newer version of Quickshell") : I18n.tr("Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration.")
checked: SettingsData.blurEnabled ?? false
enabled: BlurService.available
onToggled: checked => SettingsData.set("blurEnabled", checked)
@@ -2240,12 +2250,20 @@ Item {
settingKey: "modalBackground"
iconName: "layers"
SettingsControlledByFrame {
visible: themeColorsTab.connectedFrameModeActive
parentModal: themeColorsTab.parentModal
settingLabel: I18n.tr("Darken Modal Background")
reason: I18n.tr("Managed by Frame in Connected Mode")
}
SettingsToggleRow {
tab: "theme"
tags: ["modal", "darken", "background", "overlay"]
settingKey: "modalDarkenBackground"
text: I18n.tr("Darken Modal Background")
description: I18n.tr("Show darkened overlay behind modal dialogs")
visible: !themeColorsTab.connectedFrameModeActive
checked: SettingsData.modalDarkenBackground
onToggled: checked => SettingsData.set("modalDarkenBackground", checked)
}

View File

@@ -693,7 +693,7 @@ Item {
onTriggered: refreshButton.isRefreshing = false
}
NumberAnimation on rotation {
RotationAnimator on rotation {
running: refreshButton.isRefreshing
from: 0
to: 360

View File

@@ -55,6 +55,144 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXL
SettingsCard {
tab: "typography"
tags: ["animation", "variant", "style", "slide", "fluent", "dynamic", "motion"]
title: I18n.tr("Animation Style")
settingKey: "animationVariant"
iconName: "auto_awesome_motion"
Item {
width: parent.width
height: animVariantGroup.implicitHeight
clip: true
DankButtonGroup {
id: animVariantGroup
anchors.horizontalCenter: parent.horizontalCenter
buttonPadding: parent.width < 480 ? Theme.spacingS : Theme.spacingL
minButtonWidth: parent.width < 480 ? 64 : 96
textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
model: [I18n.tr("Material"), I18n.tr("Fluent"), I18n.tr("Dynamic")]
selectionMode: "single"
currentIndex: SettingsData.animationVariant
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.set("animationVariant", index);
}
Connections {
target: SettingsData
function onAnimationVariantChanged() {
animVariantGroup.currentIndex = SettingsData.animationVariant;
}
}
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
Item {
width: parent.width
height: variantDescription.implicitHeight + Theme.spacingS * 2
StyledText {
id: variantDescription
x: Theme.spacingM
y: Theme.spacingS
width: parent.width - Theme.spacingM * 2
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
text: {
switch (SettingsData.animationVariant) {
case 1:
return I18n.tr("Fluent: Smooth cubic deceleration in, quick snap out — clean, elegant curves.");
case 2:
return I18n.tr("Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.");
default:
return I18n.tr("Material: Material Design 3 Expressive bezier curves. The DMS default feel.");
}
}
}
}
}
SettingsCard {
tab: "typography"
tags: ["animation", "motion", "effect", "slide", "directional", "depth", "spring", "physics"]
title: I18n.tr("Motion Effects")
settingKey: "motionEffect"
iconName: "motion_photos_on"
Item {
width: parent.width
height: motionEffectGroup.implicitHeight
clip: true
DankButtonGroup {
id: motionEffectGroup
anchors.horizontalCenter: parent.horizontalCenter
buttonPadding: parent.width < 480 ? Theme.spacingS : Theme.spacingL
minButtonWidth: parent.width < 480 ? 64 : 96
textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
model: [I18n.tr("Standard"), I18n.tr("Directional"), I18n.tr("Depth")]
selectionMode: "single"
currentIndex: SettingsData.motionEffect
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.set("motionEffect", index);
}
Connections {
target: SettingsData
function onMotionEffectChanged() {
motionEffectGroup.currentIndex = SettingsData.motionEffect;
}
}
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
Item {
width: parent.width
height: motionEffectDescription.implicitHeight + Theme.spacingS * 2
StyledText {
id: motionEffectDescription
x: Theme.spacingM
y: Theme.spacingS
width: parent.width - Theme.spacingM * 2
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
text: {
switch (SettingsData.motionEffect) {
case 1:
return I18n.tr("Directional: Panels glide in from a larger distance at full size — no scale change, pure clean motion.");
case 2:
return I18n.tr("Depth: Panels scale up from small as they slide in — a dramatic pop-forward depth effect.");
default:
return I18n.tr("Standard: Classic Material Design 3 — panels rise from below with a subtle scale. The DMS default.");
}
}
}
}
}
SettingsCard {
tab: "typography"
tags: ["font", "family", "text", "typography"]
@@ -285,12 +423,6 @@ Item {
description: I18n.tr("Popouts and Modals follow global Animation Speed (disable to customize independently)")
checked: SettingsData.syncComponentAnimationSpeeds
onToggled: checked => SettingsData.set("syncComponentAnimationSpeeds", checked)
Connections {
target: SettingsData
function onSyncComponentAnimationSpeedsChanged() {
}
}
}
}

View File

@@ -0,0 +1,79 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Widgets
StyledRect {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string settingLabel: ""
property string reason: ""
property var parentModal: null
width: parent?.width ?? 0
height: contentRow.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.primary, 0.08)
border.color: Theme.withAlpha(Theme.primary, 0.18)
border.width: 1
Row {
id: contentRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingM
DankIcon {
name: "frame_source"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - Theme.iconSize - openButton.width - Theme.spacingM * 2
spacing: 2
StyledText {
text: root.settingLabel
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
wrapMode: Text.WordWrap
}
StyledText {
text: root.reason
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
visible: root.reason !== ""
}
}
DankButton {
id: openButton
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Open Frame")
backgroundColor: Theme.primary
textColor: Theme.primaryText
buttonHeight: 32
horizontalPadding: Theme.spacingM
onClicked: {
if (!root.parentModal)
return;
root.parentModal.showWithTabName("frame");
}
}
}
}

View File

@@ -83,7 +83,6 @@ Item {
description: modelData.width + "×" + modelData.height
checked: localChecked
onToggled: isChecked => {
localChecked = isChecked;
var prefs = JSON.parse(JSON.stringify(root.displayPreferences));
if (!Array.isArray(prefs) || prefs.includes("all"))
prefs = [];
@@ -94,6 +93,11 @@ Item {
model: modelData.model || ""
});
}
if (prefs.length === 0) {
localChecked = true;
return;
}
localChecked = isChecked;
root.preferencesChanged(prefs);
}
}