mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-28 14:07:47 -04:00
feat(theme): introduce dms classic interface style
- Expressive remains in place where Classic drops pills, shape morph, and setting sidebar color badges
- Classic defaults corner style to Fixed and stacks settings groups into continuous cards
- section dividers setting provides a slider to enhance the lines or remove them entirely for a clean look
Port 2.0
(cherry picked from commit 7479d4bb79)
This commit is contained in:
1 parent
d29daf8fdd
commit
0d7298b996
40 files changed
+532
-181
No files matched your search
+1
-1
Submodule dux-qml-common updated: 3d56a375f2...b602c9e1b7.
Generated
+3
-3
@@ -3,11 +3,11 @@
|
||||
"dank-qml-common": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1793150560,
|
||||
"narHash": "sha256-Vx8riKxgP2X/KZ/nzDn3WxC/wrYqAwa4yh9J6FurUEw=",
|
||||
"lastModified": 1793183831,
|
||||
"narHash": "sha256-23DNWShmRTKreSalQQZ7TE3fL3u+q7DprECU5EVEC5k=",
|
||||
"owner": "duxlabs-dev",
|
||||
"repo": "dux-qml-common",
|
||||
"rev": "3d56a375f29b691074cb61411356966da8ad060e",
|
||||
"rev": "b602c9e1b7521bab36e3b5fb0f9485dd5ef6f531",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -308,6 +308,7 @@ Singleton {
|
||||
property int motionEffect: SettingsData.AnimationEffect.Standard
|
||||
onMotionEffectChanged: saveSettings()
|
||||
property string density: Spec.SPEC.density.def
|
||||
property string interfaceStyle: Spec.SPEC.interfaceStyle.def
|
||||
property bool m3ElevationEnabled: Spec.SPEC.m3ElevationEnabled.def
|
||||
onM3ElevationEnabledChanged: saveSettings()
|
||||
property int m3ElevationIntensity: Spec.SPEC.m3ElevationIntensity.def
|
||||
@@ -337,6 +338,8 @@ Singleton {
|
||||
property real foregroundLayerTransparency: Spec.SPEC.foregroundLayerTransparency.def
|
||||
property real blurLayerOutlineOpacity: Spec.SPEC.blurLayerOutlineOpacity.def
|
||||
onBlurLayerOutlineOpacityChanged: saveSettings()
|
||||
property real sectionDividerOpacity: Spec.SPEC.sectionDividerOpacity.def
|
||||
onSectionDividerOpacityChanged: saveSettings()
|
||||
property string tooltipSurfaceColor: Spec.SPEC.tooltipSurfaceColor.def
|
||||
onTooltipSurfaceColorChanged: saveSettings()
|
||||
property string tooltipSurfaceCustomColor: Spec.SPEC.tooltipSurfaceCustomColor.def
|
||||
@@ -1747,8 +1750,17 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function applyInterfaceStyle(who, key, previous) {
|
||||
const from = Store.styleDefault("radiusMode", previous) ?? Spec.SPEC.radiusMode.def;
|
||||
if (radiusMode !== from || isManaged("radiusMode"))
|
||||
return;
|
||||
radiusMode = presetDefault("radiusMode") ?? Spec.SPEC.radiusMode.def;
|
||||
updateCompositorLayout();
|
||||
}
|
||||
|
||||
readonly property var _hooks: ({
|
||||
"applyDensityPresets": applyDensityPresets,
|
||||
"applyInterfaceStyle": applyInterfaceStyle,
|
||||
"applyStoredTheme": applyStoredTheme,
|
||||
"regenSystemThemes": regenSystemThemes,
|
||||
"updateCompositorLayout": updateCompositorLayout,
|
||||
@@ -1772,6 +1784,10 @@ Singleton {
|
||||
log.warn("Refusing an unknown density:", value);
|
||||
return false;
|
||||
}
|
||||
if (key === "interfaceStyle" && !["expressive", "classic"].includes(value)) {
|
||||
log.warn("Refusing an unknown interface style:", value);
|
||||
return false;
|
||||
}
|
||||
if (key === "cornerRadius")
|
||||
return setCornerRadius(value);
|
||||
if (key === "radiusStrength")
|
||||
@@ -1790,18 +1806,22 @@ Singleton {
|
||||
return key in AdminPolicy.lockSettings;
|
||||
}
|
||||
|
||||
function presetDefault(key) {
|
||||
return DensityPresets.value("frame", key, density) ?? Store.styleDefault(key, interfaceStyle);
|
||||
}
|
||||
|
||||
function specDefault(key) {
|
||||
return DensityPresets.value("frame", key, density) ?? SpecUtil.cloneDef(Spec.SPEC[key]?.def);
|
||||
return presetDefault(key) ?? SpecUtil.cloneDef(Spec.SPEC[key]?.def);
|
||||
}
|
||||
|
||||
function isDefault(keys) {
|
||||
return keys.every(key => !(key in Spec.SPEC) || SpecUtil.isDefault(root[key], DensityPresets.value("frame", key, density) ?? Spec.SPEC[key].def));
|
||||
return keys.every(key => !(key in Spec.SPEC) || SpecUtil.isDefault(root[key], presetDefault(key) ?? Spec.SPEC[key].def));
|
||||
}
|
||||
|
||||
function resetToDefault(keys) {
|
||||
for (const key of keys) {
|
||||
if (key in Spec.SPEC && !isManaged(key))
|
||||
Spec.set(root, key, DensityPresets.value("frame", key, density) ?? null, saveSettings, _hooks);
|
||||
Spec.set(root, key, presetDefault(key) ?? null, saveSettings, _hooks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1127,7 +1127,7 @@ Singleton {
|
||||
|
||||
readonly property real notificationExpandedIconSizeNormal: avatarSize
|
||||
readonly property real notificationExpandedIconSizeCompact: avatarSize
|
||||
readonly property real notificationButtonCornerRadius: cornerRadiusFull
|
||||
readonly property real notificationButtonCornerRadius: pillControls ? cornerRadiusFull : cornerRadiusL
|
||||
readonly property real notificationHoverRevealMargin: spacingXL
|
||||
readonly property real notificationContentSpacing: spacingXS
|
||||
readonly property real notificationCardPadding: spacingM
|
||||
@@ -1184,12 +1184,20 @@ Singleton {
|
||||
return Shape.scaledRadius(radius, limit, shapeScale, fixedRadius);
|
||||
}
|
||||
|
||||
function innerRadius(token) {
|
||||
return Shape.innerRadius(token, shapeScale, fixedRadius);
|
||||
}
|
||||
|
||||
function fullRadius(width, height) {
|
||||
return Shape.fullRadius(width, height, shapeScale, fixedRadius);
|
||||
}
|
||||
|
||||
readonly property bool classicStyle: typeof SettingsData !== "undefined" && SettingsData.interfaceStyle === "classic"
|
||||
readonly property bool shapeMorph: !classicStyle
|
||||
readonly property bool pillControls: !classicStyle
|
||||
|
||||
function buttonRadius(width, height, sizeHeight, pressed, round) {
|
||||
return Shape.buttonRadius(width, height, sizeHeight, pressed, round, shapeScale, fixedRadius);
|
||||
return Shape.buttonRadius(width, height, sizeHeight, pressed, round, shapeScale, fixedRadius, shapeMorph, pillControls);
|
||||
}
|
||||
|
||||
readonly property int densityLevel: {
|
||||
@@ -1209,7 +1217,7 @@ Singleton {
|
||||
readonly property real densityStepM: densityLevel * spacingS
|
||||
readonly property real densityStepXS: densityLevel * spacingXXS
|
||||
readonly property real groupedListGap: spacingXXS
|
||||
readonly property real groupedListInnerRadius: cornerRadiusXS
|
||||
readonly property real groupedListInnerRadius: innerRadius("xs")
|
||||
readonly property real groupedListOuterRadius: cornerRadiusL
|
||||
|
||||
property string fontFamily: typeof SettingsData !== "undefined" ? resolvedFontFamily(SettingsData.fontFamily) : DCommon.Fonts.sans
|
||||
@@ -1287,11 +1295,11 @@ Singleton {
|
||||
readonly property real avatarSize: 36
|
||||
readonly property real sliderTrackHeight: 16
|
||||
readonly property real sliderHandleWidth: 4
|
||||
readonly property real sliderHandleWidthPressed: 2
|
||||
readonly property real sliderHandleWidthPressed: shapeMorph ? 2 : sliderHandleWidth
|
||||
readonly property real sliderHandleHeight: 28
|
||||
readonly property real sliderHandleGap: 6
|
||||
readonly property real sliderTrackHeightS: 24
|
||||
readonly property real sliderHandleHeightS: 36
|
||||
readonly property real sliderTrackHeightS: classicStyle ? sliderTrackHeight : 24
|
||||
readonly property real sliderHandleHeightS: classicStyle ? sliderHandleHeight : 36
|
||||
readonly property real sliderTrackHeightM: 40
|
||||
readonly property real sliderHandleHeightM: 52
|
||||
readonly property real sliderTrackHeightL: 56
|
||||
|
||||
@@ -474,6 +474,10 @@ var LOCAL_SPEC = {
|
||||
def: "comfortable",
|
||||
onChange: "applyDensityPresets"
|
||||
},
|
||||
interfaceStyle: {
|
||||
def: "expressive",
|
||||
onChange: "applyInterfaceStyle"
|
||||
},
|
||||
m3ElevationEnabled: {
|
||||
def: true
|
||||
},
|
||||
@@ -519,6 +523,10 @@ var LOCAL_SPEC = {
|
||||
def: 0,
|
||||
coerce: Util.percentToUnit
|
||||
},
|
||||
sectionDividerOpacity: {
|
||||
def: 0.1,
|
||||
coerce: Util.percentToUnit
|
||||
},
|
||||
focusRingEnabled: {
|
||||
def: true
|
||||
},
|
||||
|
||||
@@ -173,9 +173,15 @@ function extractPins(obj) {
|
||||
return pins;
|
||||
}
|
||||
|
||||
function styleDefault(key, style) {
|
||||
if (key === "radiusMode" && style === "classic")
|
||||
return "fixed";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Sizes absent from the file load at the saved density's preset, as a reset would; a save omits only that preset.
|
||||
function densityDefault(key, density) {
|
||||
var def = SpecModule.SPEC[key].def;
|
||||
function densityDefault(key, density, style) {
|
||||
var def = styleDefault(key, style) ?? SpecModule.SPEC[key].def;
|
||||
if (key === "barConfigs" || key === "dockConfigs") {
|
||||
var presets = DensityPresets.values(key === "barConfigs" ? "bar" : "dock", density);
|
||||
return def.map(function (config) {
|
||||
@@ -204,7 +210,7 @@ function parse(root, jsonObj) {
|
||||
if (SPEC[k].persist === false)
|
||||
continue;
|
||||
if (!(k in jsonObj))
|
||||
assign(root, k, Util.cloneDef(densityDefault(k, jsonObj.density)));
|
||||
assign(root, k, Util.cloneDef(densityDefault(k, jsonObj.density, jsonObj.interfaceStyle)));
|
||||
}
|
||||
|
||||
for (var k in jsonObj) {
|
||||
@@ -243,7 +249,7 @@ function toJson(root, setKeys) {
|
||||
value = withoutInstancePositions(value);
|
||||
if (k === "builtInPluginSettings")
|
||||
value = withoutSessionBackedPluginState(value);
|
||||
if (!setKeys.has(k) && Util.isDefault(value, densityDefault(k, root.density)))
|
||||
if (!setKeys.has(k) && Util.isDefault(value, densityDefault(k, root.density, root.interfaceStyle)))
|
||||
continue;
|
||||
out[k] = value;
|
||||
}
|
||||
|
||||
@@ -360,49 +360,64 @@ Item {
|
||||
bottomPadding: SettingsMetrics.paneMargin
|
||||
spacing: SettingsMetrics.sidebarGroupGap
|
||||
|
||||
Column {
|
||||
id: searchResultsColumn
|
||||
Item {
|
||||
id: searchResultsFrame
|
||||
width: parent.width - parent.leftPadding - parent.rightPadding
|
||||
spacing: Theme.groupedListGap
|
||||
height: searchResultsColumn.height
|
||||
visible: root.searchActive
|
||||
|
||||
Repeater {
|
||||
id: searchResultsRepeater
|
||||
model: ScriptModel {
|
||||
values: SettingsSearchService.results
|
||||
Column {
|
||||
id: searchResultsColumn
|
||||
width: parent.width
|
||||
spacing: SettingsMetrics.groupGap
|
||||
|
||||
Repeater {
|
||||
id: searchResultsRepeater
|
||||
model: ScriptModel {
|
||||
values: SettingsSearchService.results
|
||||
}
|
||||
|
||||
SettingsSidebarItem {
|
||||
id: resultDelegate
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus)
|
||||
root.ensureRowVisible(resultDelegate);
|
||||
}
|
||||
isFirstInGroup: index === 0
|
||||
isLastInGroup: index === SettingsSearchService.results.length - 1
|
||||
iconName: modelData.icon || "settings"
|
||||
title: modelData.label
|
||||
hint: modelData.category
|
||||
accent: SettingsTabs.accentFor(modelData.page || SettingsTabs.pageForTabIndex(modelData.tabIndex))
|
||||
active: root.searchSelectedIndex === index
|
||||
onClicked: keyboard => {
|
||||
root.searchSelectedIndex = index;
|
||||
root.selectSearchResult(modelData, keyboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSidebarItem {
|
||||
id: resultDelegate
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus)
|
||||
root.ensureRowVisible(resultDelegate);
|
||||
}
|
||||
isFirstInGroup: index === 0
|
||||
isLastInGroup: index === SettingsSearchService.results.length - 1
|
||||
iconName: modelData.icon || "settings"
|
||||
title: modelData.label
|
||||
hint: modelData.category
|
||||
accent: SettingsTabs.accentFor(modelData.page || SettingsTabs.pageForTabIndex(modelData.tabIndex))
|
||||
active: root.searchSelectedIndex === index
|
||||
onClicked: keyboard => {
|
||||
root.searchSelectedIndex = index;
|
||||
root.selectSearchResult(modelData, keyboard);
|
||||
}
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("No matches")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: searchField.text.length > 0 && SettingsSearchService.results.length === 0
|
||||
topPadding: Theme.spacingM
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("No matches")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: searchField.text.length > 0 && SettingsSearchService.results.length === 0
|
||||
topPadding: Theme.spacingM
|
||||
Rectangle {
|
||||
anchors.fill: searchResultsColumn
|
||||
radius: Theme.groupedListOuterRadius
|
||||
color: "transparent"
|
||||
border.width: SettingsMetrics.groupFrameWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: border.width > 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,55 +429,70 @@ Item {
|
||||
Repeater {
|
||||
model: root.navGroups
|
||||
|
||||
Column {
|
||||
id: groupColumn
|
||||
Item {
|
||||
id: groupFrame
|
||||
required property var modelData
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.groupedListGap
|
||||
height: groupColumn.height
|
||||
|
||||
readonly property var visibleIds: {
|
||||
const ids = [];
|
||||
for (const entry of modelData) {
|
||||
if (SettingsTabs.isVisible(SettingsTabs.page(entry.id)))
|
||||
ids.push(entry.id);
|
||||
Column {
|
||||
id: groupColumn
|
||||
width: parent.width
|
||||
spacing: SettingsMetrics.groupGap
|
||||
|
||||
readonly property var visibleIds: {
|
||||
const ids = [];
|
||||
for (const entry of modelData) {
|
||||
if (SettingsTabs.isVisible(SettingsTabs.page(entry.id)))
|
||||
ids.push(entry.id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: groupFrame.modelData
|
||||
|
||||
SettingsSidebarItem {
|
||||
id: categoryRow
|
||||
required property var modelData
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus)
|
||||
root.ensureRowVisible(categoryRow);
|
||||
}
|
||||
readonly property bool isHighlighted: root.keyboardHighlightId === modelData.id
|
||||
onIsHighlightedChanged: {
|
||||
if (isHighlighted)
|
||||
Qt.callLater(root.ensureRowVisible, categoryRow);
|
||||
}
|
||||
|
||||
width: groupColumn.width
|
||||
visible: SettingsTabs.isVisible(SettingsTabs.page(modelData.id))
|
||||
isFirstInGroup: groupColumn.visibleIds[0] === modelData.id
|
||||
isLastInGroup: groupColumn.visibleIds[groupColumn.visibleIds.length - 1] === modelData.id
|
||||
iconName: modelData.icon || ""
|
||||
title: modelData.text || ""
|
||||
hint: SettingsTabs.hubHint(modelData)
|
||||
accent: SettingsTabs.accentFor(modelData.id)
|
||||
active: root.activeCategoryId === modelData.id && !SettingsTabs.isPluginPage(root.currentPage)
|
||||
highlighted: isHighlighted
|
||||
onClicked: keyboard => {
|
||||
root.keyboardHighlightId = "";
|
||||
root.pageRequested(modelData.id);
|
||||
root.focusAfterNavigation(keyboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: groupColumn.modelData
|
||||
|
||||
SettingsSidebarItem {
|
||||
id: categoryRow
|
||||
required property var modelData
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus)
|
||||
root.ensureRowVisible(categoryRow);
|
||||
}
|
||||
readonly property bool isHighlighted: root.keyboardHighlightId === modelData.id
|
||||
onIsHighlightedChanged: {
|
||||
if (isHighlighted)
|
||||
Qt.callLater(root.ensureRowVisible, categoryRow);
|
||||
}
|
||||
|
||||
width: groupColumn.width
|
||||
visible: SettingsTabs.isVisible(SettingsTabs.page(modelData.id))
|
||||
isFirstInGroup: groupColumn.visibleIds[0] === modelData.id
|
||||
isLastInGroup: groupColumn.visibleIds[groupColumn.visibleIds.length - 1] === modelData.id
|
||||
iconName: modelData.icon || ""
|
||||
title: modelData.text || ""
|
||||
hint: SettingsTabs.hubHint(modelData)
|
||||
accent: SettingsTabs.accentFor(modelData.id)
|
||||
active: root.activeCategoryId === modelData.id && !SettingsTabs.isPluginPage(root.currentPage)
|
||||
highlighted: isHighlighted
|
||||
onClicked: keyboard => {
|
||||
root.keyboardHighlightId = "";
|
||||
root.pageRequested(modelData.id);
|
||||
root.focusAfterNavigation(keyboard);
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: groupColumn
|
||||
radius: Theme.groupedListOuterRadius
|
||||
color: "transparent"
|
||||
border.width: SettingsMetrics.groupFrameWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: border.width > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@ Singleton {
|
||||
function sheetWidthFor(columns) {
|
||||
return sheetPadding * 2 + columns * columnWidth + (columns - 1) * gridGap;
|
||||
}
|
||||
|
||||
function pillRadius(width, height) {
|
||||
if (Theme.pillControls)
|
||||
return Theme.fullRadius(width, height);
|
||||
return Math.min(Theme.cornerRadiusL, width / 2, height / 2);
|
||||
}
|
||||
readonly property real maxHeightInset: 100
|
||||
// Short-grid floors scale with the row unit, or compact would pad small layouts back out.
|
||||
readonly property real gridScale: tileHeight / baseTileHeight
|
||||
|
||||
@@ -67,7 +67,7 @@ DEditableGrid {
|
||||
gravity: true
|
||||
rowStep: CcMetrics.gridStep
|
||||
packer: items => GridUtils.packCells(clamped(items), placementOrder, columns, null, CcMetrics.gridStep, true).cells
|
||||
placeholderRadius: draggingSlot?.small ? Theme.fullRadius(draggingSlot.width, draggingSlot.height) : (draggingSlot?.tileItem?.bodyRadius ?? Theme.fullRadius(width, CcMetrics.tileHeight))
|
||||
placeholderRadius: draggingSlot?.small ? CcMetrics.pillRadius(draggingSlot.width, draggingSlot.height) : (draggingSlot?.tileItem?.bodyRadius ?? CcMetrics.pillRadius(width, CcMetrics.tileHeight))
|
||||
|
||||
commitHandler: items => model.setLayout(withHidden(items))
|
||||
onEditModeChanged: {
|
||||
|
||||
@@ -131,7 +131,7 @@ DEditableGridSlot {
|
||||
passthrough: root.passthrough
|
||||
dragging: root.dragging
|
||||
resizing: root.resizing
|
||||
cornerRadius: root.small ? Theme.fullRadius(root.width, root.height - root.bodyInset * 2) : (root.tileItem?.bodyRadius ?? Theme.fullRadius(root.width, root.height))
|
||||
cornerRadius: root.small ? CcMetrics.pillRadius(root.width, root.height - root.bodyInset * 2) : (root.tileItem?.bodyRadius ?? CcMetrics.pillRadius(root.width, root.height))
|
||||
sizeText: root.cols + "×" + (root.small ? CcMetrics.smallRowFraction : root.rows)
|
||||
onResizeStarted: (px, py) => {
|
||||
root.biasH = root.small ? CcMetrics.smallRowFraction * root.grid.cellWidth - CcMetrics.gridGap - root.height : 0;
|
||||
|
||||
@@ -27,7 +27,7 @@ Item {
|
||||
readonly property bool receiving: groupId !== "" && host?.hoverGroup === groupId
|
||||
// Members keep their own input in edit mode so they can be dragged out instead of moving the group.
|
||||
readonly property Item passthrough: memberRow
|
||||
readonly property real bodyRadius: Theme.fullRadius(width, Math.min(height, CcMetrics.tileHeight))
|
||||
readonly property real bodyRadius: CcMetrics.pillRadius(width, Math.min(height, CcMetrics.tileHeight))
|
||||
readonly property bool memberDragging: liftedIndex >= 0
|
||||
|
||||
property int liftedIndex: -1
|
||||
|
||||
@@ -13,11 +13,11 @@ Rectangle {
|
||||
signal toggled
|
||||
|
||||
readonly property color contentColor: enabled ? (pinned ? Theme.onSecondaryContainer : Theme.onSurfaceVariant) : Theme.onSurface_38
|
||||
readonly property real restRadius: Math.min(pinned ? Theme.cornerRadiusXL : Theme.cornerRadiusM, height / 2)
|
||||
readonly property real restRadius: Math.min(pinned && Theme.shapeMorph ? Theme.cornerRadiusXL : Theme.cornerRadiusM, height / 2)
|
||||
|
||||
height: Theme.buttonHeightXS
|
||||
width: iconOnly ? height : content.implicitWidth + Theme.spacingM * 2
|
||||
radius: layer.pressed ? Math.min(Theme.cornerRadiusS, height / 2) : restRadius
|
||||
radius: layer.pressed && Theme.shapeMorph ? Math.min(Theme.cornerRadiusS, height / 2) : restRadius
|
||||
color: pinned ? Theme.secondaryContainer : "transparent"
|
||||
border.width: pinned ? 0 : Theme.outlineWidth
|
||||
border.color: Theme.outlineVariant
|
||||
|
||||
@@ -80,9 +80,9 @@ Item {
|
||||
readonly property bool hasIconBox: (showExpand || opensPage || expanded) && !compact
|
||||
readonly property bool showsActive: active && interactive
|
||||
readonly property real restRadius: {
|
||||
if (showsActive)
|
||||
if (showsActive && Theme.shapeMorph)
|
||||
return Math.min(CcMetrics.tileActiveRadius, width / 2, bodyHeight / 2);
|
||||
return tall ? Math.min(CcMetrics.tallTileRadius, width / 2, height / 2) : Theme.fullRadius(width, bodyHeight);
|
||||
return tall ? Math.min(CcMetrics.tallTileRadius, width / 2, height / 2) : CcMetrics.pillRadius(width, bodyHeight);
|
||||
}
|
||||
property bool acceptsInput: interactive && enabled
|
||||
readonly property bool bodyActive: showsActive && !hasIconBox && !bare
|
||||
@@ -121,13 +121,13 @@ Item {
|
||||
return showsActive ? CcMetrics.tileActiveColor : CcMetrics.iconBoxInactiveColor;
|
||||
}
|
||||
|
||||
property real bodyRadius: bodyLayer.pressed ? Math.min(Theme.cornerRadiusM, width / 2, bodyHeight / 2) : restRadius
|
||||
property real bodyRadius: bodyLayer.pressed && Theme.shapeMorph ? Math.min(Theme.cornerRadiusM, width / 2, bodyHeight / 2) : restRadius
|
||||
property real iconBoxRadius: {
|
||||
if (boxLayer.pressed)
|
||||
if (boxLayer.pressed && Theme.shapeMorph)
|
||||
return Math.min(Theme.cornerRadiusS, CcMetrics.iconBoxSize / 2);
|
||||
if (showsActive)
|
||||
if (showsActive && Theme.shapeMorph)
|
||||
return Math.min(CcMetrics.iconBoxActiveRadius, CcMetrics.iconBoxSize / 2);
|
||||
return Theme.fullRadius(CcMetrics.iconBoxSize, CcMetrics.iconBoxSize);
|
||||
return CcMetrics.pillRadius(CcMetrics.iconBoxSize, CcMetrics.iconBoxSize);
|
||||
}
|
||||
|
||||
width: parent?.width ?? 0
|
||||
|
||||
@@ -34,7 +34,7 @@ Item {
|
||||
readonly property real bodyRadius: {
|
||||
if (!background)
|
||||
return Theme.fullRadius(avatarSide, avatarSide);
|
||||
return tall ? Math.min(CcMetrics.tallTileRadius, width / 2, height / 2) : Theme.fullRadius(width, height);
|
||||
return tall ? Math.min(CcMetrics.tallTileRadius, width / 2, height / 2) : CcMetrics.pillRadius(width, height);
|
||||
}
|
||||
readonly property real fallbackGlyphRatio: 0.5
|
||||
readonly property string avatarSource: PortalService.profileImageUrl
|
||||
|
||||
@@ -30,11 +30,13 @@ Singleton {
|
||||
return Math.min(Theme.cornerRadiusS, thickness / 2);
|
||||
if (thickness < compactPillThickness)
|
||||
return Math.min(Theme.cornerRadiusM, thickness / 2);
|
||||
return Theme.fullRadius(thickness, thickness);
|
||||
return Theme.pillControls ? Theme.fullRadius(thickness, thickness) : Math.min(Theme.cornerRadiusL, thickness / 2);
|
||||
}
|
||||
|
||||
function cornerRadius(thickness, style, joined, pressProgress) {
|
||||
const resting = joined ? Math.min(segmentInnerRadius, thickness / 2) : pillRadius(thickness, style);
|
||||
if (!Theme.shapeMorph)
|
||||
return resting;
|
||||
const pressed = Math.min(resting, joined || style === "flat" ? segmentPressedInnerRadius : pressedRadius);
|
||||
return Math.max(0, resting + (pressed - resting) * Math.max(0, Math.min(1, pressProgress)));
|
||||
}
|
||||
|
||||
@@ -220,7 +220,13 @@ BasePill {
|
||||
height: parent.height
|
||||
buttonSize: parent.width
|
||||
iconSize: 14 * root.contentScale
|
||||
radius: pressed ? Theme.cornerRadiusXS : (checked ? Theme.cornerRadiusFull : Theme.cornerRadiusS)
|
||||
radius: {
|
||||
if (!Theme.shapeMorph)
|
||||
return Theme.cornerRadiusS;
|
||||
if (pressed)
|
||||
return Theme.cornerRadiusXS;
|
||||
return checked ? Theme.cornerRadiusFull : Theme.cornerRadiusS;
|
||||
}
|
||||
variant: "filled"
|
||||
round: false
|
||||
checkable: true
|
||||
|
||||
@@ -40,7 +40,7 @@ Item {
|
||||
iconName: "power_settings_new"
|
||||
iconColor: Theme.onSecondaryContainer
|
||||
backgroundColor: root.round ? Theme.secondaryContainer : "transparent"
|
||||
radius: pressed ? Theme.cornerRadiusS : Theme.fullRadius(width, height)
|
||||
radius: pressed && Theme.shapeMorph ? Theme.cornerRadiusS : (Theme.pillControls ? Theme.fullRadius(width, height) : Math.min(Theme.cornerRadiusL, width / 2, height / 2))
|
||||
buttonSize: Theme.buttonHeightM
|
||||
onClicked: root.lockHost?.showPowerMenu()
|
||||
}
|
||||
|
||||
@@ -521,7 +521,6 @@ Item {
|
||||
minButtonWidth: 56
|
||||
buttonPadding: Theme.spacingS
|
||||
textSize: Theme.fontSizeSmall
|
||||
spacing: Theme.groupedListGap
|
||||
onSelectionChanged: (index, selected) => root.setVisibleEntryAction(index, selected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ Item {
|
||||
readonly property bool isExpanded: root.expandedWifiSsid === modelData.ssid
|
||||
|
||||
width: parent?.width ?? 0
|
||||
spacing: Theme.groupedListGap
|
||||
spacing: SettingsMetrics.groupGap
|
||||
SettingsRow {
|
||||
id: wifiNetworkRow
|
||||
title: wifiNetworkDelegate.modelData.ssid || I18n.tr("Unknown")
|
||||
@@ -525,7 +525,7 @@ Item {
|
||||
readonly property var detailFields: networkWifiTab.wifiDetailFields(modelData)
|
||||
|
||||
width: parent?.width ?? 0
|
||||
spacing: Theme.groupedListGap
|
||||
spacing: SettingsMetrics.groupGap
|
||||
|
||||
SettingsRow {
|
||||
id: savedWifiRow
|
||||
|
||||
@@ -482,7 +482,7 @@ Item {
|
||||
readonly property bool isStopped: printerData?.state === "stopped"
|
||||
|
||||
width: parent?.width ?? 0
|
||||
spacing: Theme.groupedListGap
|
||||
spacing: SettingsMetrics.groupGap
|
||||
|
||||
SettingsRow {
|
||||
iconName: printerDelegate.isStopped ? "print_disabled" : "print"
|
||||
|
||||
@@ -472,7 +472,7 @@ Item {
|
||||
|
||||
Column {
|
||||
width: parent?.width ?? 0
|
||||
spacing: Theme.groupedListGap
|
||||
spacing: SettingsMetrics.groupGap
|
||||
|
||||
Repeater {
|
||||
model: root.packagesExpanded && !SystemUpdateService.isUpgrading ? SystemUpdateService.systemUpdates.slice(0, root.packageListCap) : []
|
||||
|
||||
@@ -251,7 +251,20 @@ Column {
|
||||
|
||||
SettingsCard {
|
||||
tab: "theme"
|
||||
title: I18n.tr("Density", "appearance settings section label")
|
||||
title: I18n.tr("Appearance")
|
||||
settingKey: "surfaceAppearance"
|
||||
|
||||
SettingsButtonGroupRow {
|
||||
tab: "theme"
|
||||
tags: ["style", "classic", "expressive", "pill", "round", "shape", "morph", "corners", "buttons"]
|
||||
settingKey: "interfaceStyle"
|
||||
text: I18n.tr("Style")
|
||||
model: [I18n.tr("Expressive"), I18n.tr("Classic", "interface style option with one corner radius on controls and no shape change on press")]
|
||||
values: ["expressive", "classic"]
|
||||
value: SettingsData.interfaceStyle
|
||||
fallbackValue: "expressive"
|
||||
onValueSelected: value => SettingsData.set("interfaceStyle", value)
|
||||
}
|
||||
|
||||
SettingsButtonGroupRow {
|
||||
tab: "theme"
|
||||
@@ -264,6 +277,20 @@ Column {
|
||||
fallbackValue: "comfortable"
|
||||
onValueSelected: value => SettingsData.set("density", value)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
tab: "theme"
|
||||
tags: ["classic", "divider", "dividers", "lines", "rows", "sections", "sidebar", "outline"]
|
||||
settingKey: "sectionDividerOpacity"
|
||||
visible: SettingsData.interfaceStyle === "classic"
|
||||
text: I18n.tr("Section dividers", "classic interface style: strength of the lines between rows of a settings section")
|
||||
minimumLabel: I18n.tr("Off")
|
||||
minimum: 0
|
||||
maximum: 40
|
||||
step: 1
|
||||
value: Math.round((SettingsData.sectionDividerOpacity ?? 0) * 100)
|
||||
onSliderValueChanged: newValue => SettingsData.set("sectionDividerOpacity", newValue / 100)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
@@ -277,7 +304,7 @@ Column {
|
||||
tags: ["corner", "radius", "rounded", "square", "fixed", "material", "shape"]
|
||||
settingKey: "radiusMode"
|
||||
text: I18n.tr("Corner style")
|
||||
model: [I18n.tr("Material scale", "corner style: Material shape scale with a strength slider"), I18n.tr("Fixed", "corner style: one radius for every corner")]
|
||||
model: [I18n.tr("Material", "corner style: Material shape scale with a strength slider"), I18n.tr("Fixed", "corner style: one radius for every corner")]
|
||||
currentIndex: SettingsData.radiusMode === "fixed" ? 1 : 0
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (selected)
|
||||
|
||||
@@ -77,13 +77,16 @@ Column {
|
||||
readonly property bool hasHidden: disabledGroups.length > 0
|
||||
property bool showHidden: false
|
||||
|
||||
readonly property bool isFirstInGroup: parent?.isSettingsGroupHost ? parent.isEdge(root, true) : true
|
||||
readonly property bool isLastInGroup: parent?.isSettingsGroupHost ? parent.isEdge(root, false) : true
|
||||
readonly property real topRadius: isFirstInGroup ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
readonly property real bottomRadius: isLastInGroup ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
// A Loader may hold the editor, so the slot the group sees is the Loader, not the editor
|
||||
readonly property Item groupSlot: parent?.isSettingsGroupHost ? root : parent
|
||||
readonly property bool inGroup: groupSlot?.parent?.isSettingsGroupHost ?? false
|
||||
readonly property bool isFirstInGroup: inGroup ? groupSlot.parent.isEdge(groupSlot, true) : true
|
||||
readonly property bool isLastInGroup: inGroup ? groupSlot.parent.isEdge(groupSlot, false) : true
|
||||
readonly property real topRadius: isFirstInGroup ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
readonly property real bottomRadius: isLastInGroup ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
|
||||
width: parent?.width ?? 0
|
||||
spacing: Theme.groupedListGap
|
||||
spacing: SettingsMetrics.groupGap
|
||||
|
||||
SettingsSearchRegistration {
|
||||
target: root
|
||||
@@ -91,8 +94,10 @@ Column {
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
inGroup: root.inGroup
|
||||
dividerBefore: root.inGroup && !root.isFirstInGroup
|
||||
topRadius: root.topRadius
|
||||
bottomRadius: Theme.groupedListInnerRadius
|
||||
bottomRadius: SettingsMetrics.groupInnerRadius
|
||||
body: StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("Drag groups above the clock to sit left of it, below to sit right. Click the eye to hide a group.", "island settings: home layout editor hint")
|
||||
@@ -114,8 +119,10 @@ Column {
|
||||
required property var modelData
|
||||
|
||||
reorderList: enabledList
|
||||
topRadius: dragging ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
bottomRadius: dragging ? Theme.groupedListOuterRadius : (position === enabledList.count - 1 && !root.hasHidden ? root.bottomRadius : Theme.groupedListInnerRadius)
|
||||
inGroup: root.inGroup
|
||||
dividerBefore: root.inGroup && !dragging
|
||||
topRadius: dragging ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
bottomRadius: dragging ? Theme.groupedListOuterRadius : (position === enabledList.count - 1 && !root.hasHidden ? root.bottomRadius : SettingsMetrics.groupInnerRadius)
|
||||
title: root.presentation[modelData.id].text
|
||||
subtitle: root.presentation[modelData.id].description
|
||||
iconName: root.presentation[modelData.id].icon
|
||||
@@ -139,8 +146,10 @@ Column {
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
topRadius: Theme.groupedListInnerRadius
|
||||
bottomRadius: root.showHidden ? Theme.groupedListInnerRadius : root.bottomRadius
|
||||
inGroup: root.inGroup
|
||||
dividerBefore: root.inGroup
|
||||
topRadius: SettingsMetrics.groupInnerRadius
|
||||
bottomRadius: root.showHidden ? SettingsMetrics.groupInnerRadius : root.bottomRadius
|
||||
title: I18n.tr("Hidden (%1)", "island settings: hidden groups divider, %1 is count").arg(root.disabledGroups.length)
|
||||
iconName: "visibility_off"
|
||||
visible: root.hasHidden
|
||||
@@ -157,7 +166,7 @@ Column {
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.groupedListGap
|
||||
spacing: SettingsMetrics.groupGap
|
||||
visible: root.showHidden && root.hasHidden
|
||||
|
||||
Repeater {
|
||||
@@ -169,8 +178,10 @@ Column {
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
topRadius: Theme.groupedListInnerRadius
|
||||
bottomRadius: index === root.disabledGroups.length - 1 ? root.bottomRadius : Theme.groupedListInnerRadius
|
||||
inGroup: root.inGroup
|
||||
dividerBefore: root.inGroup
|
||||
topRadius: SettingsMetrics.groupInnerRadius
|
||||
bottomRadius: index === root.disabledGroups.length - 1 ? root.bottomRadius : SettingsMetrics.groupInnerRadius
|
||||
title: root.presentation[modelData.id].text
|
||||
subtitle: root.presentation[modelData.id].description
|
||||
iconName: root.presentation[modelData.id].icon
|
||||
|
||||
@@ -36,7 +36,6 @@ SettingsRow {
|
||||
|
||||
title: text
|
||||
subtitle: description
|
||||
spacing: Theme.groupedListGap
|
||||
|
||||
Item {
|
||||
id: trailingHost
|
||||
@@ -60,7 +59,6 @@ SettingsRow {
|
||||
x: root.compact ? (parent.width - width) / 2 : 0
|
||||
selectionMode: "single"
|
||||
currentIndex: root.values.length > 0 ? root.indexOfValue(root.value) : 0
|
||||
spacing: root.spacing
|
||||
enabled: root.enabled
|
||||
maximumWidth: root.compact ? root.width - SettingsMetrics.rowPaddingH * 2 : -1
|
||||
onSelectionChanged: (index, selected) => {
|
||||
|
||||
@@ -22,7 +22,7 @@ Column {
|
||||
signal hideOnLastDisplayToggled(bool checked)
|
||||
|
||||
width: parent?.width ?? 0
|
||||
spacing: Theme.groupedListGap
|
||||
spacing: SettingsMetrics.groupGap
|
||||
|
||||
function screenPref(screen) {
|
||||
const pref = {
|
||||
|
||||
@@ -8,7 +8,7 @@ Item {
|
||||
|
||||
readonly property bool isSettingsGroup: true
|
||||
default property alias content: host.data
|
||||
property real spacing: Theme.groupedListGap
|
||||
property real spacing: SettingsMetrics.groupGap
|
||||
property real customPaddingH: SettingsMetrics.rowPaddingH
|
||||
property real customPaddingV: SettingsMetrics.rowPaddingV
|
||||
property bool highlighted: false
|
||||
@@ -120,14 +120,22 @@ Item {
|
||||
width: root.width
|
||||
height: slot.height
|
||||
visible: !slot.transparent
|
||||
topLeftRadius: first ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
topRightRadius: first ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
bottomLeftRadius: last ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
bottomRightRadius: last ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
topLeftRadius: first ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
topRightRadius: first ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
bottomLeftRadius: last ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
bottomRightRadius: last ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
color: root.highlighted ? Theme.blend(root.slotColor, Theme.primary, SettingsMetrics.highlightBlend) : root.slotColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.width: SettingsMetrics.groupEdgeWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Rectangle {
|
||||
x: SettingsMetrics.rowPaddingH
|
||||
width: parent.width - SettingsMetrics.rowPaddingH * 2
|
||||
height: Theme.dividerWidth
|
||||
visible: SettingsMetrics.continuousGroups && !parent.first
|
||||
color: SettingsMetrics.groupDividerColor
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None
|
||||
ColorAnimation {
|
||||
@@ -139,6 +147,16 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
z: 1
|
||||
radius: Theme.groupedListOuterRadius
|
||||
color: "transparent"
|
||||
border.width: SettingsMetrics.groupFrameWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: border.width > 0
|
||||
}
|
||||
|
||||
Item {
|
||||
id: host
|
||||
|
||||
|
||||
@@ -36,6 +36,14 @@ Singleton {
|
||||
readonly property real sectionLabelTopGap: Math.max(Theme.spacingXS, Theme.spacingS + Theme.densityStepS)
|
||||
readonly property real sectionLabelBottomGap: Theme.spacingM
|
||||
readonly property real navIconSize: Theme.avatarSize
|
||||
readonly property bool navIconBadges: !Theme.classicStyle
|
||||
readonly property bool continuousGroups: Theme.classicStyle
|
||||
readonly property real groupGap: continuousGroups ? 0 : Theme.groupedListGap
|
||||
readonly property real groupInnerRadius: continuousGroups ? 0 : Theme.groupedListInnerRadius
|
||||
// Rows share one frame in a continuous group so Layer outline never doubles up between them
|
||||
readonly property real groupEdgeWidth: continuousGroups ? 0 : Theme.layerOutlineWidth
|
||||
readonly property real groupFrameWidth: continuousGroups ? Theme.layerOutlineWidth : 0
|
||||
readonly property color groupDividerColor: Theme.withAlpha(Theme.outline, SettingsData.sectionDividerOpacity)
|
||||
readonly property real navItemMinHeight: rowMinHeight
|
||||
readonly property real sidebarGroupGap: Theme.spacingS
|
||||
readonly property real searchBarGap: Theme.spacingM
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.DCommon.Widgets
|
||||
import "../../../Common/QmlUtils.js" as QmlUtils
|
||||
|
||||
@@ -9,5 +10,6 @@ DReorderList {
|
||||
readonly property bool transparentSlot: true
|
||||
readonly property bool firstInGroup: parent?.isSettingsGroupHost ? parent.isEdge(root, true) : true
|
||||
readonly property bool lastInGroup: parent?.isSettingsGroupHost ? parent.isEdge(root, false) : true
|
||||
spacing: SettingsMetrics.groupGap
|
||||
Component.onCompleted: flickable = QmlUtils.findParentFlickable(root.parent)
|
||||
}
|
||||
@@ -14,8 +14,8 @@ SettingsRow {
|
||||
|
||||
paintBackground: true
|
||||
rowColor: dragging ? Theme.blend(SettingsMetrics.rowColor, Theme.onSurface, Theme.stateLayerDrag) : SettingsMetrics.rowColor
|
||||
topRadius: dragging || (position === 0 && reorderList.firstInGroup) ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
bottomRadius: dragging || (position === reorderList.count - 1 && reorderList.lastInGroup) ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
topRadius: dragging || (position === 0 && reorderList.firstInGroup) ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
bottomRadius: dragging || (position === reorderList.count - 1 && reorderList.lastInGroup) ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
z: dragging ? 100 : 0
|
||||
|
||||
function focusHandle(reason) {
|
||||
|
||||
@@ -53,10 +53,12 @@ T.Control {
|
||||
readonly property bool hasText: title !== "" || subtitle !== ""
|
||||
readonly property bool isHighlighted: settingKey !== "" && SettingsSearchService.highlightSection === settingKey
|
||||
property bool highlighted: isHighlighted
|
||||
property bool inGroup: (groupItem.parent?.isSettingsGroupHost ?? false) || (groupItem.parent?.parent?.isSettingsGroupHost ?? false)
|
||||
readonly property bool isFirstInGroup: _edge(true)
|
||||
readonly property bool isLastInGroup: _edge(false)
|
||||
property real topRadius: isFirstInGroup ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
property real bottomRadius: isLastInGroup ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
property bool dividerBefore: inGroup && !isFirstInGroup
|
||||
property real topRadius: isFirstInGroup ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
property real bottomRadius: isLastInGroup ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
property real minHeight: subtitle !== "" ? SettingsMetrics.rowMinHeightTwoLine : SettingsMetrics.rowMinHeight
|
||||
// Keyboard focus on the row or any control inside it tints the row, so navigation reads without a focus ring
|
||||
readonly property Item activeItem: Window.activeFocusItem
|
||||
@@ -141,8 +143,16 @@ T.Control {
|
||||
anchors.fill: parent
|
||||
visible: root.paintBackground
|
||||
color: root.active ? Theme.selectedContainer : root.highlighted ? Theme.blend(root.rowColor, Theme.primary, SettingsMetrics.highlightBlend) : root.rowColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.width: root.inGroup ? SettingsMetrics.groupEdgeWidth : Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Rectangle {
|
||||
x: root.paddingH
|
||||
width: parent.width - root.paddingH * 2
|
||||
height: Theme.dividerWidth
|
||||
visible: SettingsMetrics.continuousGroups && root.dividerBefore
|
||||
color: SettingsMetrics.groupDividerColor
|
||||
}
|
||||
topLeftRadius: root.topRadius
|
||||
topRightRadius: root.topRadius
|
||||
bottomLeftRadius: root.bottomRadius
|
||||
|
||||
@@ -33,12 +33,16 @@ Rectangle {
|
||||
}
|
||||
|
||||
readonly property var accentPair: Theme.accent(accent)
|
||||
readonly property color badgeColor: accentPair?.container ?? Theme.withAlpha(Theme.primary, Theme.tonalTintAlpha)
|
||||
readonly property color glyphColor: accentPair?.onContainer ?? Theme.primary
|
||||
readonly property color badgeColor: SettingsMetrics.navIconBadges ? accentPair?.container ?? Theme.withAlpha(Theme.primary, Theme.tonalTintAlpha) : "transparent"
|
||||
readonly property color glyphColor: {
|
||||
if (SettingsMetrics.navIconBadges)
|
||||
return accentPair?.onContainer ?? Theme.primary;
|
||||
return active ? Theme.onSelectedContainer : Theme.primary;
|
||||
}
|
||||
property bool isFirstInGroup: true
|
||||
property bool isLastInGroup: true
|
||||
property real topRadius: active || isFirstInGroup ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
property real bottomRadius: active || isLastInGroup ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
property real topRadius: (active && Theme.shapeMorph) || isFirstInGroup ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
property real bottomRadius: (active && Theme.shapeMorph) || isLastInGroup ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
|
||||
Behavior on topRadius {
|
||||
enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None
|
||||
@@ -65,9 +69,17 @@ Rectangle {
|
||||
bottomLeftRadius: bottomRadius
|
||||
bottomRightRadius: bottomRadius
|
||||
color: active ? SettingsMetrics.selectedRowColor : SettingsMetrics.sidebarRowColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.width: SettingsMetrics.groupEdgeWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Rectangle {
|
||||
x: Theme.spacingL
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
height: Theme.dividerWidth
|
||||
visible: SettingsMetrics.continuousGroups && !root.isFirstInGroup && !root.active
|
||||
color: SettingsMetrics.groupDividerColor
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None
|
||||
ColorAnimation {
|
||||
|
||||
@@ -10,6 +10,8 @@ Item {
|
||||
|
||||
readonly property bool isSettingsRow: true
|
||||
readonly property bool transparentSlot: true
|
||||
readonly property bool inGroup: parent?.isSettingsGroupHost ?? false
|
||||
readonly property bool isFirstInGroup: inGroup ? parent.isEdge(root, true) : true
|
||||
|
||||
property string startTitle: ""
|
||||
property int startHour: 7
|
||||
@@ -48,6 +50,15 @@ Item {
|
||||
width: parent?.width ?? 0
|
||||
height: halves.implicitHeight
|
||||
|
||||
Rectangle {
|
||||
x: SettingsMetrics.rowPaddingH
|
||||
z: 1
|
||||
width: parent.width - SettingsMetrics.rowPaddingH * 2
|
||||
height: Theme.dividerWidth
|
||||
visible: SettingsMetrics.continuousGroups && root.inGroup && !root.isFirstInGroup
|
||||
color: SettingsMetrics.groupDividerColor
|
||||
}
|
||||
|
||||
Row {
|
||||
id: halves
|
||||
width: parent.width
|
||||
@@ -62,6 +73,8 @@ Item {
|
||||
|
||||
width: root.showEnd ? (root.width - Theme.groupedListGap) / 2 : root.width
|
||||
paintBackground: true
|
||||
inGroup: root.inGroup
|
||||
dividerBefore: false
|
||||
topRadius: Theme.groupedListOuterRadius
|
||||
bottomRadius: Theme.groupedListOuterRadius
|
||||
title: isStart ? root.startTitle : root.endTitle
|
||||
|
||||
@@ -50,8 +50,8 @@ Item {
|
||||
SettingsToggleRow {
|
||||
id: header
|
||||
width: parent.width
|
||||
topRadius: root.firstInGroup ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
bottomRadius: root.lastInGroup && !root.expanded ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
topRadius: root.firstInGroup ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
bottomRadius: root.lastInGroup && !root.expanded ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
tab: root.tab
|
||||
tags: root.tags
|
||||
settingKey: root.settingKey
|
||||
|
||||
@@ -59,8 +59,8 @@ Item {
|
||||
SettingsToggleRow {
|
||||
id: header
|
||||
width: parent.width
|
||||
topRadius: root.firstInGroup ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
bottomRadius: root.lastInGroup && !root.checked ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius
|
||||
topRadius: root.firstInGroup ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
bottomRadius: root.lastInGroup && !root.checked ? Theme.groupedListOuterRadius : SettingsMetrics.groupInnerRadius
|
||||
tab: root.tab
|
||||
tags: root.tags
|
||||
settingKey: root.settingKey
|
||||
|
||||
@@ -50,6 +50,8 @@ Theme.buttonRadius(width, height, buttonHeight, pressed, true)
|
||||
|
||||
Use `fullRadius()` for pills and round controls so lower strength values reduce their rounding. `cornerRadiusFull` remains available for compatibility. See the shared [shape reference](../../dux-qml-common/SHAPES.md) for component baselines.
|
||||
|
||||
`Theme.buttonRadius(...)` follows the Interface style setting: Classic turns off pills and the press morph. Plugins that hand-roll control shapes can read `Theme.pillControls` and `Theme.shapeMorph` (both false in Classic).
|
||||
|
||||
## Colors
|
||||
|
||||
### Surface Colors
|
||||
|
||||
@@ -21,7 +21,7 @@ DCard {
|
||||
clickable: true
|
||||
pad: Theme.spacingL
|
||||
restRadius: Theme.groupedListOuterRadius
|
||||
bodyRadius: focusTarget.pressed ? Theme.cornerRadiusM : restRadius
|
||||
bodyRadius: focusTarget.pressed && Theme.shapeMorph ? Theme.cornerRadiusM : restRadius
|
||||
implicitHeight: body.implicitHeight + pad * 2
|
||||
Accessible.name: heading
|
||||
Accessible.description: description
|
||||
|
||||
@@ -161,12 +161,14 @@ test("set and reset refuse managed keys and change the others", () => {
|
||||
AdminPolicy: { lockSettings: { lockBeforeSuspend: true } },
|
||||
Spec: { SPEC, set: (root, key, value) => changes.push([key, value]) },
|
||||
DensityPresets: loadScript(new URL("../Common/settings/DensityPresets.js", import.meta.url)),
|
||||
Store: SettingsStore,
|
||||
density: "comfortable",
|
||||
interfaceStyle: "expressive",
|
||||
log: { warn: (...args) => warnings.push(args.at(-1)) },
|
||||
saveSettings() {},
|
||||
_hooks: {},
|
||||
lockBeforeSuspend: true
|
||||
}, ["isManaged", "set", "resetToDefault"]);
|
||||
}, ["isManaged", "set", "resetToDefault", "presetDefault"]);
|
||||
context.set("lockBeforeSuspend", false);
|
||||
context.set("lockAtStartup", true);
|
||||
context.resetToDefault(["lockBeforeSuspend", "acLockTimeout"]);
|
||||
@@ -237,6 +239,25 @@ test("settings set refuses an unknown density, from IPC too, without saving or m
|
||||
assert.deepEqual([moves, saves, SettingsData.density], [[["comfortable", "dense"]], 1, "dense"]);
|
||||
});
|
||||
|
||||
test("settings set refuses an unknown interface style, from IPC too, without saving", () => {
|
||||
let saves = 0;
|
||||
const SettingsData = settingsDataContext({
|
||||
AdminPolicy: { lockSettings: {} },
|
||||
Spec: loadScript(new URL("../Common/settings/SettingsSpec.js", import.meta.url)),
|
||||
log: { warn() {} },
|
||||
saveSettings: () => saves++,
|
||||
_hooks: {},
|
||||
interfaceStyle: "expressive"
|
||||
}, ["isManaged", "hasSetting", "set"]);
|
||||
const context = vm.createContext({ AdminPolicy: { disableSettingsIpcSet: false }, SettingsData, log: { warn() {} } });
|
||||
for (const handler of settingsIpcHandlers())
|
||||
vm.runInContext(handler, context);
|
||||
for (const value of ["Classic", "flat", ""])
|
||||
assert.equal(context.set("interfaceStyle", value), "SETTINGS_SET_FAILURE", value);
|
||||
assert.equal(context.set("interfaceStyle", "classic"), "SETTINGS_SET_SUCCESS");
|
||||
assert.deepEqual([saves, SettingsData.interfaceStyle], [1, "classic"]);
|
||||
});
|
||||
|
||||
test("the legacy session migration leaves managed lock settings alone", () => {
|
||||
const managed = new Set(["acLockTimeout", "lockBeforeSuspend"]);
|
||||
const changes = [];
|
||||
|
||||
@@ -137,8 +137,10 @@ test("an unknown previous level moves sizes from comfortable, the level Theme re
|
||||
|
||||
test("resets land on the current level's preset", () => {
|
||||
const changes = [];
|
||||
const settings = settingsFunctions(["barConfigDefault", "dockConfigDefaults", "specDefault", "isDefault", "resetToDefault", "isManaged"], vm.createContext({
|
||||
const settings = settingsFunctions(["barConfigDefault", "dockConfigDefaults", "specDefault", "isDefault", "resetToDefault", "isManaged", "presetDefault"], vm.createContext({
|
||||
DensityPresets: presets,
|
||||
Store: loadScript(new URL("../Common/settings/SettingsStore.js", import.meta.url)),
|
||||
interfaceStyle: "expressive",
|
||||
DockConfig: loadScript(new URL("../Common/settings/DockConfig.js", import.meta.url)),
|
||||
SpecUtil: loadScript(new URL("../DCommon/Common/settings/SpecUtil.js", import.meta.url)),
|
||||
Spec: { SPEC: Spec.SPEC, set: (root, key, value) => changes.push([key, value]) },
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { loadScript } from "./qml-script.mjs";
|
||||
|
||||
const Spec = loadScript(new URL("../Common/settings/SettingsSpec.js", import.meta.url));
|
||||
const store = loadScript(new URL("../Common/settings/SettingsStore.js", import.meta.url));
|
||||
const presets = loadScript(new URL("../Common/settings/DensityPresets.js", import.meta.url));
|
||||
|
||||
function settingsFunctions(names, context) {
|
||||
const source = readFileSync(new URL("../Common/SettingsData.qml", import.meta.url), "utf8");
|
||||
context.root = context;
|
||||
for (const name of names) {
|
||||
const found = source.match(new RegExp(`^ function ${name}\\(([^)]*)\\) \\{\\n[\\s\\S]*?^ \\}`, "m"));
|
||||
assert.ok(found, name);
|
||||
vm.runInContext(found[0], context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
function settings(globals) {
|
||||
return settingsFunctions(["applyInterfaceStyle", "presetDefault", "isManaged", "updateCompositorLayout"], vm.createContext(Object.assign({
|
||||
Store: store,
|
||||
Spec,
|
||||
DensityPresets: presets,
|
||||
AdminPolicy: { lockSettings: {} },
|
||||
density: "comfortable",
|
||||
compositorLayoutRefreshNeeded() {}
|
||||
}, globals)));
|
||||
}
|
||||
|
||||
test("a style change moves the corner style with it unless the user picked one", () => {
|
||||
const cases = [
|
||||
["expressive", "classic", "scale", "fixed"],
|
||||
["classic", "expressive", "fixed", "scale"],
|
||||
["expressive", "classic", "fixed", "fixed"],
|
||||
["classic", "expressive", "scale", "scale"]
|
||||
];
|
||||
for (const [from, to, mode, expected] of cases) {
|
||||
const root = settings({ interfaceStyle: from, radiusMode: mode });
|
||||
Spec.set(root, "interfaceStyle", to, () => {}, { applyInterfaceStyle: root.applyInterfaceStyle });
|
||||
assert.equal(root.radiusMode, expected, `${from} -> ${to} at ${mode}`);
|
||||
}
|
||||
const managed = settings({ interfaceStyle: "expressive", radiusMode: "scale", AdminPolicy: { lockSettings: { radiusMode: "scale" } } });
|
||||
Spec.set(managed, "interfaceStyle", "classic", () => {}, { applyInterfaceStyle: managed.applyInterfaceStyle });
|
||||
assert.equal(managed.radiusMode, "scale");
|
||||
});
|
||||
|
||||
test("a load fills a missing corner style from the file's interface style, and a save omits only that", () => {
|
||||
for (const [style, mode] of [["classic", "fixed"], ["expressive", "scale"]]) {
|
||||
const root = { settingsConfigVersion: 999 };
|
||||
store.parse(root, { configVersion: 999, interfaceStyle: style });
|
||||
assert.equal(root.radiusMode, mode, style);
|
||||
assert.equal("radiusMode" in store.toJson(root, new Set(["interfaceStyle"])), false, style);
|
||||
root.radiusMode = mode === "fixed" ? "scale" : "fixed";
|
||||
assert.equal(store.toJson(root, new Set(["interfaceStyle"])).radiusMode, root.radiusMode, style);
|
||||
const kept = { settingsConfigVersion: 999 };
|
||||
store.parse(kept, { configVersion: 999, interfaceStyle: style, radiusMode: root.radiusMode });
|
||||
assert.equal(kept.radiusMode, root.radiusMode, style);
|
||||
}
|
||||
});
|
||||
|
||||
test("under classic the corner style's default, modified state and reset follow the style", () => {
|
||||
const changes = [];
|
||||
const root = settingsFunctions(["specDefault", "isDefault", "resetToDefault", "isManaged", "presetDefault"], vm.createContext({
|
||||
Store: store,
|
||||
DensityPresets: presets,
|
||||
SpecUtil: loadScript(new URL("../DCommon/Common/settings/SpecUtil.js", import.meta.url)),
|
||||
Spec: { SPEC: Spec.SPEC, set: (target, key, value) => { changes.push([key, value]); target[key] = value; } },
|
||||
AdminPolicy: { lockSettings: {} },
|
||||
saveSettings() {},
|
||||
_hooks: {},
|
||||
density: "comfortable",
|
||||
interfaceStyle: "classic",
|
||||
radiusMode: "scale"
|
||||
}));
|
||||
assert.equal(root.specDefault("radiusMode"), "fixed");
|
||||
assert.equal(root.isDefault(["radiusMode"]), false);
|
||||
root.resetToDefault(["radiusMode"]);
|
||||
assert.deepEqual(changes, [["radiusMode", "fixed"]]);
|
||||
assert.equal(root.isDefault(["radiusMode"]), true);
|
||||
});
|
||||
@@ -101,6 +101,26 @@
|
||||
"page": "notifications",
|
||||
"icon": "palette"
|
||||
},
|
||||
{
|
||||
"section": "surfaceAppearance",
|
||||
"label": "Appearance",
|
||||
"tabIndex": -1,
|
||||
"category": "Interface style",
|
||||
"keywords": [
|
||||
"appearance",
|
||||
"buttons",
|
||||
"classic",
|
||||
"corners",
|
||||
"expressive",
|
||||
"interface",
|
||||
"morph",
|
||||
"pill",
|
||||
"round",
|
||||
"shape",
|
||||
"style"
|
||||
],
|
||||
"page": "theme_surfaces"
|
||||
},
|
||||
{
|
||||
"section": "wallpaper",
|
||||
"label": "Appearance",
|
||||
@@ -835,32 +855,6 @@
|
||||
"icon": "schedule",
|
||||
"description": "Clock, weather, locale"
|
||||
},
|
||||
{
|
||||
"section": "density",
|
||||
"label": "Density",
|
||||
"tabIndex": -1,
|
||||
"category": "Interface style",
|
||||
"keywords": [
|
||||
"bar",
|
||||
"comfortable",
|
||||
"compact",
|
||||
"control center",
|
||||
"dense",
|
||||
"density",
|
||||
"dock",
|
||||
"frame",
|
||||
"interface",
|
||||
"island",
|
||||
"menus",
|
||||
"padding",
|
||||
"rows",
|
||||
"size",
|
||||
"spacing",
|
||||
"spacious",
|
||||
"style"
|
||||
],
|
||||
"page": "theme_surfaces"
|
||||
},
|
||||
{
|
||||
"section": "selectedMonitor",
|
||||
"label": "Display",
|
||||
@@ -1428,6 +1422,32 @@
|
||||
"icon": "keyboard",
|
||||
"description": "Shortcuts, keyboard, mouse"
|
||||
},
|
||||
{
|
||||
"section": "density",
|
||||
"label": "Interface density",
|
||||
"tabIndex": -1,
|
||||
"category": "Interface style",
|
||||
"keywords": [
|
||||
"bar",
|
||||
"comfortable",
|
||||
"compact",
|
||||
"control center",
|
||||
"dense",
|
||||
"density",
|
||||
"dock",
|
||||
"frame",
|
||||
"interface",
|
||||
"island",
|
||||
"menus",
|
||||
"padding",
|
||||
"rows",
|
||||
"size",
|
||||
"spacing",
|
||||
"spacious",
|
||||
"style"
|
||||
],
|
||||
"page": "theme_surfaces"
|
||||
},
|
||||
{
|
||||
"section": "blurLayerOutline",
|
||||
"label": "Layer outline",
|
||||
@@ -2093,6 +2113,26 @@
|
||||
"page": "theme_surfaces",
|
||||
"icon": "unfold_more"
|
||||
},
|
||||
{
|
||||
"section": "sectionDividerOpacity",
|
||||
"label": "Section dividers",
|
||||
"tabIndex": -1,
|
||||
"category": "Interface style",
|
||||
"keywords": [
|
||||
"classic",
|
||||
"divider",
|
||||
"dividers",
|
||||
"interface",
|
||||
"lines",
|
||||
"outline",
|
||||
"rows",
|
||||
"section",
|
||||
"sections",
|
||||
"sidebar",
|
||||
"style"
|
||||
],
|
||||
"page": "theme_surfaces"
|
||||
},
|
||||
{
|
||||
"section": "_hub_security_accounts",
|
||||
"label": "Security",
|
||||
@@ -2291,6 +2331,25 @@
|
||||
],
|
||||
"icon": "volume_up"
|
||||
},
|
||||
{
|
||||
"section": "interfaceStyle",
|
||||
"label": "Style",
|
||||
"tabIndex": -1,
|
||||
"category": "Interface style",
|
||||
"keywords": [
|
||||
"buttons",
|
||||
"classic",
|
||||
"corners",
|
||||
"expressive",
|
||||
"interface",
|
||||
"morph",
|
||||
"pill",
|
||||
"round",
|
||||
"shape",
|
||||
"style"
|
||||
],
|
||||
"page": "theme_surfaces"
|
||||
},
|
||||
{
|
||||
"section": "notificationSummaryFontSize",
|
||||
"label": "Summary font size",
|
||||
|
||||
Reference in new issue
Block a user