Files
qbz/crates/qbz-ui/ui/myqbz/MyQbzEditModal.slint
T
vicrodh c4ddca0223 fix(input): gate hotkeys while text inputs are focused
A use-site `changed has-focus` on a std-widgets LineEdit/TextEdit never
fires — has-focus is an alias into the inner TextInput (slint#7747) — so
typing a letter that doubles as a shortcut ran the action instead of
inserting the character. Sweep every text-input surface (search, import,
create/edit playlist & folder, tag editor, colour picker, settings
fields, QConnect dev modal, kiosk shell) and drive
UiFocusState.text-input-focused from a `guard-focused` probe property
that the change tracker does observe.

cc
2026-07-16 20:37:57 -06:00

148 lines
6.8 KiB
Plaintext

// My QBZ — Rename / Description / Delete-confirm modals (spec 12 §11).
//
// Driven from the hero overflow (⋯) menu. A single global (MyQbzEditState)
// backs all three; `mode` ("rename" | "description" | "delete") selects which
// body renders. Chrome mirrors EditPlaylistModal / CreateMyQbzModal: a 420px
// panel, h2 title (18px bold), the m-input field family, and an m-footer with
// secondary Cancel + a primary/destructive submit. Backdrop click + Cancel
// close without committing; Enter submits the rename. Submit is disabled while
// busy or (rename) the trimmed name is blank.
//
// Per spec §23 the Cancel / Save button labels are HARDCODED in the Tauri
// source; we keep them @tr here (DQ2 — translate hardcoded UI strings on port).
import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { MyQbzEditState, MyQbzEditActions , UiFocusState } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { QbzPrimaryButton } from "../primitives/QbzPrimaryButton.slint";
import { SecondaryButton } from "../primitives/SecondaryButton.slint";
import { LineEdit, TextEdit } from "std-widgets.slint";
export component MyQbzEditModal inherits Rectangle {
visible: MyQbzEditState.open;
property <bool> is-rename: MyQbzEditState.mode == "rename";
property <bool> is-description: MyQbzEditState.mode == "description";
property <bool> is-delete: MyQbzEditState.mode == "delete";
// Rename submit needs a non-blank trimmed name; description/delete always
// allowed (description "" clears to null). Trim check approximated by
// comparing against a stripped copy is not available in Slint, so we gate
// on a non-empty raw string for rename (the Rust side trims again).
property <bool> can-submit:
MyQbzEditState.busy ? false
: (root.is-rename ? (MyQbzEditState.draft-name != "") : true);
if MyQbzEditState.open: Rectangle {
background: #000000bf;
TouchArea {
mouse-cursor: default;
clicked => {
MyQbzEditActions.close();
}
}
Rectangle {
width: Math.min(root.width - 80px, 420px);
height: panel.preferred-height;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
border-radius: Radius.md;
background: Theme.surface-card;
border-width: 1px;
border-color: Theme.border-subtle;
drop-shadow-blur: 32px;
drop-shadow-color: #00000080;
// Swallow clicks on the panel itself (so they don't hit the
// backdrop's close TouchArea below).
TouchArea { }
panel := VerticalLayout {
padding: 24px;
spacing: 16px;
// --- Title (h2) ------------------------------------------
Text {
text: root.is-rename
? @tr("Rename")
: (root.is-description ? @tr("Edit description") : @tr("Delete"));
color: Theme.text-primary;
font-size: 18px;
font-weight: Typography.bold;
}
// --- Body: rename (name input) ---------------------------
if root.is-rename: rename-input := LineEdit {
text <=> MyQbzEditState.draft-name;
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
accepted => {
if (root.can-submit) {
MyQbzEditActions.submit-rename();
}
}
}
// --- Body: description (textarea) ------------------------
if root.is-description: desc-input := TextEdit {
height: 96px;
wrap: word-wrap;
text <=> MyQbzEditState.draft-description;
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
// input never fires (has-focus is an alias into the inner TextInput),
// so observe it through a local property instead (#619).
property <bool> guard-focused: self.has-focus;
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
}
// --- Body: delete-confirm (prompt) -----------------------
if root.is-delete: Text {
// Delete "{name}"? This cannot be undone. — interpolated
// with the collection name (spec §11.3 deleteConfirm, double quotes).
text: @tr("Delete \"{}\"? This cannot be undone.", MyQbzEditState.name);
color: Theme.text-secondary;
font-size: 14px;
wrap: word-wrap;
}
// --- Footer (m-footer) — shared labeled primitives -------
HorizontalLayout {
alignment: end;
spacing: 8px;
// Cancel (secondary).
SecondaryButton {
label: @tr("Cancel");
height: 38px;
enabled: !MyQbzEditState.busy;
clicked => {
MyQbzEditActions.close();
}
}
// Submit — primary (rename/description) or destructive
// (delete) variant of the shared QbzPrimaryButton.
QbzPrimaryButton {
label: root.is-delete ? @tr("Delete") : @tr("Save");
height: 38px;
destructive: root.is-delete;
enabled: root.can-submit;
clicked => {
if (root.is-rename) {
MyQbzEditActions.submit-rename();
} else if (root.is-description) {
MyQbzEditActions.submit-description();
} else {
MyQbzEditActions.confirm-delete();
}
}
}
}
}
}
}
}