// Copyright (c) 2022 Marceline Cramer // SPDX-License-Identifier: AGPL-3.0-or-later use std::collections::HashMap; use std::path::PathBuf; use std::time::Instant; use canary::{CursorEventKind, Panel, Runtime, Vec2, PX_PER_MM}; use glium::backend::glutin::DisplayCreationError; use glium::{glutin, Surface}; use glutin::event::{ElementState, Event, MouseButton, WindowEvent}; use glutin::event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget}; use glutin::window::WindowId; use crate::service::gl::Graphics; use crate::service::ipc::{IpcMessage, IpcMessageSender}; #[derive(Clone, Debug)] pub enum WindowMessage { OpenWindow { id: usize, protocol: String, script: PathBuf, }, CloseWindow { id: usize, }, Quit, SendMessage { id: usize, msg: Vec, }, } pub type WindowMessageSender = EventLoopProxy; pub struct Window { pub ipc_sender: IpcMessageSender, pub ipc_id: usize, pub graphics: Graphics, pub panel: Panel, pub last_update: Instant, pub cursor_pos: Vec2, pub cursor_down: bool, } impl Window { pub fn new( ipc_sender: IpcMessageSender, ipc_id: usize, panel: Panel, event_loop: &EventLoopWindowTarget, ) -> Result { let wb = glutin::window::WindowBuilder::new() .with_transparent(true) .with_decorations(false); let cb = glutin::ContextBuilder::new() .with_vsync(true) .with_multisampling(4); let display = glium::Display::new(wb, cb, &event_loop)?; let graphics = Graphics::new(display); let last_update = Instant::now(); Ok(Self { ipc_sender, ipc_id, graphics, panel, last_update, cursor_pos: Vec2::ZERO, cursor_down: false, }) } pub fn get_id(&self) -> WindowId { self.graphics.display.gl_window().window().id() } pub fn request_redraw(&mut self) { self.graphics.display.gl_window().window().request_redraw(); } /// Receives all messages from the script and forwards them to IPC. pub fn recv_messages(&mut self) { for message in self.panel.recv_messages() { self.ipc_sender.send(IpcMessage::PanelMessage { window: self.ipc_id, message, }); } } pub fn update(&mut self) { let now = Instant::now(); let dt = now.duration_since(self.last_update).as_secs_f32(); self.panel.update(dt); self.last_update = now; self.recv_messages(); } pub fn draw(&mut self) { let commands = self.panel.draw(); self.graphics.draw(&commands); self.recv_messages(); } pub fn send_message(&mut self, msg: Vec) { self.panel.on_message(msg); self.recv_messages(); } pub fn resize(&mut self, new_size: Vec2) { self.panel.on_resize(new_size); self.recv_messages(); } pub fn on_event(&mut self, event: WindowEvent) { match event { WindowEvent::Resized(size) => { self.resize(Vec2::new(size.width as f32, size.height as f32) * PX_PER_MM); self.request_redraw() } WindowEvent::CursorMoved { position, .. } => { let x = position.x as f32 * PX_PER_MM; let y = position.y as f32 * PX_PER_MM; self.cursor_pos = Vec2::new(x, y); let event = if self.cursor_down { CursorEventKind::Drag } else { CursorEventKind::Hover }; self.panel.on_cursor_event(event, self.cursor_pos); self.recv_messages(); } WindowEvent::MouseInput { state, button: MouseButton::Left, .. } => { let event = match state { ElementState::Pressed => { self.cursor_down = true; CursorEventKind::Select } ElementState::Released => { self.cursor_down = false; CursorEventKind::Deselect } }; self.panel.on_cursor_event(event, self.cursor_pos); self.recv_messages(); } _ => {} } } } pub struct WindowStore { pub ipc_sender: IpcMessageSender, pub ipc_to_window: HashMap, pub windows: HashMap, pub runtime: Runtime, } impl WindowStore { pub fn new(ipc_sender: IpcMessageSender) -> Self { let backend = canary::backend::make_default_backend().unwrap(); let runtime = Runtime::new(backend).unwrap(); Self { ipc_sender, ipc_to_window: Default::default(), windows: Default::default(), runtime, } } pub fn get_ipc_window(&mut self, id: usize) -> Option<&mut Window> { self.ipc_to_window .get(&id) .map(|id| self.windows.get_mut(id)) .flatten() } pub fn on_message( &mut self, event_loop: &EventLoopWindowTarget, message: WindowMessage, ) -> anyhow::Result { match message { WindowMessage::OpenWindow { id, protocol, script, } => { println!("Opening window {} with script {:?}", id, script); let module = std::fs::read(script)?; let mut script = self.runtime.load_module(&module)?; let panel = script.create_panel(&protocol, vec![])?; let window = Window::new(self.ipc_sender.to_owned(), id, panel, &event_loop)?; let window_id = window.get_id(); self.windows.insert(window_id, window); self.ipc_to_window.insert(id, window_id); } WindowMessage::CloseWindow { id } => { if let Some(window_id) = self.ipc_to_window.remove(&id) { self.windows.remove(&window_id); } } WindowMessage::Quit => return Ok(true), WindowMessage::SendMessage { id, msg } => { if let Some(window) = self.get_ipc_window(id) { window.send_message(msg); } } }; Ok(false) } pub fn run(mut self, event_loop: EventLoop) -> ! { event_loop.run(move |event, event_loop, control_flow| match event { Event::WindowEvent { window_id, event } => { if let Some(window) = self.windows.get_mut(&window_id) { window.on_event(event); } } Event::RedrawRequested(id) => { if let Some(window) = self.windows.get_mut(&id) { window.draw(); } } Event::MainEventsCleared => { for (_id, window) in self.windows.iter_mut() { window.update(); window.request_redraw(); } } Event::UserEvent(event) => match self.on_message(event_loop, event.clone()) { Ok(false) => {} Ok(true) => *control_flow = ControlFlow::Exit, Err(err) => { eprintln!("Error while handling message {:?}:\n{}", event, err); } }, _ => {} }); } }