mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-11 16:30:12 -06:00
fix(engine): drag with the Text tool to make a fixed-width text box (#360)
The Text tool made its text on the press and ignored the drag, so a drag gave auto-width text like a click. The new text now stays in a TextBox gesture while pressed: past the drag slop it becomes a box as wide as the drag, its top-left at the drag's, with a fixed width and a height that follows its text. A click, or a drag within the slop, still makes auto-width text.
This commit is contained in:
@@ -192,3 +192,38 @@ fn paragraph_indent_only_moves_left_aligned_text() {
|
||||
let (x, w2) = first_x(&s);
|
||||
assert!((w2 - (w - 30.0)).abs() < 1e-6 && x.abs() < 1e-6, "centered auto-width text: no indent, the box shrinks by it");
|
||||
}
|
||||
|
||||
/// A new text made with the Text tool by a press at `from`, a drag to `to` and a release, holding
|
||||
/// `chars`: its box and resize mode.
|
||||
fn text_tool(from: (f64, f64), to: (f64, f64), chars: &str) -> (figcraft_geom::Rect, figcraft_doc::TextAutoResize) {
|
||||
use crate::tools::{PointerEvent, PointerKind, ToolId};
|
||||
let mut s = Session::new();
|
||||
s.set_tool(ToolId::Text);
|
||||
s.pointer(&PointerEvent::at(PointerKind::Down, from.0, from.1, 1.0)).unwrap();
|
||||
s.pointer(&PointerEvent::at(PointerKind::Move, to.0, to.1, 1.0)).unwrap();
|
||||
s.pointer(&PointerEvent::at(PointerKind::Up, to.0, to.1, 1.0)).unwrap();
|
||||
crate::textedit::insert(&mut s, chars).unwrap();
|
||||
let t = s.ds().unwrap().text_edit.as_ref().unwrap().id;
|
||||
let doc = s.doc().unwrap();
|
||||
let NodeKind::Text(td) = &doc.node(t).unwrap().kind else { panic!("not a text") };
|
||||
(doc.world_bounds(t).unwrap(), td.auto_resize)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_drag_with_the_text_tool_makes_a_fixed_width_box() {
|
||||
// #360: as wide as the drag, its top-left where the drag started, and its text wraps in it; a
|
||||
// click, or a drag within a few pixels, still makes auto-width text.
|
||||
use figcraft_doc::TextAutoResize;
|
||||
figcraft_text::scan_system_fonts();
|
||||
let long = "A sentence much longer than eighty pixels";
|
||||
let (line, auto) = text_tool((100.0, 100.0), (100.0, 100.0), long);
|
||||
assert_eq!(auto, TextAutoResize::WidthAndHeight);
|
||||
assert_eq!(text_tool((100.0, 100.0), (101.0, 101.0), long).1, TextAutoResize::WidthAndHeight, "a slip of the hand is a click");
|
||||
let (b, auto) = text_tool((100.0, 100.0), (180.0, 160.0), long);
|
||||
assert_eq!(auto, TextAutoResize::Height);
|
||||
assert_eq!((b.x0, b.y0, b.width()), (100.0, 100.0, 80.0));
|
||||
assert!(b.height() > line.height(), "the text wraps: {} vs one line {}", b.height(), line.height());
|
||||
// Dragged up and to the left, the box is the same.
|
||||
let (b2, _) = text_tool((180.0, 160.0), (100.0, 100.0), long);
|
||||
assert_eq!((b2.x0, b2.y0, b2.width()), (100.0, 100.0, 80.0));
|
||||
}
|
||||
|
||||
@@ -370,6 +370,11 @@ pub(crate) enum Gesture {
|
||||
TextSelect {
|
||||
id: NodeId,
|
||||
},
|
||||
/// A new text from the Text tool, still pressed: a drag makes it a box as wide as the drag.
|
||||
TextBox {
|
||||
id: NodeId,
|
||||
start: Point,
|
||||
},
|
||||
PenDrag {
|
||||
anchor: Point,
|
||||
},
|
||||
@@ -1347,6 +1352,11 @@ impl Session {
|
||||
self.tool.gesture = g;
|
||||
return self.draw_drag(ev, start, id, parent, &targets);
|
||||
}
|
||||
Gesture::TextBox { id, start } => {
|
||||
let (id, start) = (*id, *start);
|
||||
self.tool.gesture = g;
|
||||
return self.text_box_drag(ev, id, start);
|
||||
}
|
||||
Gesture::Arc { id, handle, arc } => {
|
||||
let (id, handle, arc) = (*id, *handle, *arc);
|
||||
self.tool.gesture = g;
|
||||
@@ -1760,7 +1770,7 @@ impl Session {
|
||||
self.tool.overlay.pen.clear();
|
||||
self.pencil_finish(&points, &pressure)?;
|
||||
}
|
||||
Gesture::TextSelect { .. } | Gesture::Marquee { .. } | Gesture::None => {}
|
||||
Gesture::TextSelect { .. } | Gesture::TextBox { .. } | Gesture::Marquee { .. } | Gesture::None => {}
|
||||
Gesture::PenDrag { .. } | Gesture::ShapeBuild { .. } | Gesture::Origin => {}
|
||||
Gesture::Vector(_) => self.commit_interaction()?,
|
||||
}
|
||||
@@ -1888,12 +1898,36 @@ impl Session {
|
||||
d.insert(n, parent, None)?;
|
||||
ds.selection = vec![id];
|
||||
textedit::begin(self, id, Some(0), true)?;
|
||||
self.tool.gesture = Gesture::TextBox { id, start: ev.p() };
|
||||
if !self.prefs.keep_tool {
|
||||
self.tool.tool = ToolId::Move;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Drag out a new text (Figma's text box): past the drag slop it is as wide as the drag, its
|
||||
/// top-left at the drag's, and its height follows its text.
|
||||
fn text_box_drag(&mut self, ev: &PointerEvent, id: NodeId, start: Point) -> Result<()> {
|
||||
if within_slop(ev, start) {
|
||||
return Ok(());
|
||||
}
|
||||
let r = Rect::from_points(start, ev.p());
|
||||
let d = self.ds_mut()?.doc_mut();
|
||||
let pw = d.node(id).and_then(|n| n.parent).map(|p| d.world_transform(p)).unwrap_or_default();
|
||||
let n = d.get_mut(id)?;
|
||||
n.transform = pw.inverse() * Affine::translate((r.x0.round(), r.y0.round()));
|
||||
n.width = r.width().round().max(1.0);
|
||||
n.child_layout.sizing_h = Sizing::Fixed;
|
||||
if let NodeKind::Text(t) = &mut n.kind {
|
||||
t.auto_resize = TextAutoResize::Height;
|
||||
}
|
||||
if let Some((_, h)) = figcraft_text::auto_size(n) {
|
||||
n.height = h;
|
||||
}
|
||||
self.ds_mut()?.revision += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eyedrop(&mut self, ev: &PointerEvent) -> Result<()> {
|
||||
let ds = self.ds()?;
|
||||
let doc = ds.doc.clone();
|
||||
|
||||
Reference in New Issue
Block a user