diff --git a/crates/ui-egui/src/containers.rs b/crates/ui-egui/src/containers.rs index 4ddf606..fbf0ce1 100644 --- a/crates/ui-egui/src/containers.rs +++ b/crates/ui-egui/src/containers.rs @@ -308,6 +308,8 @@ pub fn show( let mut delete_object: Option = None; // An empty box that was clicked: it has no words to answer a click, so the click starts some. let mut start_in: Option = None; + // A new page's box clicked below its date: the words start on a line under the date. + let mut start_under_date: Option = None; let right_click = ui.input(|i| i.pointer.secondary_clicked()); let pointer = ui.ctx().pointer_latest_pos(); let mut opened_now = false; @@ -476,6 +478,22 @@ pub fn show( filled_bottom = filled_bottom.max(frame.max.y); } + // A new page's box holds only its date and is drawn tall, as OneNote's first note area is. + // Below the date there was no block to take a click, so the space looked ready for + // writing and swallowed what was typed (issue #148). A click there starts a line under + // the date. + if range.len() == 1 && pageedit::is_page_stamp(blocks, range.start) { + let under_date = Rect::from_min_max(pos2(frame.min.x, child.min_rect().max.y), frame.max); + if under_date.height() > 0.0 { + let start = ui.interact(under_date, ui.id().with(("container-under-date", i)), Sense::click()); + if start.hovered() { + ui.ctx().set_cursor_icon(egui::CursorIcon::Text); + } + if start.clicked() { + start_under_date = Some(i); + } + } + } let hovered = ui.rect_contains_pointer(frame); // A box with nothing in it has no block under the pointer, so a click inside it went // nowhere and no caret appeared. The whole of it, below the handle, is a place to start @@ -1142,6 +1160,34 @@ pub fn show( ui.ctx().request_repaint(); } + if let Some(i) = start_under_date + && start_in.is_none() + && delete_at.is_none() + && container::blocks_of(boxes, i).len() == 1 + { + let at = container::blocks_of(boxes, i).end.min(blocks.len()); + blocks.insert(at, Block::Paragraph(Vec::new())); + out.changed.columns |= cols.insert_block(at); + out.justified |= justified.insert_block(at); + if let Some(c) = boxes.get_mut(i) { + c.blocks += 1; + } + *caret = Some(pageedit::Caret { + block: at, + text: String::new(), + focus: true, + selection: (0, 0), + place: None, + cell: None, + span: None, + inline: None, + }); + out.changed.blocks = true; + out.changed.caret = true; + out.boxes = true; + ui.ctx().request_repaint(); + } + // Remove an embedded block after it has been drawn, so the current frame's // hit testing and selection affordance remain stable. if let Some(block) = delete_object diff --git a/crates/ui-egui/tests/ui.rs b/crates/ui-egui/tests/ui.rs index dd44b9c..1ba18d6 100644 --- a/crates/ui-egui/tests/ui.rs +++ b/crates/ui-egui/tests/ui.rs @@ -607,6 +607,38 @@ fn clicking_a_page_opens_it_and_a_container_toggles() { review(&mut h, "markdown_page"); } +/// Issue #148: on a new page, a click in the blank space under the date line puts a caret +/// there, and what is typed is kept, in both formats. +#[test] +fn a_click_under_a_new_pages_date_starts_writing() { + for format in ["html", "cmgfm"] { + let (_t, app, s) = sample_app("ui-new-page-click"); + let mut h = harness(app, LIGHT); + h.run(); + let section = s.md_page.parent().expect("section").to_string_lossy().into_owned(); + let made = ask(&mut h, "engine.execute", json!({"command": "node.create", "params": {"parent": section, "kind": "page", "format": format}})) + .expect("page"); + let page = made.as_str().expect("a path").to_string(); + ask(&mut h, "ui.open", json!({"path": page})).expect("open"); + settle(&mut h); + let state = ask(&mut h, "ui.inspect", json!({})).expect("inspect"); + let origin = state.pointer("/page/origin").and_then(Value::as_array).expect("page origin"); + let (ox, oy) = (origin[0].as_f64().unwrap_or(0.0), origin[1].as_f64().unwrap_or(0.0)); + + for (dx, dy) in [(60.0, 90.0), (200.0, 180.0)] { + ask(&mut h, "ui.click", json!({"x": ox + dx, "y": oy + dy})).expect("click under the date"); + settle(&mut h); + let caret = ask(&mut h, "ui.inspect", json!({})).expect("inspect")["page"]["caret"].clone(); + assert!(!caret.is_null(), "{format}: a click at +{dx},+{dy} under the date puts a caret: {state}"); + } + ask(&mut h, "ui.text", json!({"text": "TEST EMPTY AREA"})).expect("type"); + settle(&mut h); + h.state_mut().save_page_now(); + let saved = ask(&mut h, "engine.execute", json!({"command": "page.load", "params": {"path": page}})).expect("load"); + assert!(saved["body"].as_str().is_some_and(|b| b.contains("TEST EMPTY AREA")), "{format}: what was typed is kept: {saved}"); + } +} + /// The caret lives in the page: clicking a block and typing changes that block, and the page /// is written back in its own format. This is the editor the owner asked for, so it is tested /// for both formats.