Fix #1685: a save or export that fails is reported in a dialog that stays
A save that couldn't write its file (a read-only folder, a file another program holds open, a full disk) was only reported in the status bar, a line the next message replaces; a background save's failure arrived there some time after the user had moved on. Save, Save As, Save a Copy, Save as Template, Export As and the commands that write one file now also say it in a dialog naming the file and the reason, which stays until it is closed. The document stays modified and keeps the file it had. A message that arrives while another dialog is open waits for it to close instead of replacing it. An agent's request gets the error as its reply, without a dialog; a failed recovery write stays in the status bar.
This commit is contained in:
parent
8f8d49110f
commit
e802ea8438
|
|
@ -41,10 +41,27 @@ pub const MESSAGE: &str = "message";
|
|||
|
||||
pub(super) const MESSAGE_SPEC: DialogSpec = DialogSpec { ok: None, ..SPEC };
|
||||
|
||||
/// Most messages kept for later while a dialog is open ([`tell`]).
|
||||
const MAX_WAITING: usize = 16;
|
||||
|
||||
/// Tell the user `message` (with an optional `detail` line), both in the UI language, in a dialog
|
||||
/// they close.
|
||||
/// they close. While another dialog is open (the message comes from work in the background) it
|
||||
/// waits its turn instead of replacing it: neither what the user was doing there nor the message
|
||||
/// is lost.
|
||||
pub fn tell(app: &mut VectorcraftApp, message: &str, detail: &str) {
|
||||
app.ui.dialog = Some(Dialog::new(MESSAGE, json!({"message": message, "detail": detail})));
|
||||
let d = Dialog::new(MESSAGE, json!({"message": message, "detail": detail}));
|
||||
if app.ui.dialog.is_none() {
|
||||
app.ui.dialog = Some(d);
|
||||
} else if app.ui.told.len() < MAX_WAITING && !app.ui.dialog.iter().chain(&app.ui.told).any(|t| t.kind == MESSAGE && t.fields == d.fields) {
|
||||
app.ui.told.push(d);
|
||||
}
|
||||
}
|
||||
|
||||
/// Show the next message that waited for a dialog to close (each frame).
|
||||
pub(super) fn show_waiting(app: &mut VectorcraftApp) {
|
||||
if app.ui.dialog.is_none() && !app.ui.told.is_empty() {
|
||||
app.ui.dialog = Some(app.ui.told.remove(0));
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes before running, so a dialog the command opens stays open.
|
||||
|
|
|
|||
|
|
@ -366,6 +366,7 @@ pub(crate) fn settle(app: &mut VectorcraftApp) {
|
|||
pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
about::show(app, ctx);
|
||||
settle(app);
|
||||
confirm::show_waiting(app);
|
||||
// The kind of dialog shown last frame: a different one (or none) means this one just opened, and
|
||||
// its first field is to take the keyboard focus (`focus_id`, until a field takes it).
|
||||
let (shown_id, focus_id) = (egui::Id::new("dialog-shown"), egui::Id::new("dialog-focus-pending"));
|
||||
|
|
|
|||
|
|
@ -3508,6 +3508,8 @@ effect Regular Pravidelný
|
|||
Serif / Mincho Patkové / mincho
|
||||
Changes made since it was last saved will be lost. Změny provedené od posledního uložení budou ztraceny.
|
||||
Can't open “{name}”. „{name}“ nelze otevřít.
|
||||
Can't save “{name}”. „{name}“ nelze uložit.
|
||||
Can't export “{name}”. „{name}“ nelze exportovat.
|
||||
Downloaded {what} Staženo: {what}
|
||||
Exported {count} file(s) to {folder} Exportováno souborů: {count} do {folder}
|
||||
Exported {what} Exportováno: {what}
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -754,6 +754,8 @@
|
|||
Do Not Move Nicht verschieben
|
||||
Do you want to save the changes you made to “{name}”? Möchten Sie die Änderungen an „{name}“ speichern?
|
||||
Can't open “{name}”. „{name}“ kann nicht geöffnet werden.
|
||||
Can't save “{name}”. „{name}“ kann nicht gespeichert werden.
|
||||
Can't export “{name}”. „{name}“ kann nicht exportiert werden.
|
||||
Document Dokument
|
||||
Document Color Mode Dokumentfarbmodus
|
||||
Document Info Dokumentinformationen
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -768,6 +768,8 @@
|
|||
Do Not Move No mover
|
||||
Do you want to save the changes you made to “{name}”? ¿Quieres guardar los cambios hechos en “{name}”?
|
||||
Can't open “{name}”. No se puede abrir «{name}».
|
||||
Can't save “{name}”. No se puede guardar «{name}».
|
||||
Can't export “{name}”. No se puede exportar «{name}».
|
||||
Document Documento
|
||||
Document Color Mode Modo de color del documento
|
||||
Document Info Información del documento
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -770,6 +770,8 @@
|
|||
Do Not Move Ne pas déplacer
|
||||
Do you want to save the changes you made to “{name}”? Voulez-vous enregistrer les modifications apportées à « {name} » ?
|
||||
Can't open “{name}”. Impossible d’ouvrir « {name} ».
|
||||
Can't save “{name}”. Impossible d’enregistrer « {name} ».
|
||||
Can't export “{name}”. Impossible d’exporter « {name} ».
|
||||
Document Document
|
||||
Document Color Mode Mode colorimétrique du document
|
||||
Document Info Informations sur le document
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -485,6 +485,8 @@
|
|||
Calligraphic Kalligrafikus
|
||||
Calligraphic Brush Options Kalligrafikus ecset beállításai
|
||||
Can't open “{name}”. Nem nyitható meg: „{name}”.
|
||||
Can't save “{name}”. Nem menthető: „{name}”.
|
||||
Can't export “{name}”. Nem exportálható: „{name}”.
|
||||
Can't print these settings: see the Summary Ezekkel a beállításokkal nem lehet nyomtatni: lásd az Összegzést
|
||||
Cancel Mégse
|
||||
Cancel Key Object Kulcsobjektum megszüntetése
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -768,6 +768,8 @@
|
|||
Do Not Move Non spostare
|
||||
Do you want to save the changes you made to “{name}”? Vuoi salvare le modifiche apportate a “{name}”?
|
||||
Can't open “{name}”. Impossibile aprire «{name}».
|
||||
Can't save “{name}”. Impossibile salvare «{name}».
|
||||
Can't export “{name}”. Impossibile esportare «{name}».
|
||||
Document Documento
|
||||
Document Color Mode Metodo colore documento
|
||||
Document Info Informazioni documento
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -1223,6 +1223,8 @@
|
|||
Divide Vertically Into 垂直方向に分割
|
||||
Do you want to save the changes you made to “{name}”? 「{name}」への変更を保存しますか?
|
||||
Can't open “{name}”. 「{name}」を開けません。
|
||||
Can't save “{name}”. 「{name}」を保存できません。
|
||||
Can't export “{name}”. 「{name}」を書き出せません。
|
||||
Document ドキュメント
|
||||
Document Ink Options ドキュメントのインキオプション
|
||||
Document JSON ドキュメント JSON
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -1218,6 +1218,8 @@
|
|||
Divide Vertically Into 세로로 나누기
|
||||
Do you want to save the changes you made to “{name}”? “{name}”의 변경 내용을 저장하시겠습니까?
|
||||
Can't open “{name}”. “{name}”을(를) 열 수 없습니다.
|
||||
Can't save “{name}”. “{name}”을(를) 저장할 수 없습니다.
|
||||
Can't export “{name}”. “{name}”을(를) 내보낼 수 없습니다.
|
||||
Document 문서
|
||||
Document Ink Options 문서 잉크 옵션
|
||||
Document JSON 문서 JSON
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -3391,6 +3391,8 @@ effect Regular Normalny
|
|||
Serif / Mincho Szeryfowe / mincho
|
||||
Changes made since it was last saved will be lost. Zmiany wprowadzone od ostatniego zapisu zostaną utracone.
|
||||
Can't open “{name}”. Nie można otworzyć „{name}”.
|
||||
Can't save “{name}”. Nie można zapisać „{name}”.
|
||||
Can't export “{name}”. Nie można wyeksportować „{name}”.
|
||||
Downloaded {what} Pobrano {what}
|
||||
Exported {count} file(s) to {folder} Wyeksportowane pliki do {folder}: {count}
|
||||
Exported {what} Wyeksportowano {what}
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -692,6 +692,8 @@
|
|||
Divide Vertically Into Dividir Verticalmente Em
|
||||
Do you want to save the changes you made to “{name}”? Deseja salvar as alterações feitas em “{name}”?
|
||||
Can't open “{name}”. Não é possível abrir “{name}”.
|
||||
Can't save “{name}”. Não é possível salvar “{name}”.
|
||||
Can't export “{name}”. Não é possível exportar “{name}”.
|
||||
Document Documento
|
||||
Document Color Mode Modo de Cor do Documento
|
||||
Document Info Informações do Documento
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -766,6 +766,8 @@
|
|||
Do Not Move Не перемещать
|
||||
Do you want to save the changes you made to “{name}”? Сохранить изменения, внесённые в «{name}»?
|
||||
Can't open “{name}”. Не удаётся открыть «{name}».
|
||||
Can't save “{name}”. Не удаётся сохранить «{name}».
|
||||
Can't export “{name}”. Не удаётся экспортировать «{name}».
|
||||
Document Документ
|
||||
Document Color Mode Цветовой режим документа
|
||||
Document Info Сведения о документе
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -762,6 +762,8 @@
|
|||
Do Not Move Не переміщати
|
||||
Do you want to save the changes you made to “{name}”? Зберегти зміни, внесені до «{name}»?
|
||||
Can't open “{name}”. Не вдається відкрити «{name}».
|
||||
Can't save “{name}”. Не вдається зберегти «{name}».
|
||||
Can't export “{name}”. Не вдається експортувати «{name}».
|
||||
Document Документ
|
||||
Document Color Mode Колірний режим документа
|
||||
Document Info Відомості про документ
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 13.
|
|
|
@ -401,6 +401,8 @@
|
|||
Calligraphic Thư pháp
|
||||
Calligraphic Brush Options Tùy chọn cọ thư pháp
|
||||
Can't open “{name}”. Không thể mở “{name}”.
|
||||
Can't save “{name}”. Không thể lưu “{name}”.
|
||||
Can't export “{name}”. Không thể xuất “{name}”.
|
||||
Can't print these settings: see the Summary Không thể in các cài đặt này: xem Tóm tắt
|
||||
Cancel Hủy
|
||||
Cancel Key Object Hủy đối tượng chính
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 11.
|
|
|
@ -723,6 +723,8 @@
|
|||
Do Not Move 不移动
|
||||
Do you want to save the changes you made to “{name}”? 要存储对“{name}”所做的更改吗?
|
||||
Can't open “{name}”. 无法打开“{name}”。
|
||||
Can't save “{name}”. 无法保存“{name}”。
|
||||
Can't export “{name}”. 无法导出“{name}”。
|
||||
Document 文档
|
||||
Document Color Mode 文档颜色模式
|
||||
Document Info 文档信息
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -741,6 +741,8 @@
|
|||
Do Not Move 不移動
|
||||
Do you want to save the changes you made to “{name}”? 要儲存對「{name}」所做的變更嗎?
|
||||
Can't open “{name}”. 無法開啟「{name}」。
|
||||
Can't save “{name}”. 無法儲存「{name}」。
|
||||
Can't export “{name}”. 無法匯出「{name}」。
|
||||
Document 文件
|
||||
Document Color Mode 文件色彩模式
|
||||
Document Info 文件資訊
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -167,6 +167,29 @@ pub fn report_open_error(app: &mut VectorcraftApp, name: &str, e: &str) {
|
|||
dialogs::confirm::tell(app, &message, &crate::i18n::message(crate::i18n::current(), e));
|
||||
}
|
||||
|
||||
/// A save or an export that didn't write `path`: say so in a dialog that names the file and the
|
||||
/// reason and stays until it is closed, as an editor does (#1685). The status bar's line, which
|
||||
/// the caller's error becomes, is replaced by the next message, and a document that wasn't saved
|
||||
/// must not look saved: it stays modified, and what was at `path` is as it was. Not for a request
|
||||
/// an agent sent and waits on: the error is its reply.
|
||||
pub(crate) fn report_write_error(app: &mut VectorcraftApp, export: bool, path: &str, e: &str) {
|
||||
if app.ui.answering_control {
|
||||
return;
|
||||
}
|
||||
let name = fileio::file_name(path);
|
||||
let message = if export { tl!("Can't export “{name}”.") } else { tl!("Can't save “{name}”.") };
|
||||
dialogs::confirm::tell(app, &crate::i18n::fmt(message, &[("name", &name)]), &crate::i18n::message(crate::i18n::current(), e));
|
||||
}
|
||||
|
||||
/// [`write_to`], a failure reported ([`report_write_error`]).
|
||||
pub(crate) fn write_reporting(app: &mut VectorcraftApp, path: &str, bytes: &[u8]) -> Result<(), String> {
|
||||
let r = write_to(&mut app.services, path, bytes);
|
||||
if let Err(e) = &r {
|
||||
report_write_error(app, false, path, e);
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
/// [`open_path`] for a file the user asked for, a failure reported ([`report_open_error`]). On
|
||||
/// the desktop a document is decoded off the UI thread ([`crate::opening`]): the result is then
|
||||
/// `{background: true, status}`, and the document opens in its tab (or the failure is reported)
|
||||
|
|
@ -259,7 +282,7 @@ pub(crate) fn write_named(app: &mut VectorcraftApp, path: Option<String>, name:
|
|||
pick_path(app, &FilePick { name: name.to_string(), folder, ..Default::default() })?
|
||||
}
|
||||
};
|
||||
write_to(&mut app.services, &path, bytes)?;
|
||||
write_reporting(app, &path, bytes)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
|
|
@ -515,8 +538,10 @@ fn write_plan(app: &mut VectorcraftApp, plan: SavePlan) -> Result<Value, String>
|
|||
}
|
||||
.map_err(|e| e.to_string())
|
||||
};
|
||||
let failed = target.clone().unwrap_or_default();
|
||||
let then = move |app: &mut VectorcraftApp, r: Result<Value, String>| {
|
||||
let r = r?;
|
||||
// Not written: the document keeps its file, format and modified state, and the user is told.
|
||||
let r = r.inspect_err(|e| report_write_error(app, false, &failed, e))?;
|
||||
// The document may have closed meanwhile: the file is written all the same.
|
||||
done.complete(&mut app.session, uid);
|
||||
let path = r["path"].as_str().unwrap_or_default().to_string();
|
||||
|
|
@ -601,8 +626,9 @@ pub fn export(app: &mut VectorcraftApp, format: Option<&str>, path: Option<Strin
|
|||
Ok(out)
|
||||
};
|
||||
let enabled = app.session.prefs.background_export;
|
||||
let mut r = background::run(app, enabled, label, None, work, |app, r| {
|
||||
let r = r?;
|
||||
let failed = target.clone();
|
||||
let mut r = background::run(app, enabled, label, None, work, move |app, r| {
|
||||
let r = r.inspect_err(|e| report_write_error(app, true, &failed, e))?;
|
||||
let what = match r["files"].as_array() {
|
||||
Some(files) => format!("{} files", files.len()),
|
||||
None => r["path"].as_str().unwrap_or_default().to_string(),
|
||||
|
|
@ -672,7 +698,7 @@ pub(crate) fn run_to_file(app: &mut VectorcraftApp, id: &str, ext: &str, mut par
|
|||
Some(p) => p,
|
||||
None => target_path(app, None, ext)?,
|
||||
};
|
||||
write_to(&mut app.services, &path, &bytes)?;
|
||||
write_reporting(app, &path, &bytes)?;
|
||||
Ok((path, v))
|
||||
}
|
||||
|
||||
|
|
@ -829,13 +855,69 @@ mod tests {
|
|||
assert!(app.ui.dialog.is_none());
|
||||
}
|
||||
|
||||
/// #1685: a save or an export that can't write its file says so in a dialog that names the
|
||||
/// file and the reason and stays until it is closed; the document stays modified and keeps
|
||||
/// the file it had. An agent's request only gets the error, and a message that arrives while
|
||||
/// another dialog is open waits for it to close.
|
||||
#[test]
|
||||
fn a_save_that_cannot_write_is_reported_in_a_dialog_that_stays() {
|
||||
let refuse: crate::WriteFn = Box::new(|p: &str, _: &[u8]| Err(format!("{p}: Access is denied. (os error 5)")));
|
||||
let mut app = VectorcraftApp::new(Session::new(), Services { write: Some(refuse), ..Default::default() });
|
||||
app.run("file.new", json!({"width": 100, "height": 80})).unwrap();
|
||||
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap();
|
||||
let unsaved = |app: &VectorcraftApp| {
|
||||
let st = app.session.doc().unwrap();
|
||||
(st.is_dirty(), st.path.clone())
|
||||
};
|
||||
for (command, path, message) in [
|
||||
("file.save", "/docs/Poster.vectorcraft", "Can't save “Poster.vectorcraft”."),
|
||||
("file.saveAs", "/docs/Poster.vectorcraft", "Can't save “Poster.vectorcraft”."),
|
||||
("file.saveCopy", "/docs/Copy.vectorcraft", "Can't save “Copy.vectorcraft”."),
|
||||
("file.exportAs", "/docs/Poster.png", "Can't export “Poster.png”."),
|
||||
("file.export.pdf", "/docs/Poster.pdf", "Can't save “Poster.pdf”."),
|
||||
] {
|
||||
let e = app.run(command, json!({"path": path})).unwrap_err();
|
||||
assert!(e.contains("Access is denied"), "{command}: {e}");
|
||||
let d = app.ui.dialog.take().unwrap_or_else(|| panic!("{command}: a dialog says so"));
|
||||
assert_eq!((d.kind.as_str(), d.str("message").as_str()), (dialogs::confirm::MESSAGE, message), "{command}");
|
||||
assert_eq!(d.str("detail"), format!("{path}: Access is denied. (os error 5)"), "{command}");
|
||||
assert_eq!(unsaved(&app), (true, None), "{command}");
|
||||
}
|
||||
// An agent waiting on its request gets the error as its reply, and no dialog.
|
||||
app.ui.answering_control = true;
|
||||
assert!(app.run("file.save", json!({"path": "/docs/Poster.vectorcraft"})).is_err());
|
||||
app.ui.answering_control = false;
|
||||
assert!(app.ui.dialog.is_none() && app.ui.told.is_empty());
|
||||
// Under another dialog (a background save that fails later) the message waits its turn,
|
||||
// once, and is drawn when that dialog is closed.
|
||||
dialogs::confirm::ask(&mut app, "Delete the swatch?", "", "edit.undo", json!({}));
|
||||
for _ in 0..2 {
|
||||
report_write_error(&mut app, false, "/docs/Poster.vectorcraft", "the disk is full");
|
||||
}
|
||||
assert_eq!((app.ui.dialog.as_ref().map(|d| d.kind.as_str()), app.ui.told.len()), (Some(dialogs::confirm::KIND), 1));
|
||||
app.ui.dialog = None;
|
||||
for _ in 0..2 {
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| dialogs::show(&mut app, ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
let d = app.ui.dialog.as_ref().expect("the message is shown, and stays");
|
||||
assert_eq!(
|
||||
(d.kind.as_str(), d.str("message").as_str(), d.str("detail").as_str()),
|
||||
(dialogs::confirm::MESSAGE, "Can't save “Poster.vectorcraft”.", "the disk is full")
|
||||
);
|
||||
assert!(app.ui.told.is_empty());
|
||||
}
|
||||
|
||||
/// #1693: a browser download that can't be started is a failed save: the document stays
|
||||
/// modified and without a file, and Save Changes doesn't close it.
|
||||
#[test]
|
||||
fn a_download_that_cannot_start_leaves_the_document_unsaved_and_open() {
|
||||
let started = Rc::new(RefCell::new(false));
|
||||
let ok = started.clone();
|
||||
let download: crate::DownloadFn = Box::new(move |_: &str, _: &[u8]| if *ok.borrow() { Ok(()) } else { Err("the browser refused the download".into()) });
|
||||
let download: crate::DownloadFn =
|
||||
Box::new(move |_: &str, _: &[u8]| if *ok.borrow() { Ok(()) } else { Err("the browser refused the download".into()) });
|
||||
let mut app = VectorcraftApp::new(Session::new(), Services { download: Some(download), ..Default::default() });
|
||||
app.run("file.new", json!({"width": 100, "height": 80})).unwrap();
|
||||
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap();
|
||||
|
|
|
|||
|
|
@ -780,7 +780,10 @@ impl VectorcraftApp {
|
|||
let reply = req.reply.clone();
|
||||
// Guarded per request: a panic must not drop the channel (taken out of `self` above).
|
||||
let roots = self.automation_roots.clone();
|
||||
let outcome = match file_access::confine(roots.as_ref(), || vectorcraft_engine::guard::catch_panic(|| control::handle(self, ctx, &req))) {
|
||||
self.ui.answering_control = true;
|
||||
let answer = file_access::confine(roots.as_ref(), || vectorcraft_engine::guard::catch_panic(|| control::handle(self, ctx, &req)));
|
||||
self.ui.answering_control = false;
|
||||
let outcome = match answer {
|
||||
Ok(outcome) => outcome,
|
||||
Err(msg) => {
|
||||
self.picks.recover();
|
||||
|
|
|
|||
|
|
@ -158,9 +158,9 @@ pub(crate) fn unembed(app: &mut VectorcraftApp, id: u64, name: &str) {
|
|||
if app.services.download.is_some() {
|
||||
if let Ok(v) = app.session.execute("links.unembed", &json!({ "id": id }))
|
||||
&& let (Some(name), Some(bytes)) = (v["name"].as_str(), v["dataBase64"].as_str().and_then(vectorcraft_format::base64_decode))
|
||||
&& let Err(e) = crate::io::write_to(&mut app.services, name, &bytes)
|
||||
{
|
||||
app.status(e);
|
||||
// A download that can't start is said like any file that can't be written.
|
||||
let _ = crate::io::write_reporting(app, name, &bytes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -306,6 +306,14 @@ pub struct UiState {
|
|||
pub dock: bool,
|
||||
pub view: ViewFlags,
|
||||
pub dialog: Option<Dialog>,
|
||||
/// Messages to show in a dialog once the open one is closed (`dialogs::confirm::tell`), in
|
||||
/// the order they came.
|
||||
#[serde(skip)]
|
||||
pub told: Vec<Dialog>,
|
||||
/// A control request is being answered: its caller gets what fails as its reply, so no
|
||||
/// dialog says it too (`io::report_write_error`).
|
||||
#[serde(skip)]
|
||||
pub answering_control: bool,
|
||||
/// Toolbar group whose flyout is open.
|
||||
pub flyout: Option<usize>,
|
||||
/// Last tool shown for each toolbar group (flyout selection sticks).
|
||||
|
|
@ -681,6 +689,8 @@ impl Default for UiState {
|
|||
dock: true,
|
||||
view: ViewFlags::default(),
|
||||
dialog: None,
|
||||
told: vec![],
|
||||
answering_control: false,
|
||||
flyout: None,
|
||||
group_tool: vectorcraft_tools::TOOL_GROUPS.iter().map(|g| g[0].id.to_string()).collect(),
|
||||
status: String::new(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue