Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,17 +336,13 @@ lays the paragraph out, so a click on one lands at its edge and the caret
steps over it, and a test holds the two lengths equal for every paragraph in
the corpus.

**A slide's label is edited in a text box where it sits**, in edit mode, on a
click: the box shows the paragraph's flat text unformatted at the paragraph's
own font, size, colour, indent and width, on the page's paper. Escape puts
the paragraph back, Ctrl+Enter or a click elsewhere keeps what was typed,
Enter is a new paragraph once it is kept, and Backspace with the caret at the
very start joins the paragraph onto the one before it, the box reopening
there with the caret at the join. The labels are addressed from the page by
the shape's index; in edit mode a drag on a slide moves the shape and does not
select its text. What is written is the difference between what the box was
given and what it hands back, so a span or a marker outside the change is
untouched.
**A slide's label is edited the same way, on the slide.** Its paragraphs are
named by their path from the page, which begins with the shape's index, and
the caret keeps to the shape it is in: moving and joining never cross into
another shape, and a click in another moves it there. A drag on a slide moves
the shape, so a label's paragraphs take clicks alone and the caret goes down
on the click; Shift with the arrows selects. The arrows and the page keys step
through the slides only while nothing has the keyboard.

**A paragraph is edited through its flat text**, built from the tree and not
from the renderer's layout: a `text:s` is its spaces, a `text:tab` a tab, a
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,8 @@ spreadsheet application recalculates it.

`xodp` draws each slide at the size the document sets, with the speaker's notes
under it. In edit mode a click picks one of the slide's shapes, a drag moves it,
a drag on a corner resizes it, and a click on the text in one opens it as a
text box where it sits: Escape puts it back, Ctrl+Enter or a click elsewhere
keeps what you typed.
a drag on a corner resizes it, and a click on the text in one puts the caret
there to type into it as in `xodt`, within that shape.

