No forgetting, deleting or clearing on a single press, anywhere
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -41,11 +41,36 @@ Item {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Forgetting is armed in place rather than through ConfirmAction: a 44px
|
||||||
|
// row inside a 300px popup has no width for a Keep/Forget it pair beside
|
||||||
|
// the device name, so the trash glyph arms itself and the row's own
|
||||||
|
// sublabel becomes the question. The token is ShellState's, the same one
|
||||||
|
// ConfirmAction uses, so arming here disarms whatever was armed elsewhere
|
||||||
|
// and only one confirm is ever live app-wide.
|
||||||
|
//
|
||||||
|
// The token is keyed by device address rather than held on the delegate,
|
||||||
|
// because discovery rebuilds this list -- and every delegate in it -- every
|
||||||
|
// time BlueZ reports something new. An arm parked on the row object would
|
||||||
|
// be dropped by the next scan result; keyed by address it survives the
|
||||||
|
// rebuild and is only ever cleared by a press or by the section closing.
|
||||||
|
readonly property string forgetPrefix: "bluetooth-forget-quick:"
|
||||||
|
|
||||||
|
function forgetId(device): string {
|
||||||
|
return root.forgetPrefix + (device.address || device.name || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function disarmForget(): void {
|
||||||
|
if (ShellState.armedConfirm.indexOf(root.forgetPrefix) === 0)
|
||||||
|
ShellState.armedConfirm = "";
|
||||||
|
}
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (root.active)
|
if (root.active) {
|
||||||
Connectivity.acquireDiscovery(root.scanHold);
|
Connectivity.acquireDiscovery(root.scanHold);
|
||||||
else
|
} else {
|
||||||
Connectivity.releaseDiscovery(root.scanHold);
|
Connectivity.releaseDiscovery(root.scanHold);
|
||||||
|
root.disarmForget();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: Connectivity.releaseDiscovery(root.scanHold)
|
Component.onDestruction: Connectivity.releaseDiscovery(root.scanHold)
|
||||||
@@ -106,18 +131,36 @@ Item {
|
|||||||
|
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
|
||||||
|
readonly property bool forgetArmed:
|
||||||
|
ShellState.armedConfirm === root.forgetId(deviceRow.modelData)
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
// BlueZ reports a plain freedesktop name ("audio-headphones");
|
// BlueZ reports a plain freedesktop name ("audio-headphones");
|
||||||
// the symbolic variant is the one that can be recolored.
|
// the symbolic variant is the one that can be recolored.
|
||||||
icon: deviceRow.modelData.icon !== "" ? deviceRow.modelData.icon + "-symbolic" : "bluetooth-symbolic"
|
icon: deviceRow.modelData.icon !== "" ? deviceRow.modelData.icon + "-symbolic" : "bluetooth-symbolic"
|
||||||
iconFallback: "bluetooth-symbolic"
|
iconFallback: "bluetooth-symbolic"
|
||||||
label: deviceRow.modelData.name || deviceRow.modelData.address
|
label: deviceRow.modelData.name || deviceRow.modelData.address
|
||||||
sublabel: root.stateText(deviceRow.modelData)
|
// Armed, the row states the question and what it costs, since
|
||||||
|
// the glyph alone is too small to carry either.
|
||||||
|
sublabel: deviceRow.forgetArmed
|
||||||
|
? "Press again to drop the pairing"
|
||||||
|
: root.stateText(deviceRow.modelData)
|
||||||
selected: deviceRow.modelData.connected
|
selected: deviceRow.modelData.connected
|
||||||
onClicked: root.activate(deviceRow.modelData)
|
// While armed the row body is the way out: a press anywhere
|
||||||
|
// else on it takes the arming back instead of connecting, so
|
||||||
|
// the escape is the largest target on screen.
|
||||||
|
onClicked: {
|
||||||
|
if (deviceRow.forgetArmed) {
|
||||||
|
root.disarmForget();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.activate(deviceRow.modelData);
|
||||||
|
}
|
||||||
|
|
||||||
// Forgetting is destructive enough that it gets its own
|
// Forgetting is destructive enough that it gets its own
|
||||||
// control rather than sharing the row click.
|
// control rather than sharing the row click -- and its own
|
||||||
|
// two presses. See root.forgetPrefix for why the arming
|
||||||
|
// happens in place here rather than through ConfirmAction.
|
||||||
IconButton {
|
IconButton {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: deviceRow.modelData.paired
|
visible: deviceRow.modelData.paired
|
||||||
@@ -125,7 +168,28 @@ Item {
|
|||||||
iconSize: 13
|
iconSize: 13
|
||||||
icon: "user-trash-symbolic"
|
icon: "user-trash-symbolic"
|
||||||
iconFallback: "window-close-symbolic"
|
iconFallback: "window-close-symbolic"
|
||||||
onClicked: deviceRow.modelData.forget()
|
tint: deviceRow.forgetArmed ? Theme.danger : Theme.fg
|
||||||
|
onClicked: {
|
||||||
|
if (!deviceRow.forgetArmed) {
|
||||||
|
ShellState.armedConfirm = root.forgetId(deviceRow.modelData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.disarmForget();
|
||||||
|
deviceRow.modelData.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Behind the parent's own fill, so the hover tint still
|
||||||
|
// reads on top of it. Danger appears only once armed --
|
||||||
|
// the first press must not look like the last one.
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
z: -1
|
||||||
|
radius: parent.radius
|
||||||
|
visible: deviceRow.forgetArmed
|
||||||
|
color: Theme.alpha(Theme.danger, 0.3)
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.alpha(Theme.danger, 0.6)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,10 +66,16 @@ Column {
|
|||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: entry.modelData.name || entry.modelData.address || "Unknown device"
|
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
|
divider: entry.index < root.shown.length - 1
|
||||||
|| root.hiddenCount > 0 || root.showAll
|
|| 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
|
activatable: !entry.modelData.pairing
|
||||||
onActivated: root.primaryAction(entry.modelData)
|
onActivated: root.primaryAction(entry.modelData)
|
||||||
|
|
||||||
@@ -87,11 +93,17 @@ Column {
|
|||||||
onClicked: root.primaryAction(entry.modelData)
|
onClicked: root.primaryAction(entry.modelData)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsButton {
|
ConfirmAction {
|
||||||
|
id: forgetConfirm
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: entry.modelData.paired
|
visible: entry.modelData.paired
|
||||||
text: "Forget"
|
actionId: "bluetooth-forget:"
|
||||||
onClicked: entry.modelData.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 {
|
SettingRow {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: "Delete this mode"
|
label: "Delete this mode"
|
||||||
detail: "Removes it and its exceptions for good"
|
// The permanence is said on the armed press, where it is the
|
||||||
controlWidth: 90
|
// 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
|
divider: false
|
||||||
|
|
||||||
SettingsButton {
|
ConfirmAction {
|
||||||
|
id: deleteConfirm
|
||||||
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "Delete"
|
actionId: "focus-mode-delete:" + root.modeId
|
||||||
tone: "danger"
|
armText: "Delete…"
|
||||||
onClicked: FocusModes.removeMode(root.modeId)
|
confirmText: "Delete it"
|
||||||
|
onConfirmed: FocusModes.removeMode(root.modeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ Rectangle {
|
|||||||
id: aliasFrame
|
id: aliasFrame
|
||||||
anchors.left: reorderControls.right
|
anchors.left: reorderControls.right
|
||||||
anchors.leftMargin: 10
|
anchors.leftMargin: 10
|
||||||
anchors.right: removeButton.left
|
anchors.right: removeConfirm.left
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: 12
|
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 {
|
Text {
|
||||||
anchors.left: aliasFrame.left
|
anchors.left: aliasFrame.left
|
||||||
anchors.right: removeButton.left
|
anchors.right: removeConfirm.left
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
anchors.top: aliasFrame.bottom
|
anchors.top: aliasFrame.bottom
|
||||||
anchors.topMargin: 7
|
anchors.topMargin: 7
|
||||||
text: root.sourceName
|
text: removeConfirm.armed
|
||||||
color: Theme.fgDim
|
? "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.family: Theme.fontFamily
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
elide: Text.ElideRight
|
wrapMode: removeConfirm.armed ? Text.WordWrap : Text.NoWrap
|
||||||
|
elide: removeConfirm.armed ? Text.ElideNone : Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -143,17 +149,14 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsButton {
|
ConfirmAction {
|
||||||
id: removeButton
|
id: removeConfirm
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 11
|
anchors.rightMargin: 11
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "Remove"
|
actionId: "home-favorite-remove:" + root.favorite.id
|
||||||
activeFocusOnTab: true
|
armText: "Remove…"
|
||||||
border.width: activeFocus ? 2 : 1
|
confirmText: "Remove it"
|
||||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
onConfirmed: root.removeRequested(root.favorite.id)
|
||||||
onClicked: root.removeRequested(root.favorite.id)
|
|
||||||
Keys.onReturnPressed: root.removeRequested(root.favorite.id)
|
|
||||||
Keys.onSpacePressed: root.removeRequested(root.favorite.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,15 +246,33 @@ SettingsPage {
|
|||||||
SliderRow { setting: "maxVisibleToasts" }
|
SliderRow { setting: "maxVisibleToasts" }
|
||||||
SliderRow { setting: "notificationHistoryLimit" }
|
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"
|
label: "Notification history"
|
||||||
detail: Notifs.history.length === 1
|
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"
|
? "1 kept now · the oldest are dropped past the limit"
|
||||||
: `${Notifs.history.length} kept now · the oldest are dropped past the limit`
|
: `${Notifs.history.length} kept now · the oldest are dropped past the limit`)
|
||||||
action: "Clear"
|
controlWidth: clearConfirm.armed ? 200 : 110
|
||||||
enabled: Notifs.history.length > 0
|
|
||||||
divider: false
|
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
|
// Rebinding stores only the new chord; what a shortcut does always
|
||||||
// comes from the desktop's configuration.
|
// 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"
|
label: "Restore every shipped shortcut"
|
||||||
detail: root.overrideCount === 0
|
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"
|
? "Every shortcut is where it shipped"
|
||||||
: root.overrideCount + (root.overrideCount === 1
|
: root.overrideCount + (root.overrideCount === 1
|
||||||
? " shortcut differs from the shipped keymap"
|
? " shortcut differs from the shipped keymap"
|
||||||
: " shortcuts differ from the shipped keymap")
|
: " shortcuts differ from the shipped keymap"))
|
||||||
action: "Restore all"
|
controlWidth: restoreConfirm.armed ? 220 : 130
|
||||||
divider: false
|
divider: false
|
||||||
|
|
||||||
|
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
|
enabled: root.overrideCount > 0 && !Keybinds.reloading
|
||||||
onTriggered: Keybinds.resetAll()
|
onConfirmed: Keybinds.resetAll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,54 @@ Rectangle {
|
|||||||
readonly property bool isDefault: root.theme.id === (root.theme.scheme === "light"
|
readonly property bool isDefault: root.theme.id === (root.theme.scheme === "light"
|
||||||
? ThemeCatalog.defaultLight : ThemeCatalog.defaultDark)
|
? 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
|
implicitHeight: preview.height + caption.height + 4
|
||||||
radius: Theme.cardRadius + 2
|
radius: Theme.cardRadius + 2
|
||||||
color: Theme.alpha(Theme.fg, 0.04)
|
color: Theme.alpha(Theme.fg, 0.04)
|
||||||
border.width: 2
|
border.width: 2
|
||||||
border.color: root.activeFocus
|
border.color: root.removeArmed
|
||||||
|
? Theme.danger
|
||||||
|
: (root.activeFocus
|
||||||
? Theme.accentSecondary
|
? Theme.accentSecondary
|
||||||
: (root.active ? Theme.accent : Theme.alpha(Theme.fg, 0.12))
|
: (root.active ? Theme.accent : Theme.alpha(Theme.fg, 0.12)))
|
||||||
clip: true
|
clip: true
|
||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
|
|
||||||
@@ -44,11 +85,13 @@ Rectangle {
|
|||||||
Accessible.name: root.theme.name
|
Accessible.name: root.theme.name
|
||||||
Accessible.description: (root.theme.scheme === "light" ? "Light theme" : "Dark theme")
|
Accessible.description: (root.theme.scheme === "light" ? "Light theme" : "Dark theme")
|
||||||
+ (root.active ? ", selected" : "")
|
+ (root.active ? ", selected" : "")
|
||||||
|
+ (root.removeArmed ? ", delete is armed — press this card to keep it" : "")
|
||||||
Accessible.focusable: true
|
Accessible.focusable: true
|
||||||
Accessible.focused: root.activeFocus
|
Accessible.focused: root.activeFocus
|
||||||
|
|
||||||
Keys.onReturnPressed: root.chosen()
|
Keys.onReturnPressed: root.press()
|
||||||
Keys.onSpacePressed: root.chosen()
|
Keys.onSpacePressed: root.press()
|
||||||
|
Keys.onEscapePressed: root.disarmRemove()
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: preview
|
id: preview
|
||||||
@@ -148,36 +191,52 @@ Rectangle {
|
|||||||
// Only saved themes can be removed, and the affordance is a
|
// Only saved themes can be removed, and the affordance is a
|
||||||
// separate focus stop so Tab never lands on "delete" when the
|
// separate focus stop so Tab never lands on "delete" when the
|
||||||
// person meant "select".
|
// 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 {
|
Rectangle {
|
||||||
id: remove
|
id: remove
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: !root.shipped
|
visible: !root.shipped
|
||||||
width: 22
|
width: root.removeArmed ? removeLabel.implicitWidth + 16 : 22
|
||||||
height: 22
|
height: 22
|
||||||
radius: 7
|
radius: 7
|
||||||
color: removeHover.hovered
|
color: root.removeArmed
|
||||||
? Theme.alpha(Theme.danger, 0.22)
|
? Theme.alpha(Theme.danger, 0.3)
|
||||||
: Theme.alpha(Theme.fg, 0.07)
|
: (removeHover.hovered
|
||||||
border.width: remove.activeFocus ? 2 : 1
|
? Theme.alpha(Theme.fg, 0.14)
|
||||||
border.color: remove.activeFocus
|
: 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)
|
? Theme.danger : Theme.alpha(Theme.fg, 0.08)
|
||||||
activeFocusOnTab: visible
|
activeFocusOnTab: visible
|
||||||
|
|
||||||
Accessible.role: Accessible.Button
|
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.focusable: true
|
||||||
Accessible.focused: remove.activeFocus
|
Accessible.focused: remove.activeFocus
|
||||||
|
|
||||||
Keys.onReturnPressed: root.removed()
|
Keys.onReturnPressed: root.pressRemove()
|
||||||
Keys.onSpacePressed: root.removed()
|
Keys.onSpacePressed: root.pressRemove()
|
||||||
|
Keys.onEscapePressed: root.disarmRemove()
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
id: removeLabel
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "✕"
|
text: root.removeArmed ? "Delete it" : "✕"
|
||||||
color: Theme.danger
|
color: root.removeArmed ? Theme.danger : Theme.fgDim
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: root.removeArmed ? Font.DemiBold : Font.Normal
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
@@ -190,7 +249,7 @@ Rectangle {
|
|||||||
// on its way out.
|
// on its way out.
|
||||||
TapHandler {
|
TapHandler {
|
||||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||||
onTapped: root.removed()
|
onTapped: root.pressRemove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,6 +261,6 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onTapped: root.chosen()
|
onTapped: root.press()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,16 @@ SettingRow {
|
|||||||
readonly property bool custom: ThemeProfiles.activeProfile.shipped !== true
|
readonly property bool custom: ThemeProfiles.activeProfile.shipped !== true
|
||||||
|
|
||||||
label: "Save as"
|
label: "Save as"
|
||||||
detail: root.custom
|
// 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"
|
? "Saving again under a new name keeps both"
|
||||||
: "Editing a shipped theme forks it; give the fork a name to keep it"
|
: "Editing a shipped theme forks it; give the fork a name to keep it")
|
||||||
divider: false
|
divider: false
|
||||||
controlWidth: root.custom ? 380 : 292
|
controlWidth: root.custom ? (deleteConfirm.armed ? 470 : 380) : 292
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -83,23 +88,14 @@ SettingRow {
|
|||||||
Keys.onSpacePressed: saveButton.save()
|
Keys.onSpacePressed: saveButton.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsButton {
|
ConfirmAction {
|
||||||
id: deleteButton
|
id: deleteConfirm
|
||||||
|
|
||||||
visible: root.custom
|
visible: root.custom
|
||||||
text: "Delete"
|
actionId: "theme-profile-delete:" + ThemeProfiles.activeId
|
||||||
tone: "danger"
|
armText: "Delete…"
|
||||||
activeFocusOnTab: visible
|
confirmText: "Delete it"
|
||||||
border.width: activeFocus ? 2 : 1
|
onConfirmed: ThemeProfiles.deleteProfile(ThemeProfiles.activeId)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user