No forgetting, deleting or clearing on a single press, anywhere
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -66,10 +66,16 @@ Column {
|
||||
|
||||
width: parent.width
|
||||
label: entry.modelData.name || entry.modelData.address || "Unknown device"
|
||||
detail: root.stateLabel(entry.modelData)
|
||||
// Armed, the detail names what Forget costs. "Forget" reads like
|
||||
// "hide from this list"; it is the pairing itself that goes, and
|
||||
// the device has to be put back into pairing mode to return.
|
||||
detail: forgetConfirm.armed
|
||||
? "Drops the pairing — the device must be put back into pairing mode to return"
|
||||
: root.stateLabel(entry.modelData)
|
||||
divider: entry.index < root.shown.length - 1
|
||||
|| root.hiddenCount > 0 || root.showAll
|
||||
controlWidth: 200
|
||||
// Room for Keep and the confirming press while the row is armed.
|
||||
controlWidth: forgetConfirm.armed ? 290 : 200
|
||||
activatable: !entry.modelData.pairing
|
||||
onActivated: root.primaryAction(entry.modelData)
|
||||
|
||||
@@ -87,11 +93,17 @@ Column {
|
||||
onClicked: root.primaryAction(entry.modelData)
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
ConfirmAction {
|
||||
id: forgetConfirm
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: entry.modelData.paired
|
||||
text: "Forget"
|
||||
onClicked: entry.modelData.forget()
|
||||
actionId: "bluetooth-forget:"
|
||||
+ (entry.modelData.address || entry.modelData.name || "")
|
||||
armText: "Forget…"
|
||||
confirmText: "Forget it"
|
||||
enabled: !entry.modelData.pairing
|
||||
onConfirmed: entry.modelData.forget()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,16 +529,29 @@ Column {
|
||||
SettingRow {
|
||||
width: parent.width
|
||||
label: "Delete this mode"
|
||||
detail: "Removes it and its exceptions for good"
|
||||
controlWidth: 90
|
||||
// The permanence is said on the armed press, where it is the
|
||||
// question being answered, rather than under a button that had
|
||||
// already done it by the time the sentence was read.
|
||||
detail: deleteConfirm.armed
|
||||
? "Removes " + (root.modeName === "" ? "this mode" : root.modeName)
|
||||
+ (root.allow.length === 0
|
||||
? ""
|
||||
: " and its " + root.allow.length
|
||||
+ (root.allow.length === 1 ? " exception" : " exceptions"))
|
||||
+ " for good"
|
||||
: "Takes it out of the list"
|
||||
controlWidth: deleteConfirm.armed ? 180 : 90
|
||||
divider: false
|
||||
|
||||
SettingsButton {
|
||||
ConfirmAction {
|
||||
id: deleteConfirm
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Delete"
|
||||
tone: "danger"
|
||||
onClicked: FocusModes.removeMode(root.modeId)
|
||||
actionId: "focus-mode-delete:" + root.modeId
|
||||
armText: "Delete…"
|
||||
confirmText: "Delete it"
|
||||
onConfirmed: FocusModes.removeMode(root.modeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ Rectangle {
|
||||
id: aliasFrame
|
||||
anchors.left: reorderControls.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: removeButton.left
|
||||
anchors.right: removeConfirm.left
|
||||
anchors.rightMargin: 12
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 12
|
||||
@@ -107,17 +107,23 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// The line under the field says which light this is; armed, it says what
|
||||
// pressing again costs instead, because the alias only lives in this
|
||||
// favourite and there is nowhere else to read it back from.
|
||||
Text {
|
||||
anchors.left: aliasFrame.left
|
||||
anchors.right: removeButton.left
|
||||
anchors.right: removeConfirm.left
|
||||
anchors.rightMargin: 12
|
||||
anchors.top: aliasFrame.bottom
|
||||
anchors.topMargin: 7
|
||||
text: root.sourceName
|
||||
color: Theme.fgDim
|
||||
text: removeConfirm.armed
|
||||
? "Drops it from My Home — the name you gave it goes too"
|
||||
: root.sourceName
|
||||
color: removeConfirm.armed ? Theme.danger : Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
elide: Text.ElideRight
|
||||
wrapMode: removeConfirm.armed ? Text.WordWrap : Text.NoWrap
|
||||
elide: removeConfirm.armed ? Text.ElideNone : Text.ElideRight
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -143,17 +149,14 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
id: removeButton
|
||||
ConfirmAction {
|
||||
id: removeConfirm
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 11
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Remove"
|
||||
activeFocusOnTab: true
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
||||
onClicked: root.removeRequested(root.favorite.id)
|
||||
Keys.onReturnPressed: root.removeRequested(root.favorite.id)
|
||||
Keys.onSpacePressed: root.removeRequested(root.favorite.id)
|
||||
actionId: "home-favorite-remove:" + root.favorite.id
|
||||
armText: "Remove…"
|
||||
confirmText: "Remove it"
|
||||
onConfirmed: root.removeRequested(root.favorite.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,15 +246,33 @@ SettingsPage {
|
||||
SliderRow { setting: "maxVisibleToasts" }
|
||||
SliderRow { setting: "notificationHistoryLimit" }
|
||||
|
||||
ActionRow {
|
||||
// A SettingRow with its own ConfirmAction rather than an ActionRow:
|
||||
// clearing history is not undoable and it takes the inline replies and
|
||||
// actions with it, so it takes two presses and the armed detail names
|
||||
// both the count and what stops being answerable.
|
||||
SettingRow {
|
||||
label: "Notification history"
|
||||
detail: Notifs.history.length === 1
|
||||
? "1 kept now · the oldest are dropped past the limit"
|
||||
: `${Notifs.history.length} kept now · the oldest are dropped past the limit`
|
||||
action: "Clear"
|
||||
enabled: Notifs.history.length > 0
|
||||
detail: clearConfirm.armed
|
||||
? (Notifs.history.length === 1
|
||||
? "Drops the 1 kept notification — its inline reply and actions go with it"
|
||||
: `Drops all ${Notifs.history.length} kept notifications — their inline replies and actions go with them`)
|
||||
: (Notifs.history.length === 1
|
||||
? "1 kept now · the oldest are dropped past the limit"
|
||||
: `${Notifs.history.length} kept now · the oldest are dropped past the limit`)
|
||||
controlWidth: clearConfirm.armed ? 200 : 110
|
||||
divider: false
|
||||
onTriggered: Notifs.dismissAll()
|
||||
|
||||
ConfirmAction {
|
||||
id: clearConfirm
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
actionId: "notification-history-clear"
|
||||
armText: "Clear…"
|
||||
confirmText: "Clear them"
|
||||
enabled: Notifs.history.length > 0
|
||||
onConfirmed: Notifs.dismissAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -408,17 +408,35 @@ SettingsPage {
|
||||
|
||||
// Rebinding stores only the new chord; what a shortcut does always
|
||||
// comes from the desktop's configuration.
|
||||
ActionRow {
|
||||
//
|
||||
// A SettingRow with its own ConfirmAction rather than an ActionRow:
|
||||
// this one press drops every override at once and reloads the live
|
||||
// compositor, so it takes two, and the armed detail says both.
|
||||
SettingRow {
|
||||
label: "Restore every shipped shortcut"
|
||||
detail: root.overrideCount === 0
|
||||
? "Every shortcut is where it shipped"
|
||||
: root.overrideCount + (root.overrideCount === 1
|
||||
? " shortcut differs from the shipped keymap"
|
||||
: " shortcuts differ from the shipped keymap")
|
||||
action: "Restore all"
|
||||
detail: restoreConfirm.armed
|
||||
? "Puts " + root.overrideCount + (root.overrideCount === 1
|
||||
? " changed shortcut" : " changed shortcuts")
|
||||
+ " back where they shipped and reloads the compositor now"
|
||||
: (root.overrideCount === 0
|
||||
? "Every shortcut is where it shipped"
|
||||
: root.overrideCount + (root.overrideCount === 1
|
||||
? " shortcut differs from the shipped keymap"
|
||||
: " shortcuts differ from the shipped keymap"))
|
||||
controlWidth: restoreConfirm.armed ? 220 : 130
|
||||
divider: false
|
||||
enabled: root.overrideCount > 0 && !Keybinds.reloading
|
||||
onTriggered: Keybinds.resetAll()
|
||||
|
||||
ConfirmAction {
|
||||
id: restoreConfirm
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
actionId: "keybinds-restore-all"
|
||||
armText: "Restore all…"
|
||||
confirmText: "Restore them"
|
||||
enabled: root.overrideCount > 0 && !Keybinds.reloading
|
||||
onConfirmed: Keybinds.resetAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,13 +30,54 @@ Rectangle {
|
||||
readonly property bool isDefault: root.theme.id === (root.theme.scheme === "light"
|
||||
? ThemeCatalog.defaultLight : ThemeCatalog.defaultDark)
|
||||
|
||||
// Delete arms in place rather than through ConfirmAction. The ✕ badge is a
|
||||
// 22px corner of a card whose whole body is the apply target roughly ten
|
||||
// pixels away -- there is nowhere in that caption to put a Keep beside a
|
||||
// Delete it without the pair becoming the card. So the badge itself is the
|
||||
// two presses: the first turns it into a labelled danger pill and reddens
|
||||
// the card's border, the second deletes. The token is ShellState's, the
|
||||
// same one ConfirmAction uses, so one confirm is armed app-wide.
|
||||
readonly property string removeActionId: "theme-card-delete:" + root.theme.id
|
||||
readonly property bool removeArmed: ShellState.armedConfirm === root.removeActionId
|
||||
|
||||
function pressRemove(): void {
|
||||
if (!root.removeArmed) {
|
||||
ShellState.armedConfirm = root.removeActionId;
|
||||
return;
|
||||
}
|
||||
root.disarmRemove();
|
||||
root.removed();
|
||||
}
|
||||
|
||||
function disarmRemove(): void {
|
||||
if (root.removeArmed)
|
||||
ShellState.armedConfirm = "";
|
||||
}
|
||||
|
||||
// Armed, the card body is the way out: a press anywhere on it takes the
|
||||
// arming back instead of applying the theme, because the miss this guards
|
||||
// against is a press landing next to the badge rather than on it.
|
||||
function press(): void {
|
||||
if (root.removeArmed) {
|
||||
root.disarmRemove();
|
||||
return;
|
||||
}
|
||||
root.chosen();
|
||||
}
|
||||
|
||||
// A card that scrolls away, or a gallery that rebuilds, must not leave the
|
||||
// token claiming a theme nothing is showing.
|
||||
Component.onDestruction: root.disarmRemove()
|
||||
|
||||
implicitHeight: preview.height + caption.height + 4
|
||||
radius: Theme.cardRadius + 2
|
||||
color: Theme.alpha(Theme.fg, 0.04)
|
||||
border.width: 2
|
||||
border.color: root.activeFocus
|
||||
? Theme.accentSecondary
|
||||
: (root.active ? Theme.accent : Theme.alpha(Theme.fg, 0.12))
|
||||
border.color: root.removeArmed
|
||||
? Theme.danger
|
||||
: (root.activeFocus
|
||||
? Theme.accentSecondary
|
||||
: (root.active ? Theme.accent : Theme.alpha(Theme.fg, 0.12)))
|
||||
clip: true
|
||||
activeFocusOnTab: true
|
||||
|
||||
@@ -44,11 +85,13 @@ Rectangle {
|
||||
Accessible.name: root.theme.name
|
||||
Accessible.description: (root.theme.scheme === "light" ? "Light theme" : "Dark theme")
|
||||
+ (root.active ? ", selected" : "")
|
||||
+ (root.removeArmed ? ", delete is armed — press this card to keep it" : "")
|
||||
Accessible.focusable: true
|
||||
Accessible.focused: root.activeFocus
|
||||
|
||||
Keys.onReturnPressed: root.chosen()
|
||||
Keys.onSpacePressed: root.chosen()
|
||||
Keys.onReturnPressed: root.press()
|
||||
Keys.onSpacePressed: root.press()
|
||||
Keys.onEscapePressed: root.disarmRemove()
|
||||
|
||||
Rectangle {
|
||||
id: preview
|
||||
@@ -148,36 +191,52 @@ Rectangle {
|
||||
// Only saved themes can be removed, and the affordance is a
|
||||
// separate focus stop so Tab never lands on "delete" when the
|
||||
// person meant "select".
|
||||
//
|
||||
// At rest the badge is neutral: danger marks the press that
|
||||
// confirms, never the one that initiates, which is SettingsButton's
|
||||
// contract and holds here too. Armed, it stops being a glyph and
|
||||
// becomes a labelled pill wide enough to read.
|
||||
Rectangle {
|
||||
id: remove
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.shipped
|
||||
width: 22
|
||||
width: root.removeArmed ? removeLabel.implicitWidth + 16 : 22
|
||||
height: 22
|
||||
radius: 7
|
||||
color: removeHover.hovered
|
||||
? Theme.alpha(Theme.danger, 0.22)
|
||||
: Theme.alpha(Theme.fg, 0.07)
|
||||
border.width: remove.activeFocus ? 2 : 1
|
||||
border.color: remove.activeFocus
|
||||
color: root.removeArmed
|
||||
? Theme.alpha(Theme.danger, 0.3)
|
||||
: (removeHover.hovered
|
||||
? Theme.alpha(Theme.fg, 0.14)
|
||||
: Theme.alpha(Theme.fg, 0.07))
|
||||
border.width: root.removeArmed || remove.activeFocus ? 2 : 1
|
||||
border.color: root.removeArmed || remove.activeFocus
|
||||
? Theme.danger : Theme.alpha(Theme.fg, 0.08)
|
||||
activeFocusOnTab: visible
|
||||
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: "Delete " + root.theme.name
|
||||
Accessible.name: root.removeArmed
|
||||
? "Delete " + root.theme.name + ", press again to confirm"
|
||||
: "Delete " + root.theme.name
|
||||
Accessible.description: root.removeArmed
|
||||
? "This theme's palette, terminal colours and effects go with it"
|
||||
: ""
|
||||
Accessible.focusable: true
|
||||
Accessible.focused: remove.activeFocus
|
||||
|
||||
Keys.onReturnPressed: root.removed()
|
||||
Keys.onSpacePressed: root.removed()
|
||||
Keys.onReturnPressed: root.pressRemove()
|
||||
Keys.onSpacePressed: root.pressRemove()
|
||||
Keys.onEscapePressed: root.disarmRemove()
|
||||
|
||||
Text {
|
||||
id: removeLabel
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "✕"
|
||||
color: Theme.danger
|
||||
text: root.removeArmed ? "Delete it" : "✕"
|
||||
color: root.removeArmed ? Theme.danger : Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: root.removeArmed ? Font.DemiBold : Font.Normal
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
@@ -190,7 +249,7 @@ Rectangle {
|
||||
// on its way out.
|
||||
TapHandler {
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
onTapped: root.removed()
|
||||
onTapped: root.pressRemove()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,6 +261,6 @@ Rectangle {
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: root.chosen()
|
||||
onTapped: root.press()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,16 @@ SettingRow {
|
||||
readonly property bool custom: ThemeProfiles.activeProfile.shipped !== true
|
||||
|
||||
label: "Save as"
|
||||
detail: root.custom
|
||||
? "Saving again under a new name keeps both"
|
||||
: "Editing a shipped theme forks it; give the fork a name to keep it"
|
||||
// Armed, the row says what the delete actually costs: a saved theme is the
|
||||
// only copy of its palette, terminal colours and effect values.
|
||||
detail: deleteConfirm.armed
|
||||
? "Deletes “" + String(ThemeProfiles.activeProfile.name ?? "this theme")
|
||||
+ "” — its palette, terminal colours and effects go with it"
|
||||
: (root.custom
|
||||
? "Saving again under a new name keeps both"
|
||||
: "Editing a shipped theme forks it; give the fork a name to keep it")
|
||||
divider: false
|
||||
controlWidth: root.custom ? 380 : 292
|
||||
controlWidth: root.custom ? (deleteConfirm.armed ? 470 : 380) : 292
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
@@ -83,23 +88,14 @@ SettingRow {
|
||||
Keys.onSpacePressed: saveButton.save()
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
id: deleteButton
|
||||
ConfirmAction {
|
||||
id: deleteConfirm
|
||||
|
||||
visible: root.custom
|
||||
text: "Delete"
|
||||
tone: "danger"
|
||||
activeFocusOnTab: visible
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.danger : Theme.alpha(Theme.danger, 0.45)
|
||||
|
||||
function remove(): void {
|
||||
ThemeProfiles.deleteProfile(ThemeProfiles.activeId);
|
||||
}
|
||||
|
||||
onClicked: deleteButton.remove()
|
||||
Keys.onReturnPressed: deleteButton.remove()
|
||||
Keys.onSpacePressed: deleteButton.remove()
|
||||
actionId: "theme-profile-delete:" + ThemeProfiles.activeId
|
||||
armText: "Delete…"
|
||||
confirmText: "Delete it"
|
||||
onConfirmed: ThemeProfiles.deleteProfile(ThemeProfiles.activeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user