Each application opens one kind of file and says so when handed another, naming
the sibling that reads it.
Expand Down
21 changes: 14 additions & 7 deletions crates/egui_richedit/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ impl<P: Clone + Eq + std::hash::Hash + std::fmt::Debug> RichEdit<P> {
/// what the pointer does on it. Called for every editable paragraph in
/// document order, on screen or not, after [`Self::input`].
///
/// `response` is the paragraph's, allocated to sense clicks and drags.
/// `response` is the paragraph's, allocated to sense clicks and, for a
/// drag to select, drags.
pub fn paragraph(&mut self, ui: &Ui, response: &Response, paragraph: &P, laid: Laid) {
let Laid {
mut galley,
Expand Down Expand Up @@ -714,11 +715,15 @@ impl<P: Clone + Eq + std::hash::Hash + std::fmt::Debug> RichEdit<P> {

// A press and its release can arrive in one frame, a quick click on a
// slow frame, and then the button is no longer down on anything; the
// click says where it was.
if pressed
&& (response.is_pointer_button_down_on() || response.clicked())
&& let Some(pos) = pos
{
// click says where it was. A paragraph that leaves drags to what is
// under it takes the click alone, so a press that becomes a drag puts
// no caret down.
let placed = if response.sense.senses_drag() {
pressed && (response.is_pointer_button_down_on() || response.clicked())
} else {
response.clicked()
};
if placed && let Some(pos) = pos {
let cursor = at_pointer(pos);
let to = Position::new(paragraph.clone(), map.to_model(cursor.index.0));
match &mut self.selection {
Expand All @@ -728,7 +733,9 @@ impl<P: Clone + Eq + std::hash::Hash + std::fmt::Debug> RichEdit<P> {
self.next_row = cursor.prefer_next_row;
self.group = None;
self.column = None;
self.dragging = true;
// A paragraph that does not sense drags leaves them to what is
// under it, as a slide does to move the shape.
self.dragging = response.sense.senses_drag();
self.last_interaction = ui.input(|input| input.time);
ui.memory_mut(|memory| memory.request_focus(self.id));
} else if self.dragging && !down {
Expand Down
52 changes: 52 additions & 0 deletions crates/egui_richedit/tests/editing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,3 +513,55 @@ fn page_down_moves_a_view_s_height_and_page_up_comes_back() {
h.key(Key::PageUp);
assert_eq!(h.focus(), Position::new(0, 2));
}

#[test]
fn a_paragraph_that_leaves_drags_takes_a_caret_on_a_click_and_not_on_a_drag() {
let ctx = Context::default();
let mut editor = RichEdit::new(Id::new("editor"));
let mut model = Plain::new(&["a label on a slide"]);
let mut frame = |events: Vec<Event>| {
let input = RawInput {
events,
screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(400.0, 400.0))),
..RawInput::default()
};
let mut output = ctx.run_ui(input, |ui| {
editor.input(ui, &mut model);
let mut job = ParagraphJob::new(LayoutJob::default());
job.text(&model.paragraphs[0], TextFormat::default());
let (job, map) = job.into_parts();
let galley = ui.fonts_mut(|fonts| fonts.layout_job(job));
let (rect, response) =
ui.allocate_exact_size(vec2(300.0, galley.size().y), Sense::click());
let laid = Laid {
galley,
map,
origin: rect.min,
};
editor.paragraph(ui, &response, &0, laid);
});
output.textures_delta.clear();
editor.selection().cloned()
};
let button = |x: f32, pressed| Event::PointerButton {
pos: Pos2::new(x, 5.0),
button: egui::PointerButton::Primary,
pressed,
modifiers: Modifiers::NONE,
};
frame(vec![Event::PointerMoved(Pos2::new(5.0, 5.0))]);
frame(vec![button(5.0, true)]);
for x in [40.0, 80.0, 120.0] {
frame(vec![Event::PointerMoved(Pos2::new(x, 5.0))]);
}
assert_eq!(
frame(vec![button(120.0, false)]),
None,
"a drag is not a click"
);

frame(vec![Event::PointerMoved(Pos2::new(5.0, 5.0))]);
frame(vec![button(5.0, true)]);
let clicked = frame(vec![button(5.0, false)]).expect("a click puts a caret down");
assert_eq!(clicked.focus.paragraph, 0);
}
13 changes: 13 additions & 0 deletions crates/odox-core/src/doc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ impl Document {
self.body()?.child(&Ns::Office, local)
}

/// Where [`Self::body_of`] is under the content root, as a path of child
/// indices: what an editor addresses the body's paragraphs from.
pub fn body_path(&self, local: &str) -> Option<Vec<usize>> {
let (at_body, body) = self
.content
.elements_indexed()
.find(|(_, e)| e.is(&Ns::Office, "body"))?;
let (at_part, _) = body
.elements_indexed()
.find(|(_, e)| e.is(&Ns::Office, local))?;
Some(vec![at_body, at_part])
}

/// A name for writing into the document, in the prefix the document
/// declares for the namespace on its content root, or the conventional one
/// where it declares none.
Expand Down
9 changes: 9 additions & 0 deletions crates/odox-core/src/doc/pres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ impl Presentation {
.at_mut(&[position])
}

/// Where the slide at a position among the body's children is under the
/// content root, as a path of child indices: what an editor addresses its
/// labels' paragraphs from.
pub fn page_path(&self, position: usize) -> Option<Vec<usize>> {
let mut path = self.document.body_path("presentation")?;
path.push(position);
Some(path)
}

/// The master page a slide names.
pub fn master(&self, slide: &Slide<'_>) -> Option<&Element> {
self.document.styles.master_page(slide.master_page?)
Expand Down
10 changes: 1 addition & 9 deletions crates/odox-core/src/doc/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,7 @@ impl TextDocument {
/// Where [`Self::body`] is under the content root, as a path of child
/// indices: what an editor addresses the body's paragraphs from.
pub fn body_path(&self) -> Option<Vec<usize>> {
let (at_body, body) = self
.document
.content
.elements_indexed()
.find(|(_, e)| e.is(&Ns::Office, "body"))?;
let (at_text, _) = body
.elements_indexed()
.find(|(_, e)| e.is(&Ns::Office, "text"))?;
Some(vec![at_body, at_text])
self.document.body_path("text")
}

/// The page layout the document's first master page points at.
Expand Down
Loading
Loading