use std::collections::{HashMap, HashSet};
use std::sync::mpsc::Receiver;
use std::sync::Arc;
use std::time::{Duration, Instant};
use winit::application::ApplicationHandler;
use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowId, WindowLevel};
use crate::engine::{
self, Audio, Clip, ClipId, Command, Controls, Core, Hold, OutputRect, Planned, Screen, Shared,
};
use crate::error::{Error, Result};
use crate::placement::Monitor;
use crate::render::Gpu;
pub struct Vtome {
excluded: HashSet<String>,
frame_rate: f64,
click_through: bool,
controls: Controls,
commands: Option<Receiver<Command>>,
shared: Arc<Shared>,
}
impl Vtome {
pub fn new<I, S>(excluded_monitors: I, frame_rate: f64) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let frame_rate = engine::sane_frame_rate(frame_rate);
let (controls, commands, shared) = engine::channel(frame_rate);
Vtome {
excluded: excluded_monitors.into_iter().map(Into::into).collect(),
frame_rate,
click_through: true,
controls,
commands: Some(commands),
shared,
}
}
pub fn click_through(mut self, click_through: bool) -> Self {
self.click_through = click_through;
self
}
pub fn controls(&self) -> Controls {
self.controls.clone()
}
pub fn add(&self, clip: Clip) -> Result<ClipId> {
self.controls.add(clip)
}
pub fn add_image(
&self,
path: impl Into<std::path::PathBuf>,
monitor: impl Into<String>,
hold: Hold,
) -> Result<ClipId> {
self.controls.add_image(path, monitor, hold)
}
pub fn add_frame(
&self,
frame: crate::frame::Frame,
monitor: impl Into<String>,
hold: Hold,
) -> Result<ClipId> {
self.controls.add_frame(frame, monitor, hold)
}
pub fn add_generic(
&self,
path: impl Into<std::path::PathBuf>,
monitor: impl Into<String>,
) -> Result<ClipId> {
self.controls.add_generic(path, monitor)
}
pub fn add_cached(
&self,
clip: Clip,
ranges: Vec<(u64, u64)>,
frames: Vec<crate::frame::Frame>,
) -> Result<ClipId> {
self.controls.add_cached(clip, ranges, frames)
}
pub fn stop(&self, id: ClipId) -> bool {
self.controls.stop(id)
}
pub fn is_running(&self, id: ClipId) -> bool {
self.controls.is_running(id)
}
pub fn is_finished(&self, id: ClipId) -> bool {
self.controls.is_finished(id)
}
pub fn snapshot(&self, id: ClipId) -> Result<crate::frame::Frame> {
self.controls.snapshot(id)
}
pub fn start(
&mut self,
outputs: HashMap<String, OutputRect>,
backgrounds: HashMap<String, String>,
audio: Audio,
) -> Result<()> {
self.start_with(audio, move |_| (outputs, backgrounds))
}
pub fn start_with<F>(&mut self, audio: Audio, choose: F) -> Result<()>
where
F: FnOnce(&[Monitor]) -> (HashMap<String, OutputRect>, HashMap<String, String>) + 'static,
{
let commands = self.commands.take().ok_or_else(|| {
Error::unsupported("this Vtome has already run; an event loop runs once per process")
})?;
let event_loop = EventLoop::new().map_err(|error| Error::Render {
reason: format!("the event loop would not start: {error}"),
})?;
let mut host = Host {
choose: Some(Box::new(choose)),
audio,
excluded: self.excluded.clone(),
click_through: self.click_through,
commands: Some(commands),
shared: Arc::clone(&self.shared),
period: Duration::from_secs_f64(1.0 / self.frame_rate),
next: Instant::now(),
core: None,
failure: None,
};
let outcome = event_loop.run_app(&mut host);
self.controls.close();
outcome.map_err(|error| Error::Render {
reason: format!("the event loop stopped: {error}"),
})?;
match host.failure {
Some(error) => Err(error),
None => Ok(()),
}
}
}
type Choose = Box<dyn FnOnce(&[Monitor]) -> (HashMap<String, OutputRect>, HashMap<String, String>)>;
struct Host {
choose: Option<Choose>,
audio: Audio,
excluded: HashSet<String>,
click_through: bool,
commands: Option<Receiver<Command>>,
shared: Arc<Shared>,
period: Duration,
next: Instant,
core: Option<Core<Arc<Window>>>,
failure: Option<Error>,
}
impl Host {
fn open(&mut self, event_loop: &ActiveEventLoop) -> Result<Core<Arc<Window>>> {
let attached = crate::window::attached(event_loop);
let choose = self
.choose
.take()
.ok_or_else(|| Error::unsupported("the engine was already opened"))?;
let (outputs, backgrounds) = choose(&attached);
let plans = engine::plan(&outputs, &backgrounds, &self.excluded, &attached, &self.shared)?;
let windows = plans
.iter()
.map(|plan| self.window(event_loop, plan))
.collect::<Result<Vec<_>>>()?;
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle(
Box::new(Arc::clone(&windows[0])),
));
let surfaces = windows
.iter()
.map(|window| {
instance
.create_surface(Arc::clone(window))
.map_err(|error| Error::Render {
reason: format!("no surface for an output window: {error}"),
})
})
.collect::<Result<Vec<_>>>()?;
let gpu = Gpu::from_instance(instance, surfaces.first())?;
let screens = plans
.iter()
.zip(windows)
.zip(surfaces)
.map(|((plan, window), surface)| Screen::new(plan, window, surface, &gpu))
.collect();
let commands = self
.commands
.take()
.ok_or_else(|| Error::unsupported("the engine was already opened"))?;
Core::new(
gpu,
&plans,
screens,
commands,
Arc::clone(&self.shared),
&self.audio,
)
}
fn window(&self, event_loop: &ActiveEventLoop, plan: &Planned) -> Result<Arc<Window>> {
let attributes = Window::default_attributes()
.with_title(format!("vtome — {}", plan.monitor))
.with_decorations(false)
.with_transparent(true)
.with_resizable(false)
.with_active(false)
.with_window_level(WindowLevel::AlwaysOnTop)
.with_position(PhysicalPosition::new(plan.x, plan.y))
.with_inner_size(PhysicalSize::new(plan.width, plan.height));
#[cfg(target_os = "macos")]
let attributes = {
use winit::platform::macos::WindowAttributesExtMacOS;
attributes.with_has_shadow(false)
};
let window = event_loop
.create_window(attributes)
.map_err(|error| Error::Render {
reason: format!("the window for {} would not open: {error}", plan.monitor),
})?;
if self.click_through {
window
.set_cursor_hittest(false)
.map_err(|error| Error::Render {
reason: format!("this platform will not let clicks through a window: {error}"),
})?;
}
Ok(Arc::new(window))
}
fn fail(&mut self, event_loop: &ActiveEventLoop, error: Error) {
self.failure = Some(error);
event_loop.exit();
}
}
impl ApplicationHandler for Host {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.core.is_some() || self.failure.is_some() {
return;
}
match self.open(event_loop) {
Ok(core) => {
self.core = Some(core);
self.next = Instant::now();
event_loop.set_control_flow(ControlFlow::WaitUntil(self.next));
}
Err(error) => self.fail(event_loop, error),
}
}
fn window_event(&mut self, _: &ActiveEventLoop, _: WindowId, _: WindowEvent) {}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
let Some(core) = self.core.as_mut() else {
return;
};
let now = Instant::now();
if now >= self.next {
match core.refresh() {
Ok(true) => {}
Ok(false) => {
event_loop.exit();
return;
}
Err(error) => {
self.fail(event_loop, error);
return;
}
}
self.next += self.period;
if self.next < now {
self.next = now + self.period;
}
}
event_loop.set_control_flow(ControlFlow::WaitUntil(self.next));
}
}