mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
Merge PR #3879: M7: an added picture resized by a corner shows only at its new size
Conflict resolutions: - crates/ui-egui/tests/content.rs: both test sets appended at the file end kept (main's format-text test, then #3879's picture-resize helpers and test).
This commit is contained in:
@@ -109,8 +109,9 @@ impl ContentView {
|
||||
}
|
||||
}
|
||||
|
||||
/// While an added item is moved, the page without it, rendered once per gesture: the item's own
|
||||
/// pixels follow the pointer over it instead of staying put until release (#1488).
|
||||
/// While an added item is moved, or a picture resized by a corner, the page without it, rendered
|
||||
/// once per gesture: the item's own pixels follow the pointer over it instead of staying put until
|
||||
/// release (#1488).
|
||||
#[derive(Default)]
|
||||
pub(crate) struct MoveBackground {
|
||||
key: Option<(u64, usize, usize)>,
|
||||
@@ -119,6 +120,8 @@ pub(crate) struct MoveBackground {
|
||||
tag: Option<u64>,
|
||||
/// The item's display-space box when the gesture started.
|
||||
from: Option<[f64; 4]>,
|
||||
/// The item is a picture: a corner stretches its pixels, and it keeps its whole applied box.
|
||||
picture: bool,
|
||||
/// The applied move's box. The page raster on screen still has the item where it was until
|
||||
/// its replacement arrives, so the preview stays up until then.
|
||||
settling: Option<[f64; 4]>,
|
||||
@@ -130,8 +133,11 @@ impl MoveBackground {
|
||||
}
|
||||
|
||||
pub fn prepare(&mut self, ctx: &egui::Context, doc: &pdfcraft_engine::Document, cv: &ContentView, scale: f32) {
|
||||
let picture = |page, index| on_page(doc.added(), page).into_iter().any(|(i, a)| i == index && matches!(a.content, AddedContent::Image(_)));
|
||||
let key = match (cv.grab, cv.selected) {
|
||||
(Some((gp, Grab::Move, _)), Some((sp, si))) if gp == sp => Some((doc.edit_generation(), sp, si)),
|
||||
// Text resized by a corner rewraps, so only a picture's pixels can follow the corner.
|
||||
(Some((gp, Grab::Corner(..), _)), Some((sp, si))) if gp == sp && picture(sp, si) => Some((doc.edit_generation(), sp, si)),
|
||||
_ => None,
|
||||
};
|
||||
if !(key.is_none() && self.settling.is_some()) && self.key != key {
|
||||
@@ -139,6 +145,7 @@ impl MoveBackground {
|
||||
if let Some((_, page, index)) = key {
|
||||
self.renderer = doc.added_content_preview(page, index).ok().map(|p| p.renderer());
|
||||
self.from = on_page(doc.added(), page).into_iter().find(|(i, _)| *i == index).map(|(_, a)| a.content.rect());
|
||||
self.picture = picture(page, index);
|
||||
}
|
||||
}
|
||||
if self.settling.is_some() {
|
||||
@@ -208,22 +215,29 @@ impl MoveBackground {
|
||||
return;
|
||||
}
|
||||
let r = screen_rect(xf, info, page, from);
|
||||
let d = if let Some((_, _, start)) = cv.grab {
|
||||
let to = if let Some((_, grab, start)) = cv.grab {
|
||||
let Some(p) = ui.input(|i| i.pointer.hover_pos()) else { return };
|
||||
p - start
|
||||
dragged(r, grab, p - start, self.picture)
|
||||
} else if let Some(to) = self.settling {
|
||||
let to = screen_rect(xf, info, page, to);
|
||||
// A moved text box keeps its top edge (its height follows its lines).
|
||||
screen_rect(xf, info, page, to).left_top() - r.left_top()
|
||||
if self.picture { to } else { r.translate(to.left_top() - r.left_top()) }
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let painter = painter.with_clip_rect(painter.clip_rect().intersect(xf.rect));
|
||||
xf.paint_image(&painter, background.id(), 0.0, 0.0, 1.0, 1.0);
|
||||
let r = r.expand(1.0);
|
||||
// A pixel around the box, so its edges come along.
|
||||
let (r, to) = (r.expand(1.0), to.expand(1.0));
|
||||
let mut mesh = egui::Mesh::with_texture(page_tex);
|
||||
for c in [r.left_top(), r.right_top(), r.right_bottom(), r.left_bottom()] {
|
||||
let (u, v) = xf.screen_to_norm(c);
|
||||
mesh.vertices.push(egui::epaint::Vertex { pos: c + d, uv: Pos2::new(u, v), color: Color32::WHITE });
|
||||
for (a, b) in [
|
||||
(r.left_top(), to.left_top()),
|
||||
(r.right_top(), to.right_top()),
|
||||
(r.right_bottom(), to.right_bottom()),
|
||||
(r.left_bottom(), to.left_bottom()),
|
||||
] {
|
||||
let (u, v) = xf.screen_to_norm(a);
|
||||
mesh.vertices.push(egui::epaint::Vertex { pos: b, uv: Pos2::new(u, v), color: Color32::WHITE });
|
||||
}
|
||||
mesh.add_triangle(0, 1, 2);
|
||||
mesh.add_triangle(0, 2, 3);
|
||||
|
||||
@@ -642,3 +642,78 @@ fn format_text_panel_sets_alignment_and_lists_planned_controls_disabled() {
|
||||
h.run_steps(2);
|
||||
assert!(h.state().text_style.bold);
|
||||
}
|
||||
|
||||
/// Red pixels in `r`: the test picture.
|
||||
fn red(pixels: &image::RgbaImage, r: egui::Rect) -> usize {
|
||||
let (x0, y0, x1, y1) = (r.left().max(0.0) as u32, r.top().max(0.0) as u32, r.right() as u32, r.bottom() as u32);
|
||||
(y0..y1.min(pixels.height()))
|
||||
.flat_map(|y| (x0..x1.min(pixels.width())).map(move |x| (x, y)))
|
||||
.filter(|(x, y)| matches!(pixels.get_pixel(*x, *y).0, [r, g, b, _] if r > 150 && g < 90 && b < 90))
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Steps until no render is pending.
|
||||
fn settle(h: &mut Harness<'static, PdfCraftApp>) {
|
||||
for _ in 0..200 {
|
||||
h.run_steps(1);
|
||||
if !h.state().render_pending() {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
panic!("the page did not finish rendering");
|
||||
}
|
||||
|
||||
/// An added picture resized by a corner is drawn at its new size while dragged and after the
|
||||
/// release until the page is rendered again. It used to stay at its old size under the outline for
|
||||
/// the whole drag, and to show there again after the release.
|
||||
#[test]
|
||||
fn a_resized_added_picture_shows_only_at_its_new_size() {
|
||||
let mut h = harness();
|
||||
assert!(h.state_mut().execute("edit.edit_text"));
|
||||
h.run_steps(2);
|
||||
let mut png = Vec::new();
|
||||
{
|
||||
let mut enc = png::Encoder::new(&mut png, 60, 60);
|
||||
enc.set_color(png::ColorType::Rgb);
|
||||
let mut w = enc.write_header().unwrap();
|
||||
w.write_image_data(&[200u8, 30, 30].repeat(3600)).unwrap();
|
||||
}
|
||||
h.state_mut().add_image("red.png".into(), png);
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().views[0].content.selected, Some((0, 0)), "the picture is selected");
|
||||
settle(&mut h);
|
||||
let rect = added(&h)[0].content.rect();
|
||||
let r = item_on_screen(&h, rect);
|
||||
// The lower right corner to the middle: half the size, the upper left corner kept.
|
||||
let (grip, to) = (r.expand(1.0).right_bottom(), r.center());
|
||||
let kept = egui::Rect::from_min_max(r.min, r.center()).shrink(3.0);
|
||||
let gone = egui::Rect::from_min_max(r.center() + egui::vec2(4.0, 4.0), r.max - egui::vec2(3.0, 3.0));
|
||||
h.hover_at(grip);
|
||||
h.run_steps(1);
|
||||
h.drag_at(grip);
|
||||
h.run_steps(1);
|
||||
for k in 1..=4 {
|
||||
h.hover_at(grip + (to - grip) * (k as f32 / 4.0));
|
||||
h.run_steps(1);
|
||||
}
|
||||
settle(&mut h);
|
||||
let pixels = h.render().unwrap();
|
||||
assert!(red(&pixels, kept) > 100, "while dragged, the picture is drawn at its new size");
|
||||
assert_eq!(red(&pixels, gone), 0, "while dragged, the picture shows at its old size");
|
||||
assert_eq!(added(&h)[0].content.rect(), rect, "nothing is applied before release");
|
||||
h.state_mut().views[0].hold_rasters = true;
|
||||
h.drop_at(to);
|
||||
for frame in 0..4 {
|
||||
h.step();
|
||||
let pixels = h.render().unwrap();
|
||||
assert!(red(&pixels, kept) > 100, "frame {frame} after the release: the picture is not at its new size");
|
||||
assert_eq!(red(&pixels, gone), 0, "frame {frame} after the release: the picture shows at its old size");
|
||||
}
|
||||
assert_ne!(added(&h)[0].content.rect(), rect, "the resize is applied");
|
||||
h.state_mut().views[0].hold_rasters = false;
|
||||
settle(&mut h);
|
||||
h.run_steps(2);
|
||||
let pixels = h.render().unwrap();
|
||||
assert!(red(&pixels, kept) > 100 && red(&pixels, gone) == 0, "the page raster has the picture at its new size");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user