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.
This commit is contained in:
817 files changed
+143101
No files matched your search
@@ -0,0 +1,147 @@
|
||||
//! The show: keys, hidden slides, black, laser (C10.5).
|
||||
|
||||
use ely_office::prelude::*;
|
||||
use gpui::{Entity, Keystroke, TestAppContext};
|
||||
|
||||
use super::objects::{act, blank};
|
||||
use super::{settle, update};
|
||||
use crate::deck::Deck;
|
||||
use crate::slideshow::{Show, steps::Place};
|
||||
|
||||
fn press(cx: &mut TestAppContext, show: &Entity<Show>, key: &str) -> bool {
|
||||
let key = Keystroke::parse(key).expect("a key");
|
||||
cx.update(|app| show.update(app, |show, cx| show.key(&key, cx)))
|
||||
}
|
||||
|
||||
/// Keys typed into the show's own window.
|
||||
pub(super) fn typed(cx: &mut TestAppContext, show: &Entity<Show>, keys: &str) {
|
||||
let window = cx.update(|app| show.read(app).windows[0]);
|
||||
cx.simulate_keystrokes(window, keys);
|
||||
}
|
||||
|
||||
pub(super) fn place(cx: &mut TestAppContext, show: &Entity<Show>) -> Place {
|
||||
cx.update(|app| show.read(app).place)
|
||||
}
|
||||
|
||||
/// Slide two: click effect, notes. Three: hidden.
|
||||
pub(super) fn three_slides(cx: &mut TestAppContext, deck: &Entity<Deck>) {
|
||||
let slides = |app: &gpui::App| {
|
||||
let tab = deck.read(app).active_tab();
|
||||
tab.map(|tab| tab.extra.slides.list.iter().map(|s| s.visible).collect::<Vec<_>>())
|
||||
};
|
||||
update(cx, deck, |deck, cx| deck.design_command("slide.insert", &[], cx));
|
||||
settle(cx, |app| slides(app).is_some_and(|list| list.len() == 2));
|
||||
act(cx, deck, ("draw.shape", &["rectangle", "true"]), |o| {
|
||||
o.objects.len() == 3
|
||||
});
|
||||
act(cx, deck, ("object.select", &["2", "false"]), |o| {
|
||||
o.selection.is_some()
|
||||
});
|
||||
let add = serde_json::json!({ "preset": "ooo-entrance-fade-in" }).to_string();
|
||||
let effects = |app: &gpui::App| {
|
||||
let tab = deck.read(app).active_tab();
|
||||
tab.and_then(|tab| tab.extra.motion.as_ref().map(|(_, e)| e.len()))
|
||||
};
|
||||
update(cx, deck, |deck, cx| deck.design_command("animation.add", &[&add], cx));
|
||||
settle(cx, |app| effects(app) == Some(1));
|
||||
update(cx, deck, |deck, cx| {
|
||||
deck.design_command("notes.set", &["Say hello", "1"], cx);
|
||||
deck.design_command("slide.insert", &[], cx);
|
||||
});
|
||||
settle(cx, |app| slides(app).is_some_and(|list| list.len() == 3));
|
||||
update(cx, deck, |deck, cx| deck.design_command("slide.hide", &[], cx));
|
||||
settle(cx, |app| slides(app) == Some(vec![true, true, false]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_show_plays_clicks_skips_hidden_and_ends() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let Some(deck) = blank(&mut cx, dir.path()) else {
|
||||
eprintln!("SKIP: no LOK build");
|
||||
return;
|
||||
};
|
||||
three_slides(&mut cx, &deck);
|
||||
update(&mut cx, &deck, |deck, cx| deck.start_show(false, cx));
|
||||
settle(&cx, |app| deck.read(app).show.is_some());
|
||||
let show = cx.update(|app| deck.read(app).show.clone().expect("playing"));
|
||||
settle(&cx, |app| {
|
||||
let show = show.read(app);
|
||||
show.stack(0).is_some() && show.stack(1).is_some()
|
||||
});
|
||||
let windows = cx.update(|app| show.read(app).windows.len());
|
||||
assert_eq!(windows, 1, "one screen: no presenter view");
|
||||
assert_eq!(place(&mut cx, &show), Place::Slide { part: 0, clicks: 0 });
|
||||
typed(&mut cx, &show, "right");
|
||||
assert_eq!(place(&mut cx, &show), Place::Slide { part: 1, clicks: 0 });
|
||||
let notes = cx.update(|app| show.read(app).slides[1].notes.clone());
|
||||
assert_eq!(notes, "Say hello");
|
||||
press(&mut cx, &show, "space");
|
||||
assert_eq!(place(&mut cx, &show), Place::Slide { part: 1, clicks: 1 });
|
||||
press(&mut cx, &show, "right");
|
||||
assert_eq!(place(&mut cx, &show), Place::End, "the hidden slide is passed");
|
||||
press(&mut cx, &show, "left");
|
||||
assert_eq!(place(&mut cx, &show), Place::Slide { part: 1, clicks: 1 });
|
||||
press(&mut cx, &show, "1");
|
||||
press(&mut cx, &show, "enter");
|
||||
assert_eq!(place(&mut cx, &show), Place::Slide { part: 0, clicks: 0 });
|
||||
press(&mut cx, &show, "b");
|
||||
press(&mut cx, &show, "l");
|
||||
assert!(cx.update(|app| show.read(app).black && show.read(app).laser));
|
||||
assert!(!press(&mut cx, &show, "cmd-q"), "app keys pass through");
|
||||
let before = cx.update(|app| app.windows().len());
|
||||
typed(&mut cx, &show, "escape");
|
||||
settle(&cx, |app| deck.read(app).show.is_none());
|
||||
assert_eq!(cx.update(|app| app.windows().len()), before - 1, "the show window closes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_here_starts_on_the_shown_slide_even_hidden() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let Some(deck) = blank(&mut cx, dir.path()) else {
|
||||
eprintln!("SKIP: no LOK build");
|
||||
return;
|
||||
};
|
||||
three_slides(&mut cx, &deck);
|
||||
update(&mut cx, &deck, |deck, cx| deck.start_show(true, cx));
|
||||
settle(&cx, |app| deck.read(app).show.is_some());
|
||||
let show = cx.update(|app| deck.read(app).show.clone().expect("playing"));
|
||||
assert_eq!(place(&mut cx, &show), Place::Slide { part: 2, clicks: 0 });
|
||||
press(&mut cx, &show, "right");
|
||||
assert_eq!(place(&mut cx, &show), Place::End);
|
||||
press(&mut cx, &show, "right");
|
||||
settle(&cx, |app| deck.read(app).show.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_click_on_media_finds_it_and_elsewhere_does_not() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let Some(deck) = blank(&mut cx, dir.path()) else {
|
||||
eprintln!("SKIP: no LOK build");
|
||||
return;
|
||||
};
|
||||
let clip = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../tests/fixtures/media/clip.mp4");
|
||||
let url = ely_lok::file_url(&clip);
|
||||
update(&mut cx, &deck, |deck, cx| {
|
||||
deck.design_command("media.insert", &[&url, "false", "8000", "4500"], cx)
|
||||
});
|
||||
settle(&cx, |app| {
|
||||
deck.read(app).active_tab().is_some_and(|tab| tab.extra.media.len() == 1)
|
||||
});
|
||||
update(&mut cx, &deck, |deck, cx| deck.start_show(true, cx));
|
||||
settle(&cx, |app| deck.read(app).show.is_some());
|
||||
let show = cx.update(|app| deck.read(app).show.clone().expect("playing"));
|
||||
let (bounds, size) = cx.update(|app| {
|
||||
let show = show.read(app);
|
||||
(show.slides[0].media[0].bounds, show.slide)
|
||||
});
|
||||
let centre = (
|
||||
(bounds.0 + bounds.2 / 2) as f32 / size.0,
|
||||
(bounds.1 + bounds.3 / 2) as f32 / size.1,
|
||||
);
|
||||
assert_eq!(cx.update(|app| show.read(app).media_at(centre)), Some(0));
|
||||
assert_eq!(cx.update(|app| show.read(app).media_at((0.01, 0.01))), None);
|
||||
}
|
||||
Reference in new issue
Block a user