UI: notes, alt text and rename drafts no longer carry over to another document (#413)

This commit is contained in:
MarekSpodBiedry
2026-10-13 17:40:31 +02:00
parent 457406a7a5
commit 902edb3c85
4 changed files with 60 additions and 7 deletions
+5 -3
View File
@@ -93,6 +93,7 @@ fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
return;
};
let x = deckcraft_engine::cmd::xfrm_of(&st.doc, &st.selection, &sh);
let uid = st.uid;
// Tabs as icons.
ui.horizontal(|ui| {
for (tab, icon, tip) in [
@@ -173,13 +174,14 @@ fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
fields(ui, "Vertical", "fy", x.y, "y", "shape.move", 72.0);
ui.add_space(6.0);
ui.label(egui::RichText::new("Alt Text").font(theme::bold(13.0)));
let id = ui.id().with(("alt", sh.id.0));
let mut alt: String = ui.data_mut(|d| d.get_temp(id).unwrap_or_else(|| sh.descr.clone()));
let id = ui.id().with(("alt", uid, sh.id.0));
let stored: Option<(String, String)> = ui.data_mut(|d| d.get_temp(id));
let mut alt = widgets::text_draft(stored, &sh.descr);
let r = ui.add(egui::TextEdit::multiline(&mut alt).desired_rows(3).desired_width(f32::INFINITY));
if r.lost_focus() && alt != sh.descr {
run(app, "shape.altText", json!({"text": alt}));
}
ui.data_mut(|d| d.insert_temp(id, alt));
ui.data_mut(|d| d.insert_temp(id, (sh.descr.clone(), alt)));
}
"text" => {
ui.label(egui::RichText::new("Text Box").font(theme::bold(13.0)));
+26
View File
@@ -28,6 +28,18 @@ pub struct State {
pub anchor: Option<u32>,
/// Row being dragged and the drop it would make: (index among siblings, line y).
pub drag: Option<(u32, Option<(usize, f32)>)>,
/// Document (by uid) the state above belongs to.
owner: Option<u64>,
}
impl State {
/// Ties the pane state to a document: shape ids repeat between documents, so switching
/// documents drops the pending rename, folds, anchor and drag.
pub fn bind(&mut self, uid: u64) {
if self.owner != Some(uid) {
*self = State { owner: Some(uid), ..State::default() };
}
}
}
/// One line of the list.
@@ -108,6 +120,7 @@ fn small_icon_button(ui: &mut Ui, icon: Icon, label: &str, enabled: bool) -> egu
pub fn show(app: &mut SlideApp, ui: &mut Ui) {
let t = Tokens::get(ui.ctx());
let Some(st) = app.session.active() else { return };
app.selpane.bind(st.uid);
let rows = rows(st.shapes(), &app.selpane.collapsed);
let selected: Vec<u32> = st.selection.shapes.iter().map(|i| i.0).collect();
let has_sel = !selected.is_empty();
@@ -388,6 +401,19 @@ fn context_menu(app: &mut SlideApp, resp: &egui::Response, row: &Row) {
mod tests {
use super::*;
#[test]
fn switching_document_drops_a_pending_rename() {
let mut s = State::default();
s.bind(1);
s.renaming = Some((5, "draft".to_string()));
s.collapsed.insert(2);
s.bind(1);
assert!(s.renaming.is_some(), "same document keeps the draft");
s.bind(2);
assert!(s.renaming.is_none());
assert!(s.collapsed.is_empty());
}
fn r(id: u32, top: f32) -> (u32, Rect) {
(id, Rect::from_min_size(pos2(0.0, top), vec2(100.0, ROW_H)))
}
+5 -4
View File
@@ -3,8 +3,8 @@
use egui::{Align2, Color32, CornerRadius, Rect, Sense, Stroke, Ui, pos2, vec2};
use serde_json::json;
use crate::SlideApp;
use crate::theme::{self, Tokens};
use crate::{SlideApp, widgets};
/// Grid of slides; click selects, double-click opens in Normal view, drag reorders.
pub fn show(app: &mut SlideApp, ui: &mut Ui) {
@@ -133,8 +133,9 @@ pub fn notes_page(app: &mut SlideApp, ui: &mut Ui) {
let notes_rect = Rect::from_min_max(pos2(srect.min.x, srect.max.y + 24.0), pos2(srect.max.x, page.max.y - page_h * 0.06));
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(notes_rect));
let current = st.current_slide().map(|s| s.notes_text()).unwrap_or_default();
let id = egui::Id::new(("notespage", idx));
let mut buf: String = child.data_mut(|d| d.get_temp(id).unwrap_or_else(|| current.clone()));
let id = egui::Id::new(("notespage", st.uid, idx));
let stored: Option<(String, String)> = child.data_mut(|d| d.get_temp(id));
let mut buf = widgets::text_draft(stored, &current);
let r = child.add(
egui::TextEdit::multiline(&mut buf)
.hint_text("Click to add text")
@@ -147,7 +148,7 @@ pub fn notes_page(app: &mut SlideApp, ui: &mut Ui) {
if r.lost_focus() && buf != current {
let _ = app.run("slide.notes", json!({"text": buf, "index": idx}));
}
child.data_mut(|d| d.insert_temp(id, buf));
child.data_mut(|d| d.insert_temp(id, (current, buf)));
let nav = ui.input(|i| (i.key_pressed(egui::Key::PageDown), i.key_pressed(egui::Key::PageUp)));
if nav.0 {
let _ = app.run("slide.next", json!({}));
+24
View File
@@ -423,6 +423,15 @@ pub fn fmt_num(v: f64, suffix: &str) -> String {
format!("{s}{suffix}")
}
/// The text a kept-open text box shows: the typed draft while the stored value is still what the
/// draft started from, otherwise the stored value (another document, a reorder or an undo changed it).
pub fn text_draft(stored: Option<(String, String)>, current: &str) -> String {
match stored {
Some((base, buf)) if base == current => buf,
_ => current.to_string(),
}
}
/// Section header inside panes.
pub fn section(ui: &mut Ui, title: &str, open: bool) -> bool {
let t = Tokens::get(ui.ctx());
@@ -485,4 +494,19 @@ mod tests {
assert_eq!(*picked.borrow(), Some(Some(ColorRef::rgb(Rgba::from_hex("00FF00").unwrap()))));
assert!(harness.query_by_role(Role::TextInput).is_none(), "picking a colour closes the menu");
}
/// #413: a draft is kept while the stored text is unchanged ...
#[test]
fn text_draft_keeps_typing_while_the_stored_text_is_unchanged() {
let stored = Some(("old".to_string(), "old plus typing".to_string()));
assert_eq!(text_draft(stored, "old"), "old plus typing");
}
/// ... and dropped when the stored text changes underneath it.
#[test]
fn text_draft_drops_a_stale_draft_when_the_stored_text_changes() {
let stored = Some(("old".to_string(), "old plus typing".to_string()));
assert_eq!(text_draft(stored, "undone"), "undone");
assert_eq!(text_draft(None, "fresh"), "fresh");
}
}