Files
ElyOffice/apps/ely-show/tests/compose.rs
T
ZacharyZhang-NY b7fca0146d Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
2026-10-08 21:20:08 -04:00

135 lines
4.5 KiB
Rust

//! Transition frames drawn by Metal, read back as pixels.
#[cfg(target_os = "macos")]
#[path = "../src/preview/transition.rs"]
#[allow(dead_code, unused_imports)]
mod transition;
#[cfg(target_os = "macos")]
#[path = "../src/preview/timeline.rs"]
#[allow(dead_code, unused_imports)]
mod timeline;
#[cfg(target_os = "macos")]
#[path = "../src/preview/compose.rs"]
mod compose;
#[cfg(target_os = "macos")]
fn main() {
use std::sync::Arc;
use ely_lok::show::Part;
use gpui::{
AppContext as _, Context, IntoElement, ParentElement as _, Render, RenderImage,
Styled as _, VisualTestAppContext, Window, div, px, size,
};
use image::{Frame, ImageBuffer, Rgba, RgbaImage};
type Stack = Vec<(Part, Arc<RenderImage>)>;
// Pixels go in as BGRA; `fill` picks the painted columns.
let picture = |bgra: [u8; 4], fill: fn(u32) -> bool| {
let image = ImageBuffer::<Rgba<u8>, _>::from_fn(16, 8, |x, _| match fill(x) {
true => Rgba(bgra),
false => Rgba([0, 0, 0, 0]),
});
Arc::new(RenderImage::new([Frame::new(image)]))
};
let plain = |bgra: [u8; 4]| vec![(Part::Still, picture(bgra, |_| true))];
struct Shot {
old: Stack,
new: Stack,
preset: &'static str,
/// The old slide's effects, as when it left.
runs: Option<Vec<timeline::Run>>,
}
impl Render for Shot {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
let frame = transition::frame(self.preset, 0.5);
let (w, h) = (px(200.0), px(100.0));
let old = match &self.runs {
Some(runs) => compose::left(
(&self.old, runs),
((1600.0, 800.0), 1.0),
(w, h),
gpui::green(),
),
None => {
compose::slide(&self.old, |_| true, (w, h), gpui::green()).into_any_element()
}
};
let shown = compose::transition((Some(old), &self.new), &frame, (w, h), gpui::green());
div().w(w).h(h).relative().child(shown)
}
}
let mut cx = VisualTestAppContext::new(gpui_platform::current_platform(false));
let mut shoot = |shot: Shot| -> RgbaImage {
let window = cx
.open_offscreen_window(size(px(200.0), px(100.0)), |_, cx| cx.new(|_| shot))
.expect("opens a window");
// Pictures load after the first frame.
for _ in 0..3 {
cx.run_until_parked();
cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear(cx))
.expect("draws");
}
cx.capture_screenshot(window.into()).expect("draws")
};
let at = |shot: &RgbaImage, x: f32| {
*shot.get_pixel((shot.width() as f32 * x) as u32, shot.height() / 2)
};
for preset in ["uncover-left", "cover-left", "push-left"] {
let (old, new) = (plain([0, 0, 255, 255]), plain([255, 0, 0, 255]));
let shot = shoot(Shot {
old,
new,
preset,
runs: None,
});
let (left, right) = (at(&shot, 0.25), at(&shot, 0.75));
// Halfway, the old slide is left, the new one right.
assert!(left[0] > 200 && left[2] < 50, "{preset}: left is {left:?}");
assert!(
right[2] > 200 && right[0] < 50,
"{preset}: right is {right:?}"
);
}
// A red object, its exit done, covered from the right.
let object = || {
let moving = Part::Moving {
hash: "gone".into(),
visible: true,
bounds: (0, 0, 400, 800),
};
vec![
(Part::Still, picture([0, 0, 0, 0], |_| true)),
(moving, picture([0, 0, 255, 255], |x| x < 4)),
]
};
let exit = timeline::Run {
hash: "gone".into(),
preset: "ooo-exit-disappear".into(),
begin: 0.0,
end: 0.5,
};
for (runs, red) in [(vec![exit], false), (Vec::new(), true)] {
let shot = shoot(Shot {
old: object(),
new: plain([255, 0, 0, 255]),
preset: "cover-left",
runs: Some(runs),
});
let pixel = at(&shot, 0.1);
assert_eq!(
pixel[0] > 200 && pixel[1] < 50,
red,
"the object is {pixel:?}"
);
}
println!("compose: halfway frames show both slides in order; exits stay gone");
}
#[cfg(not(target_os = "macos"))]
fn main() {
println!("SKIP: the pixel check runs on macOS");
}