mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
UI: notes, alt text and rename drafts no longer carry over to another document (#413)
This commit is contained in:
@@ -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)));
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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, ¤t);
|
||||
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!({}));
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user