//! The show: full screen, presenter view, keys (SHOW-11, 12). use std::{ collections::{HashMap, HashSet}, path::PathBuf, sync::{Arc, Mutex}, time::{Duration, Instant}, }; use ely_lok::{ Doc, Worker, show::{Part, ShowSlide}, }; use gpui::{AnyWindowHandle, Context, RenderImage, SharedString, Task, WeakEntity}; use crate::deck::Deck; use crate::preview::timeline::{self, Run}; use steps::Place; mod audience; mod keys; mod layers; mod media; mod presenter; mod stage; pub(crate) mod start; pub(crate) mod steps; pub use keys::{bind, bind_keys, menu}; /// A slide's layers as pictures, bottom first. pub type Stack = Arc)>>; /// Clicks long done: a slide shown fully built. const BUILT: f64 = -1.0e6; /// The slide left, as it looked when left. pub struct Leaving { pub stack: Stack, pub runs: Vec, /// Seconds after its entry. pub at: f64, } /// How a step reaches its place. #[derive(Clone, Copy, PartialEq)] enum How { /// The next click group on the same slide. Click, /// The next slide, with its transition. Enter, /// Straight there: jumps, Home, End. Jump, /// Back to a slide, all its effects done. Back, } pub struct Show { deck: WeakEntity, worker: Arc>, /// The copy the show plays; edits never reach it. doc: Doc, copy: PathBuf, pub slides: Vec, /// The slide in 1/100 mm. pub slide: (f32, f32), pixels: (u32, u32), pub place: Place, /// When each click group began, seconds after entry. pub clicks: Vec, pub entered: Instant, /// The slide's layers are on their way; its clock waits. pub waiting: bool, pub leaving: Option, pub black: bool, pub laser: bool, /// The pointer on the slide, as shares. pub pointer: Option<(f32, f32)>, pub began: Instant, layers: HashMap, /// Stacks no longer wanted; freed once nothing draws them. gone: Vec, /// Per window, audience then presenter: images to free. retired: [ely_gpui_component::primitives::Retired; 2], asking: HashSet, /// Digits typed toward a jump. pub typed: String, pub windows: Vec, /// The presenter view is open. pub presenter: bool, /// What went wrong, for the presenter. pub notice: Option, advance: Option>, /// Lets the left slide go once the transition ends. release: Option>, media: Option, ended: bool, } impl Show { pub fn new( deck: WeakEntity, (worker, doc, copy): (Arc>, Doc, PathBuf), (slides, first): (Vec, usize), (slide, pixels): ((f32, f32), (u32, u32)), cx: &mut Context, ) -> Self { let mut show = Show { deck, worker, doc, copy, slides, slide, pixels, place: Place::Slide { part: first, clicks: 0, }, clicks: vec![0.0], entered: Instant::now(), waiting: true, leaving: None, black: false, laser: false, pointer: None, began: Instant::now(), layers: HashMap::new(), gone: Vec::new(), retired: Default::default(), asking: HashSet::new(), typed: String::new(), windows: Vec::new(), presenter: false, notice: None, advance: None, release: None, media: None, ended: false, }; show.fetch(cx); show.plan_advance(cx); show } pub fn part(&self) -> Option { match self.place { Place::Slide { part, .. } => Some(part), Place::End => None, } } /// The next slide the show will reach. pub fn upcoming(&self) -> Option { let from = self.part()?; (from + 1..self.slides.len()).find(|at| !self.slides[*at].hidden) } fn go(&mut self, to: Place, how: How, cx: &mut Context) { self.typed.clear(); let now = match self.waiting { true => 0.0, false => self.entered.elapsed().as_secs_f64(), }; match (how, to) { (How::Click, Place::Slide { part, .. }) => { let at = steps::click_at(&self.slides[part].effects, &self.clicks, now); self.clicks.push(at); } (_, Place::Slide { part, clicks }) => { let transition = &self.slides[part].transition; let moving = how == How::Enter && !transition.preset.is_empty(); self.leaving = (self.part()) .filter(|_| moving) .and_then(|from| { Some(Leaving { stack: self.layers.get(&from)?.clone(), runs: steps::runs(&self.slides[from].effects, &self.clicks), at: now, }) }); let lead = match self.leaving { Some(_) => transition.duration, None => 0.0, }; self.entered = Instant::now(); self.waiting = !self.layers.contains_key(&part); self.clicks = match how { How::Back => vec![BUILT; clicks + 1], _ => vec![lead], }; (self.media, self.notice) = (None, None); } (_, Place::End) => { self.leaving = None; (self.media, self.notice) = (None, None); } } log::info!("show: goes to {to:?}"); self.place = to; self.fetch(cx); self.plan_advance(cx); self.plan_release(cx); cx.notify(); } /// The slide's layers came: its clock starts. pub(super) fn arrived(&mut self, cx: &mut Context) { if std::mem::replace(&mut self.waiting, false) { log::info!("show: the slide is drawn; its clock starts"); self.entered = Instant::now(); self.plan_advance(cx); self.plan_release(cx); } } /// Drops the left slide's layers when the transition ends. fn plan_release(&mut self, cx: &mut Context) { self.release = None; let (Some(part), Some(_)) = (self.part(), &self.leaving) else { return; }; if self.waiting { return; } let rest = self.slides[part].transition.duration - self.entered.elapsed().as_secs_f64(); self.release = Some(cx.spawn(async move |this, cx| { cx.background_executor() .timer(Duration::from_secs_f64(rest.max(0.0))) .await; let dropped = this.update(cx, |show, cx| { show.leaving = None; cx.notify(); }); if dropped.is_err() { log::info!("show: ended before its transition"); } })); } pub fn next(&mut self, cx: &mut Context) { if self.place == Place::End { return self.close(cx); } let to = steps::next(&self.slides, self.place); let how = match (self.part(), to) { (Some(from), Place::Slide { part, .. }) if from == part => How::Click, _ => How::Enter, }; self.go(to, how, cx); } pub fn previous(&mut self, cx: &mut Context) { self.go(steps::previous(&self.slides, self.place), How::Back, cx); } /// Goes to slide `number`, counted from one. pub fn jump(&mut self, number: usize, cx: &mut Context) { if (1..=self.slides.len()).contains(&number) { let to = Place::Slide { part: number - 1, clicks: 0, }; self.go(to, How::Jump, cx); } else { log::info!("show: no slide {number}"); self.typed.clear(); cx.notify(); } } pub fn toggle_black(&mut self, cx: &mut Context) { self.black = !self.black; log::info!("show: black screen {}", self.black); cx.notify(); } pub fn toggle_laser(&mut self, cx: &mut Context) { self.laser = !self.laser; self.pointer = None; log::info!("show: laser pointer {}", self.laser); cx.notify(); } pub fn point(&mut self, at: Option<(f32, f32)>, cx: &mut Context) { if self.laser && self.pointer != at { self.pointer = at; cx.notify(); } } /// When the slide's effects have all run. fn settled_at(&self) -> f64 { let Some(part) = self.part() else { return 0.0; }; let runs = steps::runs(&self.slides[part].effects, &self.clicks); timeline::length(&runs).max(self.clicks.iter().copied().fold(0.0, f64::max)) } /// Whether frames still change. pub fn moving(&self) -> bool { !self.waiting && self.entered.elapsed().as_secs_f64() < self.settled_at() } /// Advances by itself after the slide's time. fn plan_advance(&mut self, cx: &mut Context) { self.advance = None; let Some(part) = self.part() else { return; }; let transition = &self.slides[part].transition; if !transition.auto || self.waiting { return; } let wait = (self.settled_at() - self.entered.elapsed().as_secs_f64()).max(0.0) + transition.time; self.advance = Some(cx.spawn(async move |this, cx| { cx.background_executor() .timer(Duration::from_secs_f64(wait)) .await; if this.update(cx, |show, cx| show.next(cx)).is_err() { log::info!("show: ended before it advanced"); } })); } /// Closes the windows and the copy. pub fn close(&mut self, cx: &mut Context) { if std::mem::replace(&mut self.ended, true) { return; } log::info!("show: ends"); (self.advance, self.release) = (None, None); self.media = None; let (windows, deck, me) = (std::mem::take(&mut self.windows), self.deck.clone(), cx.entity_id()); start::discard(self.worker.clone(), self.doc.clone(), self.copy.clone(), cx); // A key may come from a window being closed. cx.defer(move |cx| { for window in windows { if let Err(error) = window.update(cx, |_, window, _| window.remove_window()) { log::error!("show: a window did not close: {error}"); } } if let Some(deck) = deck.upgrade() { deck.update(cx, |deck, cx| deck.show_ended(me, cx)); } }); } }