mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-30 05:27:06 -06:00
Text (#93): show gray markers on empty list paragraphs
This commit is contained in:
@@ -222,6 +222,9 @@ pub struct RenderOpts<'a> {
|
||||
pub skip: &'a [ShapeId],
|
||||
/// Hide the text of this shape (the UI draws it while editing).
|
||||
pub skip_text: Option<ShapeId>,
|
||||
/// Text being edited: replace its prompt with gray markers on empty list paragraphs.
|
||||
/// Ignored outside editor rendering.
|
||||
pub editing_text: Option<ShapeId>,
|
||||
/// Animated GIF pictures and how far into their play they are (seconds); others show frame 1.
|
||||
pub gif_times: &'a [(ShapeId, f64)],
|
||||
/// Worker threads (0 = this thread; required when effects use filters).
|
||||
@@ -246,6 +249,7 @@ impl Default for RenderOpts<'_> {
|
||||
state: None,
|
||||
skip: &[],
|
||||
skip_text: None,
|
||||
editing_text: None,
|
||||
gif_times: &[],
|
||||
threads: 0,
|
||||
offset: (0.0, 0.0),
|
||||
@@ -921,7 +925,7 @@ impl Renderer {
|
||||
}
|
||||
ShapeKind::Table(t) => {
|
||||
// Rows grow with their text: what the cells cover, borders included.
|
||||
let cells = table::draw(ctx, f.pres, rctx, t, m, x.w, x.h, &f.fields);
|
||||
let cells = table::draw(ctx, f.pres, rctx, t, m, x.w, x.h, &f.fields, f.opts.edit && f.opts.editing_text == Some(s.id));
|
||||
self.track(m, cells.unwrap_or(Rect::new(0.0, 0.0, x.w, x.h)).inflate(2.0, 2.0));
|
||||
}
|
||||
ShapeKind::Chart(c) => {
|
||||
@@ -1300,8 +1304,21 @@ impl Renderer {
|
||||
&text_geo
|
||||
};
|
||||
let tr = geo.text_rect;
|
||||
if !body.is_empty() {
|
||||
let l = deckcraft_text::layout(rctx, s, body, &Opts { rect: tr, fields: &f.fields, prompt_color: None, no_shrink: false });
|
||||
let editing = f.opts.edit && f.opts.editing_text == Some(s.id);
|
||||
if !body.is_empty() || editing {
|
||||
// Imported placeholders can have no paragraph until the first keystroke.
|
||||
let mut empty;
|
||||
let body = if editing && body.paragraphs.is_empty() {
|
||||
empty = body.clone();
|
||||
empty.paragraphs.push(Default::default());
|
||||
&empty
|
||||
} else {
|
||||
body
|
||||
};
|
||||
let mut l = deckcraft_text::layout(rctx, s, body, &Opts { rect: tr, fields: &f.fields, prompt_color: None, no_shrink: false });
|
||||
if editing {
|
||||
preview_empty_bullets(&mut l);
|
||||
}
|
||||
let tm = text_transform(m, tr, l.rotation);
|
||||
for b in l.lines.iter().filter_map(line_bounds) {
|
||||
// Glyphs reach a little past their carets and line box (italics, swashes).
|
||||
@@ -2034,6 +2051,15 @@ pub fn draw_text(ctx: &mut RenderContext, rctx: &Ctx, s: &Shape, body: &TextBody
|
||||
draw_layout(ctx, &l, tr, m);
|
||||
}
|
||||
|
||||
/// Add transient list markers to an editor layout, using the document's own bullet glyphs.
|
||||
fn preview_empty_bullets(l: &mut deckcraft_text::TextLayout) {
|
||||
for mut run in l.empty_bullets.drain(..) {
|
||||
run.color = Rgba::rgb(0xB3, 0xB3, 0xB3);
|
||||
run.alpha = 1.0;
|
||||
l.runs.push(run);
|
||||
}
|
||||
}
|
||||
|
||||
/// Where text laid out in text rect `tr` is drawn, for a shape drawn with `m` (shape-local →
|
||||
/// output): `m`, without mirroring, then the layout's `rotation` about the rect's centre.
|
||||
///
|
||||
|
||||
@@ -327,6 +327,7 @@ pub fn draw(
|
||||
_w: f64,
|
||||
_h: f64,
|
||||
fields: &dyn deckcraft_text::Fields,
|
||||
editing: bool,
|
||||
) -> Option<kurbo::Rect> {
|
||||
let _ = pres;
|
||||
let rects = cell_rects(t, &row_heights(rctx, t, fields));
|
||||
@@ -418,14 +419,18 @@ pub fn draw(
|
||||
// Text.
|
||||
for (r, c, rect) in &rects {
|
||||
let Some(cell) = t.cell(*r, *c) else { continue };
|
||||
if cell.text.is_empty() || text_off_target(cell, *rect, m, (ctx.width(), ctx.height())) {
|
||||
if (cell.text.is_empty() && !editing) || text_off_target(cell, *rect, m, (ctx.width(), ctx.height())) {
|
||||
continue;
|
||||
}
|
||||
let body = cell_body(rctx, t, *r, *c, cell);
|
||||
let tmp = Shape { text: Some(body.clone()), ..Default::default() };
|
||||
#[cfg(test)]
|
||||
TEXT_CELLS_DRAWN.with(|n| n.set(n.get() + 1));
|
||||
crate::draw_text(ctx, rctx, &tmp, &body, *rect, m, fields, None);
|
||||
let mut l = deckcraft_text::layout(rctx, &tmp, &body, &deckcraft_text::Opts { rect: *rect, fields, prompt_color: None, no_shrink: false });
|
||||
if editing {
|
||||
crate::preview_empty_bullets(&mut l);
|
||||
}
|
||||
crate::draw_layout(ctx, &l, *rect, m);
|
||||
let _ = peniko::Fill::NonZero;
|
||||
}
|
||||
covered
|
||||
|
||||
@@ -2509,3 +2509,30 @@ fn single_series_column_chart_varies_colours_by_point() {
|
||||
};
|
||||
assert_ne!(render(true), render(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_bullet_is_gray_only_while_editing_and_never_in_exports() {
|
||||
use deckcraft_model::text::{Bullet, Paragraph};
|
||||
let mut para = Paragraph::default();
|
||||
para.props.bullet = Some(Bullet::Char { char: "•".into() });
|
||||
para.props.margin_left = Some(24.0);
|
||||
para.props.indent = Some(-24.0);
|
||||
para.end_props.size = Some(32.0);
|
||||
let sh = Shape {
|
||||
id: ShapeId(901),
|
||||
xfrm: Some(Xfrm::new(100.0, 100.0, 400.0, 120.0)),
|
||||
text: Some(TextBody { paragraphs: vec![para], ..Default::default() }),
|
||||
..Default::default()
|
||||
};
|
||||
let p = deck_with(vec![sh]);
|
||||
let render = |edit, editing_text| render_slide(&p, 0, &RenderOpts { edit, editing_text, ..Default::default() });
|
||||
let plain = render(false, None);
|
||||
assert_eq!(plain.pixels, render(true, None).pixels);
|
||||
assert_eq!(plain.pixels, render(false, Some(ShapeId(901))).pixels, "export ignores editor markers");
|
||||
assert_eq!(plain.pixels, render(true, Some(ShapeId(902))).pixels, "other shapes stay empty");
|
||||
let editing = render(true, Some(ShapeId(901)));
|
||||
let changed: Vec<_> = editing.pixels.chunks_exact(4).zip(plain.pixels.chunks_exact(4)).filter(|(a, b)| a != b).collect();
|
||||
assert!(!changed.is_empty(), "an empty bullet must be visible");
|
||||
assert!(changed.iter().any(|(px, _)| px[0] == 179 && px[1] == 179 && px[2] == 179));
|
||||
assert!(changed.iter().all(|(px, _)| px[0] >= 179 && px[0] == px[1] && px[1] == px[2]));
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ fn copy_opts<'a>(o: &RenderOpts<'a>) -> RenderOpts<'a> {
|
||||
state: o.state,
|
||||
skip: o.skip,
|
||||
skip_text: o.skip_text,
|
||||
editing_text: o.editing_text,
|
||||
gif_times: o.gif_times,
|
||||
threads: o.threads,
|
||||
offset: o.offset,
|
||||
|
||||
Reference in New Issue
Block a user