mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-18 09:14:01 -04:00
cc: implement undo functionality ctrl+z & drop rings w/misc qol
This commit is contained in:
@@ -57,6 +57,10 @@ Singleton {
|
||||
readonly property real tileTextGap: Theme.spacingM
|
||||
readonly property real footerHeight: iconBoxSize
|
||||
readonly property real footerGap: Theme.spacingS
|
||||
// Half the gap, so the grid and row rings meet on one line instead of drawing two.
|
||||
readonly property real dropZoneInset: gridGap / 2
|
||||
readonly property real dropZoneOuterInset: Theme.spacingS
|
||||
readonly property real dropZoneRadius: Theme.cornerRadiusL
|
||||
readonly property real runningAppsIconSize: Theme.iconSize * iconScale
|
||||
// The resize corner counts a small tile as this many rows and snaps to the nearer of this and a full row.
|
||||
readonly property real smallRowFraction: 0.5
|
||||
|
||||
@@ -39,7 +39,10 @@ Item {
|
||||
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)
|
||||
|
||||
property bool canUndo: false
|
||||
|
||||
signal addWidgetRequested
|
||||
signal undoRequested
|
||||
signal resetRequested
|
||||
signal clearRequested
|
||||
signal moveRequested
|
||||
@@ -217,6 +220,29 @@ Item {
|
||||
};
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
readonly property real inset: CcMetrics.dropZoneInset
|
||||
readonly property bool receiving: root.incoming !== null || (root.liftedIndex >= 0 && root.liftedSlot !== null)
|
||||
|
||||
x: (I18n.isRtl !== root.editAtEnd ? root.trackWidth * (1 - trackScale.xScale) : 0) - inset
|
||||
y: -inset
|
||||
width: root.trackWidth * trackScale.xScale + inset * 2
|
||||
height: CcMetrics.footerHeight + inset * 2
|
||||
radius: CcMetrics.dropZoneRadius
|
||||
color: "transparent"
|
||||
border.width: Theme.outlineWidth
|
||||
border.color: receiving ? Theme.primary : Theme.outlineVariant
|
||||
visible: root.editMode
|
||||
|
||||
Behavior on border.color {
|
||||
enabled: CcMetrics.animationsEnabled
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: track
|
||||
|
||||
@@ -435,8 +461,8 @@ Item {
|
||||
Item {
|
||||
id: editActions
|
||||
|
||||
anchors.left: root.editAtEnd ? parent.left : undefined
|
||||
anchors.right: root.editAtEnd ? undefined : parent.right
|
||||
// A side anchor set to undefined is never released, so flipping sides stretched the block across the row.
|
||||
x: root.editAtEnd !== I18n.isRtl ? 0 : root.width - width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: root.editActionsWidth
|
||||
height: CcMetrics.footerHeight
|
||||
@@ -464,10 +490,8 @@ Item {
|
||||
buttonSize: Theme.iconButtonSize
|
||||
iconName: "more_horiz"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: CcMetrics.tileInactiveContent
|
||||
backgroundColor: CcMetrics.tileInactiveColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
iconColor: Theme.onSecondaryContainer
|
||||
backgroundColor: Theme.secondaryContainer
|
||||
tooltipText: I18n.tr("More")
|
||||
onClicked: editMenu.openAt(moreButton)
|
||||
}
|
||||
@@ -476,12 +500,11 @@ Item {
|
||||
StyledRect {
|
||||
id: trash
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: root.editActionsWidth - Theme.iconButtonSize - root.spacing
|
||||
height: CcMetrics.footerHeight
|
||||
anchors.fill: parent
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: root.overTrash ? Theme.errorContainer : CcMetrics.tileInactiveColor
|
||||
border.width: Theme.outlineWidth
|
||||
border.color: root.overTrash ? Theme.error : Theme.outlineVariant
|
||||
visible: root.dragging && root.dragRemovable
|
||||
Accessible.role: Accessible.Graphic
|
||||
Accessible.name: I18n.tr("Remove")
|
||||
@@ -508,6 +531,12 @@ Item {
|
||||
|
||||
transientSurfaceTracker: root.transientSurfaceTracker
|
||||
items: [
|
||||
{
|
||||
"iconName": "undo",
|
||||
"label": I18n.tr("Undo"),
|
||||
"enabled": root.canUndo,
|
||||
"action": () => root.undoRequested()
|
||||
},
|
||||
{
|
||||
"iconName": root.onTop ? "vertical_align_bottom" : "vertical_align_top",
|
||||
"label": root.onTop ? I18n.tr("Move row to footer", "control center edit menu, moves the row from the header to the footer") : I18n.tr("Move row to header", "control center edit menu, moves the row from the footer to the header"),
|
||||
|
||||
@@ -29,6 +29,8 @@ DEditableGrid {
|
||||
signal closeRequested
|
||||
property bool tapToClose: false
|
||||
property var runningToplevels: []
|
||||
// A member drag inside a quick-tiles group never reaches the grid's own interaction state.
|
||||
property bool memberDragging: false
|
||||
|
||||
readonly property real gridHeight: layoutHeight
|
||||
readonly property real cellWidth: (width + CcMetrics.gridGap) / columns
|
||||
@@ -47,6 +49,10 @@ DEditableGrid {
|
||||
placeholderRadius: draggingSlot?.small ? Theme.fullRadius(draggingSlot.width, draggingSlot.height) : (draggingSlot?.tileItem?.bodyRadius ?? Theme.fullRadius(width, CcMetrics.tileHeight))
|
||||
|
||||
commitHandler: items => model.setLayout(withHidden(items))
|
||||
onEditModeChanged: {
|
||||
if (!editMode)
|
||||
memberDragging = false;
|
||||
}
|
||||
|
||||
function clamped(items) {
|
||||
return items.map(widget => Object.assign({}, widget, WidgetUtils.clampSize(widget, columns, maximumRows)));
|
||||
|
||||
@@ -71,9 +71,15 @@ Item {
|
||||
return others()[slot]?.modelData.index ?? -1;
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (liftedIndex >= 0)
|
||||
host.memberDragging = false;
|
||||
}
|
||||
|
||||
function lift(member, scenePoint) {
|
||||
liftedWidth = member.ownWidth;
|
||||
liftedIndex = member.modelData.index;
|
||||
host.memberDragging = true;
|
||||
moveLifted(member, scenePoint);
|
||||
}
|
||||
|
||||
@@ -103,6 +109,7 @@ Item {
|
||||
const unchanged = before === (members[position + 1]?.index ?? -1);
|
||||
liftedIndex = -1;
|
||||
dropSlot = -1;
|
||||
host.memberDragging = false;
|
||||
if (slot >= 0) {
|
||||
host.clearExternal();
|
||||
if (!unchanged)
|
||||
|
||||
@@ -136,6 +136,22 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
CcListRow {
|
||||
visible: root.batteryIndex >= 0 && (SettingsData.controlCenterWidgets[root.batteryIndex]?.batteryStyle ?? "icon") !== "icon"
|
||||
iconName: "palette"
|
||||
title: I18n.tr("Color")
|
||||
body: DDropdown {
|
||||
readonly property var labels: [I18n.tr("Theme", "battery settings: theme accent indicator colors"), I18n.tr("Level", "battery settings: charge level indicator colors")]
|
||||
|
||||
compactMode: true
|
||||
dropdownWidth: parent.width
|
||||
transientSurfaceTracker: contextMenu.transientSurfaceTracker
|
||||
currentValue: labels[(SettingsData.controlCenterWidgets[root.batteryIndex]?.batteryColorMode ?? "level") === "level" ? 1 : 0]
|
||||
options: labels
|
||||
onValueChanged: value => WidgetUtils.setOption(root.batteryIndex, "batteryColorMode", labels.indexOf(value) === 1 ? "level" : "theme")
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.isUser ? DashRegistry.sheetOptionSpecs("user") : []
|
||||
|
||||
|
||||
@@ -55,7 +55,19 @@ FocusScope {
|
||||
}
|
||||
property Item detailReturnFocus: null
|
||||
property var pageHistory: []
|
||||
property var editSnapshot: null
|
||||
// One string so Discard and undo can compare and restore the whole session state at once.
|
||||
readonly property string editState: JSON.stringify({
|
||||
"widgets": SettingsData.controlCenterWidgets || [],
|
||||
"columns": SettingsData.controlCenterColumns,
|
||||
"footerPosition": SettingsData.controlCenterFooterPosition
|
||||
})
|
||||
property string editSnapshot: ""
|
||||
property string undoBase: ""
|
||||
property var undoStack: []
|
||||
readonly property int undoLimit: 50
|
||||
property bool undoBatching: false
|
||||
readonly property bool editIdle: !widgetGrid.interacting && !widgetGrid.memberDragging && footer.liftedIndex < 0 && footer.resizePreview === null && !panelResizing
|
||||
readonly property bool canUndo: host.editMode && undoStack.length > 0 && editIdle && !widgetSheetOpen && !pageOpen
|
||||
readonly property bool panelResizing: panelResizer.resizing
|
||||
readonly property real sheetContentWidth: host.sheetContentWidth ?? CcMetrics.sheetWidthFor(gridColumns)
|
||||
readonly property vector4d surfaceCornerRadii: host.surfaceCornerRadii ?? Qt.vector4d(Theme.windowRadius, Theme.windowRadius, Theme.windowRadius, Theme.windowRadius)
|
||||
@@ -172,22 +184,58 @@ FocusScope {
|
||||
configOverlayLoader.active = false;
|
||||
}
|
||||
|
||||
function applyEditState(state) {
|
||||
if (state === "")
|
||||
return;
|
||||
const wanted = JSON.parse(state);
|
||||
if (JSON.stringify(SettingsData.controlCenterWidgets || []) !== JSON.stringify(wanted.widgets))
|
||||
SettingsData.set("controlCenterWidgets", wanted.widgets);
|
||||
if (SettingsData.controlCenterColumns !== wanted.columns)
|
||||
SettingsData.set("controlCenterColumns", wanted.columns);
|
||||
if (SettingsData.controlCenterFooterPosition !== wanted.footerPosition)
|
||||
SettingsData.set("controlCenterFooterPosition", wanted.footerPosition);
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
const snapshot = editSnapshot;
|
||||
host.editMode = false;
|
||||
if (!snapshot)
|
||||
return;
|
||||
if (JSON.stringify(SettingsData.controlCenterWidgets) !== snapshot.widgets)
|
||||
SettingsData.set("controlCenterWidgets", JSON.parse(snapshot.widgets));
|
||||
if (SettingsData.controlCenterColumns !== snapshot.columns)
|
||||
SettingsData.set("controlCenterColumns", snapshot.columns);
|
||||
if (SettingsData.controlCenterFooterPosition !== snapshot.footerPosition)
|
||||
SettingsData.set("controlCenterFooterPosition", snapshot.footerPosition);
|
||||
if (snapshot !== "")
|
||||
applyEditState(snapshot);
|
||||
}
|
||||
|
||||
// Goes by the dragged tile, not the pointer: the middle of its top row has to cross the grid's edge as it
|
||||
// was at drag start, which the grid growing under the drag cannot move. Below the grid that edge sits one
|
||||
// row lower while the panel has room to grow, so a tile can still be dropped into a new bottom row.
|
||||
function undoEdit() {
|
||||
if (!canUndo)
|
||||
return;
|
||||
const previous = undoStack[undoStack.length - 1];
|
||||
undoStack = undoStack.slice(0, -1);
|
||||
undoBase = previous;
|
||||
undoBatching = true;
|
||||
applyEditState(previous);
|
||||
undoBatching = false;
|
||||
}
|
||||
|
||||
onEditStateChanged: {
|
||||
if (!host.editMode || undoBatching || editState === undoBase)
|
||||
return;
|
||||
undoStack = undoStack.concat([undoBase]).slice(-undoLimit);
|
||||
undoBase = editState;
|
||||
}
|
||||
|
||||
// Keeps the intermediate states of a multi-key write out of the undo stack.
|
||||
function batchEdit(change) {
|
||||
const before = editState;
|
||||
undoBatching = true;
|
||||
change();
|
||||
undoBatching = false;
|
||||
if (editState === before)
|
||||
return;
|
||||
undoStack = undoStack.concat([before]).slice(-undoLimit);
|
||||
undoBase = editState;
|
||||
}
|
||||
|
||||
// Goes by the dragged tile, not the pointer, against the grid edge as it was at drag start so the grid growing
|
||||
// under the drag cannot move it. Below the grid the edge sits at the middle of the row that opens while the
|
||||
// panel has room: the upper half still drops there, past it the footer takes the tile instead of running ahead.
|
||||
function footerTakesGridDrag(tile) {
|
||||
if (gridDragWidget === null || gridDragWidget.id === WidgetUtils.GROUP_ID || footer.freeCells() < WidgetUtils.footerMinCells(gridDragWidget.id))
|
||||
return false;
|
||||
@@ -197,7 +245,7 @@ FocusScope {
|
||||
return anchor < -CcMetrics.gridGap / 2;
|
||||
const newRow = CcMetrics.gridGap + rowUnit;
|
||||
const room = widgetGrid.pinnedHeight + newRow <= availableGridHeight ? newRow : 0;
|
||||
return anchor > widgetGrid.pinnedHeight + room + CcMetrics.gridGap / 2;
|
||||
return anchor > widgetGrid.pinnedHeight + room / 2 + CcMetrics.gridGap / 2;
|
||||
}
|
||||
|
||||
function groupTakesGridDrag(scenePoint) {
|
||||
@@ -331,6 +379,13 @@ FocusScope {
|
||||
onActivated: root.host.editMode = true
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Ctrl+Z"]
|
||||
enabled: root.host.shouldBeVisible && root.canUndo
|
||||
context: Qt.WindowShortcut
|
||||
onActivated: root.undoEdit()
|
||||
}
|
||||
|
||||
readonly property string expandedSection: host.expandedSection ?? ""
|
||||
readonly property bool editMode: host.editMode
|
||||
|
||||
@@ -343,16 +398,15 @@ FocusScope {
|
||||
onEditModeChanged: {
|
||||
if (editMode) {
|
||||
host.collapseAll();
|
||||
editSnapshot = {
|
||||
"widgets": JSON.stringify(SettingsData.controlCenterWidgets),
|
||||
"columns": SettingsData.controlCenterColumns,
|
||||
"footerPosition": SettingsData.controlCenterFooterPosition
|
||||
};
|
||||
editSnapshot = editState;
|
||||
undoBase = editState;
|
||||
} else {
|
||||
panelResizer.cancel();
|
||||
editSnapshot = null;
|
||||
editSnapshot = "";
|
||||
undoBase = "";
|
||||
widgetSheetRequested = false;
|
||||
}
|
||||
undoStack = [];
|
||||
if (!activeFocus)
|
||||
forceActiveFocus();
|
||||
}
|
||||
@@ -443,6 +497,28 @@ FocusScope {
|
||||
height: root.gridHeight
|
||||
opacity: CcMetrics.hideCoveredContent ? 1 - root.coveredAmount : 1
|
||||
|
||||
Rectangle {
|
||||
readonly property bool receiving: widgetGrid.externalItem !== null || (widgetGrid.draggingSourceIndex >= 0 && !widgetGrid.heldOutside)
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: -CcMetrics.dropZoneOuterInset
|
||||
anchors.topMargin: root.footerOnTop && root.footerShown ? -CcMetrics.dropZoneInset : -CcMetrics.dropZoneOuterInset
|
||||
anchors.bottomMargin: !root.footerOnTop && root.footerShown ? -CcMetrics.dropZoneInset : -CcMetrics.dropZoneOuterInset
|
||||
radius: CcMetrics.dropZoneRadius
|
||||
color: "transparent"
|
||||
border.width: Theme.outlineWidth
|
||||
border.color: receiving ? Theme.primary : Theme.outlineVariant
|
||||
visible: root.host.editMode
|
||||
|
||||
Behavior on border.color {
|
||||
enabled: CcMetrics.animationsEnabled
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CcTileGrid {
|
||||
id: widgetGrid
|
||||
columns: root.gridColumns
|
||||
@@ -507,8 +583,10 @@ FocusScope {
|
||||
dropHandler: (index, cells, scenePoint) => root.dropFromFooter(index, scenePoint)
|
||||
leavesRow: visual => root.gridTakesFooterDrag(visual)
|
||||
opacity: body.opacity
|
||||
canUndo: root.canUndo
|
||||
onAddWidgetRequested: root.openWidgetSheet()
|
||||
onResetRequested: widgetModel.resetToDefault()
|
||||
onUndoRequested: root.undoEdit()
|
||||
onResetRequested: root.batchEdit(() => widgetModel.resetToDefault())
|
||||
onClearRequested: widgetModel.clearAll()
|
||||
onMoveRequested: SettingsData.set("controlCenterFooterPosition", root.footerOnTop ? "bottom" : "top")
|
||||
onRemoveRequested: index => widgetModel.removeWidget(index)
|
||||
|
||||
@@ -12,6 +12,7 @@ CcTile {
|
||||
readonly property bool available: BatteryService.batteryAvailable
|
||||
readonly property bool profileMode: !available && PowerProfileWatcher.available
|
||||
readonly property string meterStyle: widgetData?.batteryStyle ?? "icon"
|
||||
readonly property bool levelColors: (widgetData?.batteryColorMode ?? "level") === "level"
|
||||
readonly property bool showsMeter: available && meterStyle !== "icon"
|
||||
property real meterWidth: 0
|
||||
|
||||
@@ -60,7 +61,7 @@ CcTile {
|
||||
|
||||
BatteryMeter {
|
||||
meterStyle: root.meterStyle
|
||||
levelColors: true
|
||||
levelColors: root.levelColors
|
||||
thickness: CcMetrics.pillMeterThickness
|
||||
scale: Math.min(1, Math.max(0, root.iconExtent - Theme.spacingXS * 2) / Math.max(1, implicitWidth, implicitHeight))
|
||||
}
|
||||
@@ -75,7 +76,7 @@ CcTile {
|
||||
|
||||
anchors.centerIn: parent
|
||||
meterStyle: root.meterStyle
|
||||
levelColors: true
|
||||
levelColors: root.levelColors
|
||||
thickness: CcMetrics.pillMeterThickness
|
||||
scale: Math.min(1, (parent.width - Theme.spacingXS * 2) / Math.max(1, implicitWidth))
|
||||
}
|
||||
@@ -95,7 +96,7 @@ CcTile {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
thickness: CcMetrics.tallMeterThickness
|
||||
meterStyle: root.showsMeter ? root.meterStyle : "solid"
|
||||
levelColors: true
|
||||
levelColors: root.levelColors
|
||||
showNumber: false
|
||||
visible: root.available
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user