mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-14 14:32:31 +00:00
Merge pull request #6051 from vangel76/fix/6027-textbox-edit
Text boxes inside a group can be edited
This commit is contained in:
8 files changed
+313
-17
No files matched your search
@@ -33,6 +33,7 @@ fn placed(story: StoryRef, path: Path, off: usize, x: f32) -> Placed {
|
||||
text_box: None,
|
||||
wrap: Wrap::InFrontOfText,
|
||||
origin: Default::default(),
|
||||
group_boxes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -611,6 +611,7 @@ mod tests_warp_lookups {
|
||||
text_box: id,
|
||||
wrap: Default::default(),
|
||||
origin: Default::default(),
|
||||
group_boxes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-10
@@ -778,16 +778,6 @@ pub(crate) fn place_objects(ctx: &mut Ctx, req: &ObjectPlacement) -> (Vec<Placed
|
||||
};
|
||||
// A box linked from another shows (and edits) that box's story.
|
||||
let shown = text_box.map(|id| textbox::shown_story(ctx, id));
|
||||
front.push(Placed::Object {
|
||||
rect,
|
||||
spin: float.spin(),
|
||||
story,
|
||||
path: Path(path.to_vec()),
|
||||
off: c.start,
|
||||
text_box: shown,
|
||||
wrap: float.wrap,
|
||||
origin: Point::new(at.col.0, late_here.map_or(at.para_y, |l| l.top)),
|
||||
});
|
||||
// Its text (a group: its text boxes', each where the group's turn carries it; the
|
||||
// text itself stays upright), unless the box budget says no (a box inside itself,
|
||||
// too deep, too many).
|
||||
@@ -803,6 +793,25 @@ pub(crate) fn place_objects(ctx: &mut Ctx, req: &ObjectPlacement) -> (Vec<Placed
|
||||
.collect(),
|
||||
_ => text_box.map(|id| (id, rect)).into_iter().collect(),
|
||||
};
|
||||
// A group's text boxes are where a click edits their text (#6027): kept on the
|
||||
// group's area, relative to it so they move with it.
|
||||
let group_boxes = match obj {
|
||||
InlineObject::Group { .. } => {
|
||||
boxes.iter().map(|&(id, r)| (Rect::new(r.x - rect.x, r.y - rect.y, r.w, r.h), textbox::shown_story(ctx, id))).collect()
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
front.push(Placed::Object {
|
||||
rect,
|
||||
spin: float.spin(),
|
||||
story,
|
||||
path: Path(path.to_vec()),
|
||||
off: c.start,
|
||||
text_box: shown,
|
||||
wrap: float.wrap,
|
||||
origin: Point::new(at.col.0, late_here.map_or(at.para_y, |l| l.top)),
|
||||
group_boxes,
|
||||
});
|
||||
for (id, rect) in boxes {
|
||||
let items = textbox::box_items(ctx, id, rect, depth);
|
||||
let layer = if float.wrap == Wrap::BehindText { &mut behind } else { &mut front };
|
||||
|
||||
@@ -142,7 +142,7 @@ fn all_objects(page: &Page, index: usize) -> impl Iterator<Item = ObjectHit> + '
|
||||
|
||||
fn objects_in(items: &[Placed], index: usize) -> impl Iterator<Item = ObjectHit> + '_ {
|
||||
items.iter().rev().filter_map(move |it| match it {
|
||||
Placed::Object { rect, spin, story, path, off, text_box, wrap, origin } => Some(ObjectHit {
|
||||
Placed::Object { rect, spin, story, path, off, text_box, wrap, origin, .. } => Some(ObjectHit {
|
||||
page: index,
|
||||
rect: *rect,
|
||||
spin: *spin,
|
||||
@@ -158,6 +158,35 @@ fn objects_in(items: &[Placed], index: usize) -> impl Iterator<Item = ObjectHit>
|
||||
})
|
||||
}
|
||||
|
||||
/// A text box inside a group, on the page: the group's place (`story`, `path`, `off`), the box's
|
||||
/// upright page frame and the story it shows.
|
||||
struct GroupBox<'a> {
|
||||
story: StoryRef,
|
||||
path: &'a Path,
|
||||
off: usize,
|
||||
rect: Rect,
|
||||
part: u32,
|
||||
}
|
||||
|
||||
/// The text boxes of the groups among `items`, topmost first (as [`objects_in`]).
|
||||
fn group_boxes_in(items: &[Placed]) -> impl Iterator<Item = GroupBox<'_>> + '_ {
|
||||
items.iter().rev().flat_map(|it| {
|
||||
let boxes = match it {
|
||||
Placed::Object { rect, story, path, off, group_boxes, .. } => Some((rect, story, path, off, group_boxes)),
|
||||
_ => None,
|
||||
};
|
||||
boxes.into_iter().flat_map(|(rect, story, path, off, group_boxes)| {
|
||||
group_boxes.iter().rev().map(move |(r, part)| GroupBox {
|
||||
story: *story,
|
||||
path,
|
||||
off: *off,
|
||||
rect: Rect::new(rect.x + r.x, rect.y + r.y, r.w, r.h),
|
||||
part: *part,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// The story of the text (not a text box's) near (x, y): body, footnotes, endnotes.
|
||||
fn text_at(p: &Page, index: usize, x: f32, y: f32) -> Option<StoryRef> {
|
||||
let boxes: Vec<StoryRef> = objects(p, index).filter_map(|o| o.text_box.map(StoryRef::Part)).collect();
|
||||
@@ -437,8 +466,24 @@ impl DocLayout {
|
||||
/// body text is.
|
||||
pub fn story_at(&self, page: usize, x: f32, y: f32) -> Option<StoryRef> {
|
||||
let p = self.pages.get(page)?;
|
||||
// A group's text boxes count as text boxes too (#6027).
|
||||
// Topmost first, a text box on its own or one in a group alike (a group's last member is painted on top).
|
||||
let text_box = |behind: bool| {
|
||||
objects(p, page).find(|o| o.behind() == behind && o.text_box.is_some() && o.rect.contains(o.unspin(x, y))).and_then(|o| o.text_box)
|
||||
p.items.iter().rev().find_map(|it| {
|
||||
let Placed::Object { rect, spin, text_box, wrap, group_boxes, .. } = it else { return None };
|
||||
if (*wrap == Wrap::BehindText) != behind {
|
||||
return None;
|
||||
}
|
||||
let centre = Point::new(rect.x + rect.w / 2.0, rect.y + rect.h / 2.0);
|
||||
let (ux, uy) = spin.unapply(centre.x, centre.y, x, y);
|
||||
text_box.filter(|_| rect.contains(Point::new(ux, uy))).or_else(|| {
|
||||
group_boxes
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(r, _)| Rect::new(rect.x + r.x, rect.y + r.y, r.w, r.h).contains(Point::new(x, y)))
|
||||
.map(|(_, id)| *id)
|
||||
})
|
||||
})
|
||||
};
|
||||
text_box(false).map(StoryRef::Part).or_else(|| text_at(p, page, x, y)).or_else(|| text_box(true).map(StoryRef::Part))
|
||||
}
|
||||
@@ -472,12 +517,23 @@ impl DocLayout {
|
||||
// (a form scanned as a picture, with text boxes laid over it to fill in). The same holds
|
||||
// among the objects behind the text.
|
||||
let inside_text_box = |o: &ObjectHit| o.text_box.is_some() && !grabs(o) && o.rect.contains(o.unspin(x, y));
|
||||
// Inside one of a group's text boxes (off its border) is that box's text too (#6027).
|
||||
let in_group_box = |o: &ObjectHit| {
|
||||
let items: [&[Placed]; 3] = [&p.items, &p.header, &p.footer];
|
||||
items.into_iter().flat_map(group_boxes_in).any(|b| {
|
||||
b.story == o.story
|
||||
&& *b.path == o.path
|
||||
&& b.off == o.off
|
||||
&& b.rect.w.min(b.rect.h) > edge * 3.0
|
||||
&& b.rect.expand(-edge).contains(Point::new(x, y))
|
||||
})
|
||||
};
|
||||
let topmost = |behind: bool| cands.iter().find(|o| o.behind() == behind && (grabs(o) || inside_text_box(o)));
|
||||
match topmost(false) {
|
||||
Some(o) if inside_text_box(o) => None,
|
||||
Some(o) if inside_text_box(o) || in_group_box(o) => None,
|
||||
Some(o) => Some(o.clone()),
|
||||
None if text_here => None,
|
||||
None => topmost(true).filter(|o| !inside_text_box(o)).cloned(),
|
||||
None => topmost(true).filter(|o| !inside_text_box(o) && !in_group_box(o)).cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +544,7 @@ impl DocLayout {
|
||||
.chain(objects_in(&p.footer, page))
|
||||
.find(|o| o.text_box.is_some() && o.rect.contains(Point::new(x, y)))
|
||||
.and_then(|o| o.text_box)
|
||||
.or_else(|| group_boxes_in(&p.header).chain(group_boxes_in(&p.footer)).find(|b| b.rect.contains(Point::new(x, y))).map(|b| b.part))
|
||||
}
|
||||
|
||||
/// Where the object at `pos` (its U+FFFC) is laid out.
|
||||
@@ -516,6 +573,18 @@ impl DocLayout {
|
||||
self.find_object(page_hint, |o| o.text_box == Some(part))
|
||||
}
|
||||
|
||||
/// The story the text box showing `part` sits in (where the box or its group is).
|
||||
pub fn text_box_story(&self, part: u32, page_hint: usize) -> Option<StoryRef> {
|
||||
self.text_box(part, page_hint).map(|o| o.story).or_else(|| {
|
||||
let on = |i: usize| {
|
||||
let p = self.pages.get(i)?;
|
||||
let items: [&[Placed]; 3] = [&p.items, &p.header, &p.footer];
|
||||
items.into_iter().flat_map(group_boxes_in).find(|b| b.part == part).map(|b| b.story)
|
||||
};
|
||||
on(page_hint).or_else(|| (0..self.pages.len()).filter(|i| *i != page_hint).find_map(on))
|
||||
})
|
||||
}
|
||||
|
||||
/// The first object matching `f`, looking on `page_hint` first (where the caret is: one page
|
||||
/// to scan, not the whole document).
|
||||
pub fn find_object(&self, page_hint: usize, f: impl Fn(&ObjectHit) -> bool) -> Option<ObjectHit> {
|
||||
@@ -1018,6 +1087,7 @@ mod object_index_tests {
|
||||
text_box: None,
|
||||
wrap: Wrap::InFrontOfText,
|
||||
origin: Point::new(5.0, 6.0),
|
||||
group_boxes: Vec::new(),
|
||||
};
|
||||
let layout = DocLayout {
|
||||
pages: vec![
|
||||
|
||||
@@ -105,6 +105,10 @@ pub enum Placed {
|
||||
/// Its paragraph's column left and top: where offsets relative to the column and the
|
||||
/// paragraph (`Anchor::Column` / `Anchor::Paragraph`) start.
|
||||
origin: wordcraft_geom::Point,
|
||||
/// A group's text boxes: each one's upright frame relative to `rect`'s top-left (so it
|
||||
/// moves with the group; [`Placed::turn`] turns it with the group) and the story it shows.
|
||||
/// Empty for anything else.
|
||||
group_boxes: Vec<(Rect, u32)>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -179,9 +183,15 @@ impl Placed {
|
||||
| Placed::Cell { rect, .. } => {
|
||||
*rect = turn_rect(turn, x, y, *rect);
|
||||
}
|
||||
Placed::Object { rect, origin, .. } => {
|
||||
*rect = turn_rect(turn, x, y, *rect);
|
||||
Placed::Object { rect, origin, group_boxes, .. } => {
|
||||
let old = *rect;
|
||||
*rect = turn_rect(turn, x, y, old);
|
||||
(origin.x, origin.y) = turn_point(turn, x, y, origin.x, origin.y);
|
||||
// A group's text boxes turn with it, kept relative to its new frame.
|
||||
for (r, _) in group_boxes.iter_mut() {
|
||||
let t = turn_rect(turn, x, y, Rect::new(old.x + r.x, old.y + r.y, r.w, r.h));
|
||||
*r = Rect::new(t.x - rect.x, t.y - rect.y, t.w, t.h);
|
||||
}
|
||||
}
|
||||
Placed::Clip { rect, items, .. } => {
|
||||
*rect = turn_rect(turn, x, y, *rect);
|
||||
|
||||
@@ -3902,6 +3902,127 @@ fn hidden_drawings_are_neither_drawn_nor_take_room() {
|
||||
assert_eq!(draws.iter().filter(|x| matches!(x, display::Draw::Shape { .. })).count(), 1, "{draws:?}");
|
||||
}
|
||||
|
||||
/// #6027: a text box inside a group (Word's cards of a coloured bar and a text box) is text to
|
||||
/// a click, as a text box on its own is: the click edits its text instead of grabbing the
|
||||
/// group. The group's other members still grab the group.
|
||||
#[test]
|
||||
fn a_grouped_text_box_is_text_to_a_click() {
|
||||
crate::test_fonts();
|
||||
let mut d = Document::from_text("body text");
|
||||
let id = d.add_part(
|
||||
wordcraft_doc::PartKind::TextBox,
|
||||
vec![wordcraft_doc::para_block(wordcraft_doc::Paragraph::with_text("inside", Default::default()))],
|
||||
);
|
||||
let shape = |kind, w, h, story| InlineObject::Shape {
|
||||
kind,
|
||||
w,
|
||||
h,
|
||||
fill: None,
|
||||
stroke: None,
|
||||
stroke_width: 1.0,
|
||||
float: Default::default(),
|
||||
story,
|
||||
freeform: None,
|
||||
effects: Default::default(),
|
||||
extra: Default::default(),
|
||||
};
|
||||
let float = wordcraft_doc::para::Float {
|
||||
wrap: wordcraft_doc::para::Wrap::InFrontOfText,
|
||||
h_rel: wordcraft_doc::para::Anchor::Page,
|
||||
v_rel: wordcraft_doc::para::Anchor::Page,
|
||||
x: 100.0,
|
||||
y: 300.0,
|
||||
..Default::default()
|
||||
};
|
||||
let group = InlineObject::Group {
|
||||
w: 200.0,
|
||||
h: 100.0,
|
||||
float,
|
||||
ch_w: 200.0,
|
||||
ch_h: 100.0,
|
||||
children: vec![
|
||||
wordcraft_doc::para::GroupChild { x: 0.0, y: 0.0, obj: shape(wordcraft_doc::para::ShapeKind::Rectangle, 200.0, 30.0, None) },
|
||||
wordcraft_doc::para::GroupChild { x: 0.0, y: 40.0, obj: shape(wordcraft_doc::para::ShapeKind::TextBox, 200.0, 60.0, Some(id)) },
|
||||
],
|
||||
canvas: None,
|
||||
};
|
||||
d.insert_object(&Pos::body(0, 0), group, &Default::default()).unwrap();
|
||||
let l = lay(&d);
|
||||
// Inside the text box: its story, and no object to grab.
|
||||
assert_eq!(l.story_at(0, 200.0, 370.0), Some(StoryRef::Part(id)));
|
||||
assert_eq!(l.object_at(0, 200.0, 370.0, 4.0), None);
|
||||
assert_eq!(l.text_box_story(id, 0), Some(StoryRef::Body), "the box's text sits in the body");
|
||||
// On the bar, and on the text box's border: the group.
|
||||
for (x, y) in [(200.0, 315.0), (200.0, 340.5)] {
|
||||
let hit = l.object_at(0, x, y, 4.0).unwrap_or_else(|| panic!("the group at {x}, {y}"));
|
||||
assert_eq!((hit.path.clone(), hit.text_box), (Path::top(0), None));
|
||||
}
|
||||
assert_ne!(l.story_at(0, 200.0, 315.0), Some(StoryRef::Part(id)));
|
||||
// The group keeps its text box's area relative to itself, so the area moves with it.
|
||||
let Some(Placed::Object { rect, group_boxes, .. }) = l.pages[0].items.iter().find(|i| matches!(i, Placed::Object { .. })) else { panic!() };
|
||||
assert_eq!((rect.x, rect.y), (100.0, 300.0));
|
||||
assert_eq!(group_boxes.as_slice(), [(wordcraft_geom::Rect::new(0.0, 40.0, 200.0, 60.0), id)]);
|
||||
}
|
||||
|
||||
/// #6027: a group's text box areas turn with the group (a group in a cell or text box whose
|
||||
/// text runs down the page) and are found in headers and footers too.
|
||||
#[test]
|
||||
fn grouped_text_box_areas_turn_with_the_group_and_work_in_headers() {
|
||||
let id = 7;
|
||||
let group = |rect| Placed::Object {
|
||||
rect,
|
||||
spin: Default::default(),
|
||||
story: StoryRef::Part(1),
|
||||
path: Path::top(0),
|
||||
off: 0,
|
||||
text_box: None,
|
||||
wrap: Default::default(),
|
||||
origin: Default::default(),
|
||||
group_boxes: vec![(wordcraft_geom::Rect::new(0.0, 40.0, 200.0, 60.0), id)],
|
||||
};
|
||||
let mut g = group(wordcraft_geom::Rect::new(0.0, 0.0, 200.0, 100.0));
|
||||
g.turn(TextDirection::Down, 300.0, 50.0);
|
||||
let Placed::Object { rect, group_boxes, .. } = &g else { panic!() };
|
||||
let want = crate::placed::turn_rect(TextDirection::Down, 300.0, 50.0, wordcraft_geom::Rect::new(0.0, 40.0, 200.0, 60.0));
|
||||
let (r, _) = group_boxes[0];
|
||||
assert_eq!(wordcraft_geom::Rect::new(rect.x + r.x, rect.y + r.y, r.w, r.h), want);
|
||||
// A header's group: its box is where a click in the header edits text.
|
||||
let page = Page { header: vec![group(wordcraft_geom::Rect::new(100.0, 30.0, 200.0, 100.0))], ..Default::default() };
|
||||
let l = DocLayout { pages: vec![page], ..Default::default() };
|
||||
assert_eq!(l.header_footer_text_box_at(0, 150.0, 100.0), Some(id));
|
||||
assert_eq!(l.header_footer_text_box_at(0, 150.0, 40.0), None, "the group's bar is not text");
|
||||
}
|
||||
|
||||
/// #6027: where two text boxes of one group overlap, a click enters the one painted on top (the
|
||||
/// last member), in the body and in a header alike.
|
||||
#[test]
|
||||
fn click_in_overlapping_grouped_text_boxes_enters_the_top_one() {
|
||||
let (low, top) = (3, 4);
|
||||
let group = |rect| Placed::Object {
|
||||
rect,
|
||||
spin: Default::default(),
|
||||
story: StoryRef::Part(1),
|
||||
path: Path::top(0),
|
||||
off: 0,
|
||||
text_box: None,
|
||||
wrap: Default::default(),
|
||||
origin: Default::default(),
|
||||
group_boxes: vec![(wordcraft_geom::Rect::new(0.0, 0.0, 100.0, 100.0), low), (wordcraft_geom::Rect::new(50.0, 50.0, 100.0, 100.0), top)],
|
||||
};
|
||||
let page = Page {
|
||||
items: vec![group(wordcraft_geom::Rect::new(100.0, 100.0, 150.0, 150.0))],
|
||||
header: vec![group(wordcraft_geom::Rect::new(100.0, 100.0, 150.0, 150.0))],
|
||||
..Default::default()
|
||||
};
|
||||
let l = DocLayout { pages: vec![page], ..Default::default() };
|
||||
// Overlap (page 175, 175 is inside both boxes): the top box takes the click, and typing.
|
||||
assert_eq!(l.story_at(0, 175.0, 175.0), Some(StoryRef::Part(top)));
|
||||
assert_eq!(l.header_footer_text_box_at(0, 175.0, 175.0), Some(top));
|
||||
// Outside the overlap each box still answers for itself.
|
||||
assert_eq!(l.story_at(0, 110.0, 110.0), Some(StoryRef::Part(low)));
|
||||
assert_eq!(l.story_at(0, 240.0, 240.0), Some(StoryRef::Part(top)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deleted_float_leaves_no_wrap_area_without_markup() {
|
||||
crate::test_fonts();
|
||||
@@ -12451,6 +12572,7 @@ fn split_cell_moves_image_frame() {
|
||||
text_box: None,
|
||||
wrap: Wrap::Square,
|
||||
origin: wordcraft_geom::Point::new(3.0, 145.0),
|
||||
group_boxes: Vec::new(),
|
||||
};
|
||||
let mut kept = image.clone();
|
||||
kept.translate(0.0, top - 150.0);
|
||||
|
||||
@@ -15,7 +15,7 @@ pub(crate) fn part_kind(app: &WordApp, s: StoryRef) -> Option<PartKind> {
|
||||
/// The story a text box's story sits in (where the box is), when the caret is in a text box.
|
||||
pub(crate) fn text_box_host(app: &WordApp, layout: &DocLayout) -> Option<StoryRef> {
|
||||
match app.session.sel.focus.story {
|
||||
StoryRef::Part(id) if in_text_box(app) => layout.text_box(id, app.session.page_hint).map(|o| o.story),
|
||||
StoryRef::Part(id) if in_text_box(app) => layout.text_box_story(id, app.session.page_hint),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,6 +1018,89 @@ fn dragging_a_large_picture_renders_a_preview_within_the_gpu_texture_limit() {
|
||||
assert!(width <= max_side && height <= max_side, "preview is {width}×{height}, texture limit is {max_side}");
|
||||
}
|
||||
|
||||
/// #6027: a click, or a double-click, in the text of a text box inside a group puts the caret
|
||||
/// there, and typing edits that text; the group stays. Before, the click selected the whole
|
||||
/// group and typing replaced it.
|
||||
#[test]
|
||||
fn typing_after_a_click_in_a_grouped_text_box_edits_its_text() {
|
||||
use egui::{Event, PointerButton};
|
||||
for clicks in [1, 2] {
|
||||
let mut d = Document::new();
|
||||
let id = d.add_part(
|
||||
wordcraft_doc::PartKind::TextBox,
|
||||
vec![wordcraft_doc::para_block(wordcraft_doc::Paragraph::with_text("Card", Default::default()))],
|
||||
);
|
||||
let shape = |kind, h, story| InlineObject::Shape {
|
||||
kind,
|
||||
w: 200.0,
|
||||
h,
|
||||
fill: None,
|
||||
stroke: None,
|
||||
stroke_width: 1.0,
|
||||
float: Default::default(),
|
||||
story,
|
||||
freeform: None,
|
||||
effects: Default::default(),
|
||||
extra: Default::default(),
|
||||
};
|
||||
let float = wordcraft_doc::para::Float {
|
||||
wrap: wordcraft_doc::para::Wrap::InFrontOfText,
|
||||
h_rel: wordcraft_doc::para::Anchor::Page,
|
||||
v_rel: wordcraft_doc::para::Anchor::Page,
|
||||
x: 150.0,
|
||||
y: 200.0,
|
||||
..Default::default()
|
||||
};
|
||||
let group = InlineObject::Group {
|
||||
w: 200.0,
|
||||
h: 100.0,
|
||||
float,
|
||||
ch_w: 200.0,
|
||||
ch_h: 100.0,
|
||||
children: vec![
|
||||
wordcraft_doc::para::GroupChild { x: 0.0, y: 0.0, obj: shape(ShapeKind::Rectangle, 30.0, None) },
|
||||
wordcraft_doc::para::GroupChild { x: 0.0, y: 40.0, obj: shape(ShapeKind::TextBox, 60.0, Some(id)) },
|
||||
],
|
||||
canvas: None,
|
||||
};
|
||||
d.insert_object(&Pos::body(0, 0), group, &Default::default()).unwrap();
|
||||
let mut a = WordApp::new(wordcraft_engine::Session::new(d), crate::Services::default());
|
||||
a.run("view.commentsPane", json!({"value": false})).unwrap();
|
||||
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).with_step_dt(1.0 / 60.0).build_ui_state(
|
||||
|ui, app: &mut WordApp| {
|
||||
let ctx = ui.ctx().clone();
|
||||
app.logic(&ctx);
|
||||
app.ui(ui);
|
||||
},
|
||||
a,
|
||||
);
|
||||
for _ in 0..6 {
|
||||
h.step();
|
||||
}
|
||||
// In the box's text, right of "Card" (the box starts 40 pt into the group).
|
||||
let at = crate::canvas::page_to_screen(h.state_mut(), 0, 150.0 + 120.0, 200.0 + 52.0).unwrap();
|
||||
h.event(Event::PointerMoved(at));
|
||||
for _ in 0..clicks {
|
||||
h.event(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: true, modifiers: Default::default() });
|
||||
h.event(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: false, modifiers: Default::default() });
|
||||
}
|
||||
for _ in 0..3 {
|
||||
h.step();
|
||||
}
|
||||
h.event(Event::Text("!".into()));
|
||||
for _ in 0..3 {
|
||||
h.step();
|
||||
}
|
||||
let doc = &h.state().session.doc;
|
||||
let text: String = doc.parts.get(&id).unwrap().blocks.iter().filter_map(|b| b.as_para()).map(|p| p.text.clone()).collect();
|
||||
assert!(text.contains('!'), "{clicks} click(s): typed into the box: {text:?}");
|
||||
assert!(
|
||||
matches!(doc.body.first().and_then(|b| b.as_para()).and_then(|p| p.objects.first()), Some(InlineObject::Group { .. })),
|
||||
"{clicks} click(s): the group is still there"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// #2637: inside a Drawing Canvas the mouse works on the members, as in Word. A click on a
|
||||
/// shape picks that shape (the canvas stays the selection in the text) and shows its frame;
|
||||
/// dragging it moves it inside the canvas; a click in a text box member's text puts the
|
||||
|
||||
Reference in new issue
Block a user