mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-31 07:27:47 -04:00
refactor(control center): free the edit button and misc tweaks
This commit is contained in:
1 parent
585827e7e4
commit
b48fce0447
15 files changed
+212
-124
No files matched your search
@@ -131,9 +131,10 @@ function cellAt(layout, x, y, cols, rows) {
|
||||
};
|
||||
}
|
||||
|
||||
// With gravity, a tile dropped onto the one below it gets lifted straight back into the hole it left, so a tile
|
||||
// fully covering exactly one other that fits where it came from trades places with it instead.
|
||||
function swapInto(items, cells, index, target) {
|
||||
// With gravity, a tile dragged down onto another and dropped at its row gets lifted straight back above it. So a
|
||||
// tile coming from above lands just past the tiles it overlaps, and one fully covering a single tile that fits
|
||||
// where it came from trades places with it.
|
||||
function dropInto(items, cells, index, target) {
|
||||
const origin = cells[index];
|
||||
if (!origin || !target)
|
||||
return items;
|
||||
@@ -151,12 +152,20 @@ function swapInto(items, cells, index, target) {
|
||||
};
|
||||
if (overlaps([box(origin)], moved.x, moved.y, moved.w, moved.h))
|
||||
return items;
|
||||
const hits = cells.reduce((found, cell, i) => i !== index && cell && overlaps([box(cell)], moved.x, moved.y, moved.w, moved.h) ? found.concat([i]) : found, []);
|
||||
const other = hits.length === 1 ? cells[hits[0]] : null;
|
||||
const hits = cells.filter((cell, i) => i !== index && cell && overlaps([box(cell)], moved.x, moved.y, moved.w, moved.h));
|
||||
if (hits.length === 0)
|
||||
return items;
|
||||
if (hits.every(cell => cell.row >= origin.row + origin.rows)) {
|
||||
const row = hits.reduce((bottom, cell) => Math.max(bottom, cell.row + cell.rows), moved.y);
|
||||
return items.map((item, i) => i === index ? Object.assign({}, item, {
|
||||
"row": row
|
||||
}) : item);
|
||||
}
|
||||
const other = hits.length === 1 ? hits[0] : null;
|
||||
const covered = other && other.col >= moved.x && other.row >= moved.y && other.col + other.cols <= moved.x + moved.w && other.row + other.rows <= moved.y + moved.h;
|
||||
if (!covered || other.cols > origin.cols || other.rows > origin.rows)
|
||||
return items;
|
||||
return items.map((item, i) => i === hits[0] ? Object.assign({}, item, {
|
||||
return items.map((item, i) => cells[i] === other ? Object.assign({}, item, {
|
||||
"col": origin.col,
|
||||
"row": origin.row
|
||||
}) : item);
|
||||
|
||||
@@ -21,7 +21,7 @@ Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("SettingsData")
|
||||
|
||||
readonly property int settingsConfigVersion: 36
|
||||
readonly property int settingsConfigVersion: 37
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
|
||||
@@ -544,6 +544,15 @@ var LOCAL_SPEC = {
|
||||
w: 4,
|
||||
h: 1,
|
||||
footer: true
|
||||
},
|
||||
{
|
||||
id: "edit",
|
||||
enabled: true,
|
||||
w: 1,
|
||||
h: 1,
|
||||
small: true,
|
||||
footer: true,
|
||||
footerEnd: true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -707,6 +707,12 @@ function migrateToVersion(obj, targetVersion) {
|
||||
settings.configVersion = 35;
|
||||
}
|
||||
|
||||
if (currentVersion < 37 && targetVersion >= 37) {
|
||||
if (Array.isArray(settings.controlCenterWidgets) && !settings.controlCenterWidgets.some(widget => widget?.id === "edit"))
|
||||
settings.controlCenterWidgets = settings.controlCenterWidgets.concat([Object.assign({}, SpecModule.SPEC.controlCenterWidgets.def.find(widget => widget.id === "edit"))]);
|
||||
settings.configVersion = 37;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ DankGridEditChrome {
|
||||
signal configRequested(var anchor)
|
||||
|
||||
hasOptions: WidgetUtils.hasOptions(widgetData.id)
|
||||
removable: WidgetUtils.isRemovable(widgetData)
|
||||
buttonSize: Theme.iconSize
|
||||
hitOverflow: CcMetrics.gridGap / 2
|
||||
iconSize: PopoutMetrics.chromeIconSize
|
||||
|
||||
@@ -34,38 +34,43 @@ Item {
|
||||
property var gridSlot: null
|
||||
property var resizePreview: null
|
||||
property Item flyingItem: null
|
||||
property bool incomingRemovable: true
|
||||
readonly property bool dragging: gridDragging || liftedIndex >= 0
|
||||
readonly property bool dragRemovable: liftedIndex >= 0 ? WidgetUtils.isRemovable(items.find(item => item.index === liftedIndex)?.widget) : incomingRemovable
|
||||
readonly property bool overTrash: trashContains(liftedIndex >= 0 ? liftedPoint : gridDragPoint)
|
||||
|
||||
signal addWidgetRequested
|
||||
signal resetRequested
|
||||
signal clearRequested
|
||||
signal moveRequested
|
||||
signal editToggled
|
||||
signal finishRequested
|
||||
signal cancelRequested
|
||||
signal removeRequested(int index)
|
||||
signal configRequested(int index, var widgetData, var anchor)
|
||||
signal itemMoved(int index, rect sceneRect, bool leaving)
|
||||
signal resized(int index, int cells, bool fill)
|
||||
signal resized(int index, real cells, bool fill)
|
||||
|
||||
// Grid columns, so footer widths line up with the tiles above.
|
||||
readonly property real spacing: CcMetrics.gridGap
|
||||
readonly property real pitch: grid.cellWidth
|
||||
readonly property real editActionsWidth: Theme.iconButtonSize * 2 + spacing
|
||||
readonly property real trackWidth: Math.max(0, width - trailing.width - CcMetrics.footerGap)
|
||||
// Edit mode shows the whole row scaled into the room beside the edit buttons, so every width stays reachable.
|
||||
readonly property real editScale: trackWidth > 0 ? Math.min(1, (trackWidth - editActionsWidth - CcMetrics.footerGap) / trackWidth) : 1
|
||||
readonly property real editActionsWidth: Theme.iconButtonSize * 3 + spacing * 2
|
||||
// The row owns its full width in both modes, so capacity never changes under the user's feet; edit mode
|
||||
// shows the whole row scaled into the room beside the edit buttons, so every width stays reachable.
|
||||
readonly property real trackWidth: width
|
||||
readonly property real editScale: width > 0 ? Math.min(1, (width - editActionsWidth - CcMetrics.footerGap) / width) : 1
|
||||
readonly property real trackScale: editMode ? editScale : 1
|
||||
readonly property int capacity: Math.max(0, Math.floor((trackWidth + spacing) / pitch))
|
||||
readonly property real step: CcMetrics.gridStep
|
||||
readonly property real capacity: Math.max(0, Math.floor((trackWidth + spacing) / pitch / step + 1e-6) * step)
|
||||
readonly property var fills: items.map(item => resizePreview?.index === item.index ? resizePreview.fill : WidgetUtils.footerFills(item.widget))
|
||||
readonly property var mins: items.map(item => WidgetUtils.footerMinCells(item.widget.id))
|
||||
readonly property var ends: items.map(item => WidgetUtils.footerEnds(item.widget))
|
||||
readonly property var keeps: items.map(item => !WidgetUtils.isRemovable(item.widget))
|
||||
// Fill items count at their minimum, so a drop or a neighbour growing takes room from them first.
|
||||
readonly property var fixedCells: WidgetUtils.fitFooterCells(items.map((item, i) => fills[i] ? mins[i] : (resizePreview?.index === item.index ? resizePreview.cells : WidgetUtils.footerCells(item.widget))), mins, capacity)
|
||||
readonly property int fixedUsed: fixedCells.reduce((sum, count) => sum + count, 0)
|
||||
readonly property var cells: WidgetUtils.spreadFooterFill(fixedCells, fills, capacity - fixedUsed - (incoming?.cells ?? 0))
|
||||
readonly property var fixedCells: WidgetUtils.fitFooterCells(items.map((item, i) => fills[i] ? mins[i] : (resizePreview?.index === item.index ? resizePreview.cells : WidgetUtils.footerCells(item.widget))), mins, capacity, keeps, step)
|
||||
readonly property real fixedUsed: fixedCells.reduce((sum, count) => sum + count, 0)
|
||||
readonly property var cells: WidgetUtils.spreadFooterFill(fixedCells, fills, capacity - fixedUsed - (incoming?.cells ?? 0), step)
|
||||
readonly property int slackIndex: fills.findIndex((fill, i) => fill && cells[i] > 0)
|
||||
// The first fill item also takes the part of a cell left over, so it ends exactly at the next button.
|
||||
// The first fill item also takes the part of a cell left over, so it ends exactly at the next item or the edge.
|
||||
readonly property real fillSlack: slackIndex < 0 ? 0 : Math.max(0, trackWidth + spacing - (cells.reduce((sum, count) => sum + count, 0) + (incoming?.cells ?? 0)) * pitch)
|
||||
// Stable keys keep delegates alive across resizes and reorders, so they update in place instead of rebuilding.
|
||||
readonly property var keys: {
|
||||
@@ -109,7 +114,7 @@ Item {
|
||||
}
|
||||
|
||||
function trashContains(scenePosition) {
|
||||
if (!editMode || !dragging)
|
||||
if (!editMode || !dragging || !dragRemovable)
|
||||
return false;
|
||||
return trash.contains(trash.mapFromItem(null, scenePosition.x, scenePosition.y));
|
||||
}
|
||||
@@ -275,7 +280,7 @@ Item {
|
||||
"index": -1,
|
||||
"widget": ({})
|
||||
})
|
||||
readonly property int cells: root.cells[index] ?? 0
|
||||
readonly property real cells: root.cells[index] ?? 0
|
||||
readonly property bool lifted: root.liftedIndex === entry.index
|
||||
readonly property real span: root.spanWidth(Math.max(1, cells)) + (index === root.slackIndex ? root.fillSlack : 0)
|
||||
readonly property real restX: root.xFor(root.layout.offsets[index] ?? 0, span)
|
||||
@@ -337,7 +342,7 @@ Item {
|
||||
id: chrome
|
||||
|
||||
property real startX: 0
|
||||
property int startCells: 0
|
||||
property real startCells: 0
|
||||
property bool startFill: false
|
||||
|
||||
anchors.fill: parent
|
||||
@@ -361,12 +366,12 @@ Item {
|
||||
"fill": startFill
|
||||
};
|
||||
}
|
||||
// Pulling past the edit buttons turns the item into a fill, which runs on to the pencil outside edit mode.
|
||||
// Pulling past the edit buttons turns the item into a fill, which runs on to the row's edge outside edit mode.
|
||||
onResizeMoved: (px, py) => {
|
||||
const delta = (mapToItem(null, px, py).x - startX) * (I18n.isRtl ? -1 : 1) / root.trackScale;
|
||||
const min = root.mins[footerItem.index];
|
||||
const max = Math.max(min, root.capacity - (root.fixedUsed - root.fixedCells[footerItem.index]));
|
||||
const wanted = Math.round(startCells + delta / root.pitch);
|
||||
const wanted = Math.round((startCells + delta / root.pitch) / root.step) * root.step;
|
||||
root.resizePreview = {
|
||||
"index": footerItem.entry.index,
|
||||
"cells": Math.max(min, Math.min(max, wanted)),
|
||||
@@ -420,65 +425,85 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: trailing.left
|
||||
anchors.rightMargin: CcMetrics.footerGap
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: root.spacing
|
||||
visible: root.editMode && !root.dragging
|
||||
Item {
|
||||
id: editActions
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: Theme.iconButtonSize
|
||||
iconName: "add"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: Theme.onSecondaryContainer
|
||||
backgroundColor: Theme.secondaryContainer
|
||||
tooltipText: I18n.tr("Add widget")
|
||||
onClicked: root.addWidgetRequested()
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: moreButton
|
||||
|
||||
buttonSize: Theme.iconButtonSize
|
||||
iconName: "more_horiz"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: CcMetrics.tileInactiveContent
|
||||
backgroundColor: CcMetrics.tileInactiveColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
tooltipText: I18n.tr("More")
|
||||
onClicked: editMenu.openAt(moreButton)
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: trash
|
||||
|
||||
anchors.right: trailing.left
|
||||
anchors.rightMargin: CcMetrics.footerGap
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: root.editActionsWidth
|
||||
height: CcMetrics.footerHeight
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: root.overTrash ? Theme.errorContainer : CcMetrics.tileInactiveColor
|
||||
visible: root.editMode && root.dragging
|
||||
Accessible.role: Accessible.Graphic
|
||||
Accessible.name: I18n.tr("Remove")
|
||||
visible: root.editMode
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: root.spacing
|
||||
visible: !root.dragging
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: Theme.iconButtonSize
|
||||
iconName: "add"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: Theme.onSecondaryContainer
|
||||
backgroundColor: Theme.secondaryContainer
|
||||
tooltipText: I18n.tr("Add widget")
|
||||
onClicked: root.addWidgetRequested()
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: moreButton
|
||||
|
||||
buttonSize: Theme.iconButtonSize
|
||||
iconName: "more_horiz"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: CcMetrics.tileInactiveContent
|
||||
backgroundColor: CcMetrics.tileInactiveColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
tooltipText: I18n.tr("More")
|
||||
onClicked: editMenu.openAt(moreButton)
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "delete"
|
||||
size: CcMetrics.iconBoxIconSize
|
||||
filled: root.overTrash
|
||||
color: root.overTrash ? Theme.onErrorContainer : CcMetrics.tileInactiveContent
|
||||
StyledRect {
|
||||
id: trash
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: root.editActionsWidth - Theme.iconButtonSize - root.spacing
|
||||
height: CcMetrics.footerHeight
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: root.overTrash ? Theme.errorContainer : CcMetrics.tileInactiveColor
|
||||
visible: root.dragging && root.dragRemovable
|
||||
Accessible.role: Accessible.Graphic
|
||||
Accessible.name: I18n.tr("Remove")
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "delete"
|
||||
size: CcMetrics.iconBoxIconSize
|
||||
filled: root.overTrash
|
||||
color: root.overTrash ? Theme.onErrorContainer : CcMetrics.tileInactiveContent
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
buttonSize: Theme.iconButtonSize
|
||||
iconName: "check"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: Theme.onPrimary
|
||||
backgroundColor: Theme.primary
|
||||
tooltipText: I18n.tr("Save")
|
||||
onClicked: root.finishRequested()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,7 +514,7 @@ Item {
|
||||
items: [
|
||||
{
|
||||
"iconName": root.onTop ? "vertical_align_bottom" : "vertical_align_top",
|
||||
"label": root.onTop ? I18n.tr("Move down") : I18n.tr("Move up"),
|
||||
"label": root.onTop ? I18n.tr("Move row to footer") : I18n.tr("Move row to header"),
|
||||
"action": () => root.moveRequested()
|
||||
},
|
||||
{
|
||||
@@ -511,42 +536,6 @@ Item {
|
||||
]
|
||||
}
|
||||
|
||||
Item {
|
||||
id: trailing
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
// One column, the same pill as a small button docked beside it.
|
||||
width: root.spanWidth(1)
|
||||
height: CcMetrics.footerHeight
|
||||
|
||||
DankActionButton {
|
||||
anchors.fill: parent
|
||||
buttonSize: CcMetrics.footerHeight
|
||||
iconName: "check"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: Theme.onPrimary
|
||||
backgroundColor: Theme.primary
|
||||
tooltipText: I18n.tr("Save")
|
||||
visible: root.editMode
|
||||
onClicked: root.editToggled()
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
anchors.fill: parent
|
||||
buttonSize: CcMetrics.footerHeight
|
||||
iconName: "edit"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: CcMetrics.tileInactiveContent
|
||||
backgroundColor: CcMetrics.tileInactiveColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
tooltipText: I18n.tr("Edit")
|
||||
visible: !root.editMode
|
||||
onClicked: root.editToggled()
|
||||
}
|
||||
}
|
||||
|
||||
// The track's layer only draws inside the row, so a lifted item is shown from here while it travels.
|
||||
ShaderEffectSource {
|
||||
readonly property real pad: PopoutMetrics.editOverflow
|
||||
|
||||
@@ -24,6 +24,7 @@ DankEditableGrid {
|
||||
signal lockRequested
|
||||
signal powerRequested
|
||||
signal settingsRequested
|
||||
signal editRequested
|
||||
signal accountsRequested
|
||||
signal closeRequested
|
||||
property bool tapToClose: false
|
||||
|
||||
@@ -88,7 +88,7 @@ DankEditableGridSlot {
|
||||
dragging: root.dragging
|
||||
resizing: root.resizing
|
||||
cornerRadius: root.small ? Theme.fullRadius(root.width, root.height) : (root.tileItem?.bodyRadius ?? Theme.fullRadius(root.width, root.height))
|
||||
sizeText: root.cols + "×" + root.rows
|
||||
sizeText: root.cols + "×" + (root.small ? CcMetrics.smallRowFraction : root.rows)
|
||||
onResizeStarted: (px, py) => {
|
||||
root.biasH = root.small ? (CcMetrics.smallRowFraction - 1) * root.grid.cellWidth : 0;
|
||||
root.beginResize(px, py);
|
||||
|
||||
@@ -74,6 +74,9 @@ FocusScope {
|
||||
implicitHeight: targetImplicitHeight
|
||||
focus: true
|
||||
|
||||
Component.onCompleted: WidgetUtils.ensureEditButton()
|
||||
onPlacedWidgetIdsChanged: WidgetUtils.ensureEditButton()
|
||||
|
||||
function navigateTo(section) {
|
||||
if (section === host.expandedSection)
|
||||
return;
|
||||
@@ -279,6 +282,13 @@ FocusScope {
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["F2", "Ctrl+E"]
|
||||
enabled: root.host.shouldBeVisible && !root.host.editMode && !root.pageOpen
|
||||
context: Qt.WindowShortcut
|
||||
onActivated: root.host.editMode = true
|
||||
}
|
||||
|
||||
readonly property string expandedSection: host.expandedSection ?? ""
|
||||
readonly property bool editMode: host.editMode
|
||||
|
||||
@@ -416,6 +426,7 @@ FocusScope {
|
||||
onColorPickerRequested: root.host.openColorPicker()
|
||||
onCloseRequested: root.host.close()
|
||||
onSettingsRequested: root.host.openSettings()
|
||||
onEditRequested: root.host.editMode = true
|
||||
onAccountsRequested: root.host.openAccounts()
|
||||
onLockRequested: {
|
||||
root.host.close();
|
||||
@@ -449,6 +460,7 @@ FocusScope {
|
||||
items: root.footerItems
|
||||
gridDragging: root.gridDragWidget !== null
|
||||
gridDragPoint: widgetGrid.dragScenePoint
|
||||
incomingRemovable: WidgetUtils.isRemovable(root.gridDragWidget)
|
||||
incoming: root.gridDragWidget !== null && widgetGrid.heldOutside && !footer.overTrash ? Object.assign({
|
||||
"cells": Math.min(WidgetUtils.footerCells(root.gridDragWidget), footer.freeCells())
|
||||
}, footer.gridSlot) : null
|
||||
@@ -463,7 +475,7 @@ FocusScope {
|
||||
onConfigRequested: (index, widgetData, anchor) => root.openConfigOverlay(index, widgetData, anchor)
|
||||
onItemMoved: (index, sceneRect, leaving) => root.previewFooterDrag(index, sceneRect, leaving)
|
||||
onResized: (index, cells, fill) => WidgetUtils.setFooterSize(index, cells, fill)
|
||||
onEditToggled: root.host.editMode = !root.host.editMode
|
||||
onFinishRequested: root.host.editMode = false
|
||||
onCancelRequested: root.cancelEdit()
|
||||
}
|
||||
|
||||
|
||||
@@ -171,6 +171,7 @@ QtObject {
|
||||
case "lock":
|
||||
case "power":
|
||||
case "settings":
|
||||
case "edit":
|
||||
return actionTile;
|
||||
case "runningApps":
|
||||
return runningAppsTile;
|
||||
@@ -228,6 +229,15 @@ QtObject {
|
||||
"category": "system",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "edit",
|
||||
"text": I18n.tr("Edit"),
|
||||
"description": I18n.tr("Edit widgets"),
|
||||
"icon": "edit",
|
||||
"type": "action",
|
||||
"category": "system",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "runningApps",
|
||||
"text": I18n.tr("Running apps"),
|
||||
|
||||
@@ -23,6 +23,9 @@ CcTile {
|
||||
case "settings":
|
||||
host?.settingsRequested();
|
||||
return;
|
||||
case "edit":
|
||||
host?.editRequested();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,12 @@
|
||||
.import qs.Services as Services
|
||||
.import qs.Modules.ControlCenter as ControlCenter
|
||||
.import "../../../Common/GridLayout.js" as GridLayout
|
||||
.import "../../../Common/settings/SettingsSpec.js" as Spec
|
||||
|
||||
var OPTION_IDS = ["diskUsage", "brightnessSlider", "idleInhibitor", "user"];
|
||||
var ACTION_IDS = ["settings", "lock", "power"];
|
||||
var EDIT_ID = "edit";
|
||||
var BUTTON_IDS = ACTION_IDS.concat([EDIT_ID]);
|
||||
var FOOTER_CELLS = {
|
||||
"runningApps": 4,
|
||||
"user": 3,
|
||||
@@ -30,13 +33,31 @@ function inFooter(widget) {
|
||||
return !!widget?.footer;
|
||||
}
|
||||
|
||||
function isRemovable(widget) {
|
||||
return widget?.id !== EDIT_ID;
|
||||
}
|
||||
|
||||
// A saved list without the pencil gets it back whatever state the migration left it in.
|
||||
function ensureEditButton() {
|
||||
const widgets = Common.SettingsData.controlCenterWidgets;
|
||||
if (!Array.isArray(widgets) || widgets.some(widget => widget?.id === EDIT_ID))
|
||||
return;
|
||||
const fallback = Spec.SPEC.controlCenterWidgets.def.find(widget => widget.id === EDIT_ID);
|
||||
Common.SettingsData.set("controlCenterWidgets", widgets.concat([Object.assign({}, fallback)]));
|
||||
}
|
||||
|
||||
function footerMinCells(id) {
|
||||
return isSliderWidget(id) ? 3 : 1;
|
||||
}
|
||||
|
||||
function footerStep(value) {
|
||||
const step = ControlCenter.CcMetrics.gridStep;
|
||||
return Math.round(value / step) * step;
|
||||
}
|
||||
|
||||
// `footerW` outlives the footer flag, so an item dragged back in returns at the width it left with.
|
||||
function footerCells(widget) {
|
||||
return Math.max(footerMinCells(widget?.id), Math.round(Number(widget?.footerW) || FOOTER_CELLS[widget?.id] || 1));
|
||||
return Math.max(footerMinCells(widget?.id), footerStep(Number(widget?.footerW) || FOOTER_CELLS[widget?.id] || 1));
|
||||
}
|
||||
|
||||
function footerFills(widget) {
|
||||
@@ -47,17 +68,19 @@ function footerEnds(widget) {
|
||||
return widget?.footerEnd === true;
|
||||
}
|
||||
|
||||
function fitFooterCells(sizes, mins, capacity) {
|
||||
function fitFooterCells(sizes, mins, capacity, keep, step) {
|
||||
const fitted = sizes.slice();
|
||||
let over = fitted.reduce((sum, cells) => sum + cells, 0) - capacity;
|
||||
while (over > 0) {
|
||||
const widest = fitted.reduce((best, cells, i) => cells > mins[i] && (best < 0 || cells > fitted[best]) ? i : best, -1);
|
||||
if (widest < 0)
|
||||
break;
|
||||
fitted[widest]--;
|
||||
over--;
|
||||
fitted[widest] -= step;
|
||||
over -= step;
|
||||
}
|
||||
for (let i = fitted.length - 1; over > 0 && i >= 0; i--) {
|
||||
if (keep[i])
|
||||
continue;
|
||||
over -= fitted[i];
|
||||
fitted[i] = 0;
|
||||
}
|
||||
@@ -115,7 +138,7 @@ function sizeSpec(widget, columns, rows = Infinity) {
|
||||
};
|
||||
if (widget?.id === "user")
|
||||
spec.w = (Number.isFinite(columns) ? columns : ControlCenter.CcMetrics.defaultColumns) - ACTION_IDS.length;
|
||||
if (ACTION_IDS.includes(widget?.id))
|
||||
if (BUTTON_IDS.includes(widget?.id))
|
||||
spec.w = 1;
|
||||
return spec;
|
||||
}
|
||||
@@ -142,7 +165,7 @@ function clampSize(widget, columns, rows = Infinity) {
|
||||
function addWidget(widgetId, columns) {
|
||||
const widgets = Common.SettingsData.controlCenterWidgets.slice();
|
||||
const widget = defaultWidget(widgetId, columns);
|
||||
if (ACTION_IDS.includes(widgetId))
|
||||
if (BUTTON_IDS.includes(widgetId))
|
||||
widget.small = true;
|
||||
|
||||
if (widgetId === "diskUsage") {
|
||||
@@ -166,7 +189,7 @@ function generateUniqueId() {
|
||||
|
||||
function removeWidget(index) {
|
||||
const widgets = Common.SettingsData.controlCenterWidgets.slice();
|
||||
if (index < 0 || index >= widgets.length)
|
||||
if (index < 0 || index >= widgets.length || !isRemovable(widgets[index]))
|
||||
return;
|
||||
widgets.splice(index, 1);
|
||||
Common.SettingsData.set("controlCenterWidgets", widgets);
|
||||
@@ -204,7 +227,7 @@ function moveToFooter(index, beforeIndex, cells, end) {
|
||||
}
|
||||
|
||||
// Hands spare cells to fill items, the earlier ones taking the remainder.
|
||||
function spreadFooterFill(cells, fills, spare) {
|
||||
function spreadFooterFill(cells, fills, spare, step) {
|
||||
const count = fills.filter(Boolean).length;
|
||||
if (count === 0 || spare <= 0)
|
||||
return cells;
|
||||
@@ -212,7 +235,7 @@ function spreadFooterFill(cells, fills, spare) {
|
||||
return cells.map((size, i) => {
|
||||
if (!fills[i] || size === 0)
|
||||
return size;
|
||||
const share = Math.ceil(left / count);
|
||||
const share = Math.ceil(left / count / step) * step;
|
||||
left -= share;
|
||||
return size + Math.max(0, share);
|
||||
});
|
||||
@@ -254,5 +277,5 @@ function resetToDefault() {
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
Common.SettingsData.set("controlCenterWidgets", []);
|
||||
Common.SettingsData.set("controlCenterWidgets", Common.SettingsData.controlCenterWidgets.filter(widget => !isRemovable(widget)));
|
||||
}
|
||||
@@ -54,7 +54,7 @@ Item {
|
||||
if (interactingIndex < 0 || !changes)
|
||||
return items;
|
||||
const changed = items.map((item, i) => i === interactingIndex ? Object.assign({}, item, changes) : item);
|
||||
return swapDrags && dragCell && draggingSourceIndex >= 0 ? GridUtils.swapInto(changed, pinnedCells, draggingSourceIndex, dragCell) : changed;
|
||||
return swapDrags && dragCell && draggingSourceIndex >= 0 ? GridUtils.dropInto(changed, pinnedCells, draggingSourceIndex, dragCell) : changed;
|
||||
}
|
||||
|
||||
signal layoutCommitted(var items)
|
||||
|
||||
@@ -80,3 +80,19 @@ test("panel step follows the pointer and holds on a pinned edge", () => {
|
||||
assert.equal(grid.nearestStep(rightEdge, 8, 6, 30, rightEdge(8) + 50), 9);
|
||||
assert.equal(grid.nearestStep(rightEdge, 20, 6, 30, 1400), 20);
|
||||
});
|
||||
|
||||
test("a tile dragged down lands past the tile it covers instead of back above it", () => {
|
||||
const items = [{ id: "a" }, { id: "b" }, { id: "c" }];
|
||||
const cells = [{ col: 0, row: 0, cols: 1, rows: 1 }, { col: 0, row: 1, cols: 1, rows: 1 }, { col: 0, row: 2, cols: 1, rows: 2 }];
|
||||
assert.deepEqual(plain(grid.dropInto(items, cells, 0, { col: 0, row: 1 })), [{ id: "a", row: 2 }, { id: "b" }, { id: "c" }]);
|
||||
assert.deepEqual(plain(grid.dropInto(items, cells, 0, { col: 0, row: 2 })), [{ id: "a", row: 4 }, { id: "b" }, { id: "c" }]);
|
||||
assert.deepEqual(plain(grid.dropInto(items, cells, 2, { col: 0, row: 0 })), items);
|
||||
assert.deepEqual(plain(grid.dropInto(items, cells, 1, { col: 0, row: 2 })), [{ id: "a" }, { id: "b", row: 4 }, { id: "c" }]);
|
||||
});
|
||||
|
||||
test("a tile dropped sideways or up onto a same-size tile trades places with it", () => {
|
||||
const items = [{ id: "a" }, { id: "b" }, { id: "c" }];
|
||||
const cells = [{ col: 0, row: 0, cols: 1, rows: 1 }, { col: 1, row: 0, cols: 1, rows: 1 }, { col: 1, row: 1, cols: 1, rows: 1 }];
|
||||
assert.deepEqual(plain(grid.dropInto(items, cells, 0, { col: 1, row: 0 })), [{ id: "a" }, { id: "b", col: 0, row: 0 }, { id: "c" }]);
|
||||
assert.deepEqual(plain(grid.dropInto(items, cells, 2, { col: 1, row: 0 })), [{ id: "a" }, { id: "b", col: 1, row: 1 }, { id: "c" }]);
|
||||
});
|
||||
@@ -30,3 +30,12 @@ test("header entries become a user row and lock, power and settings tiles", () =
|
||||
const released = store.migrateToVersion({ configVersion: 18, controlCenterWidgets: [{ id: "wifi", width: 50 }] }, 35);
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(released.controlCenterWidgets.map(w => [w.id, w.w, w.h, !!w.footer]))), [["user", 5, 1, false], ["settings", 1, 1, false], ["lock", 1, 1, false], ["power", 1, 1, false], ["wifi", 4, 1, false], ["runningApps", 4, 1, true]]);
|
||||
});
|
||||
|
||||
test("the edit button joins the footer end once and keeps a placed one", () => {
|
||||
const migrated = store.migrateToVersion({ configVersion: 36, controlCenterWidgets: [{ id: "wifi", w: 4, h: 1 }] }, 37);
|
||||
assert.equal(migrated.configVersion, 37);
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(migrated.controlCenterWidgets)), [{ id: "wifi", w: 4, h: 1 }, { id: "edit", enabled: true, w: 1, h: 1, small: true, footer: true, footerEnd: true }]);
|
||||
const placed = store.migrateToVersion({ configVersion: 36, controlCenterWidgets: [{ id: "edit", w: 1, h: 1, col: 2, row: 0 }] }, 37);
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(placed.controlCenterWidgets)), [{ id: "edit", w: 1, h: 1, col: 2, row: 0 }]);
|
||||
assert.equal(store.migrateToVersion({ configVersion: 36 }, 37).controlCenterWidgets, undefined);
|
||||
});
|
||||
Reference in new issue
Block a user