#[macro_use] extern crate mini_gl_fb; extern crate glutin; use mini_gl_fb::glutin::event_loop::EventLoop; use mini_gl_fb::glutin::event::{Event, WindowEvent, MouseButton, VirtualKeyCode, KeyboardInput, ElementState}; use mini_gl_fb::{get_fancy, GlutinBreakout}; use mini_gl_fb::glutin::dpi::{LogicalSize, LogicalPosition}; use mini_gl_fb::glutin::window::{Window, WindowId, CursorIcon}; use mini_gl_fb::glutin::event_loop::ControlFlow; use mini_gl_fb::glutin::platform::run_return::EventLoopExtRunReturn; /// Turn up this number to make the pixels bigger. 1 is one logical pixel const SCALE_FACTOR: f64 = 2.; /// A window being tracked by a `MultiWindow`. All tracked windows will be forwarded all events /// received on the `MultiWindow`'s event loop. trait TrackedWindow { /// Handles one event from the event loop. Returns true if the window needs to be kept alive, /// otherwise it will be closed. Window events should be checked to ensure that their ID is one /// that the TrackedWindow is interested in. fn handle_event(&mut self, event: &Event<()>) -> bool; } /// Manages multiple `TrackedWindow`s by forwarding events to them. struct MultiWindow { windows: Vec>>, } impl MultiWindow { /// Creates a new `MultiWindow`. pub fn new() -> Self { MultiWindow { windows: vec![], } } /// Adds a new `TrackedWindow` to the `MultiWindow`. pub fn add(&mut self, window: Box) { self.windows.push(Some(window)) } /// Runs the event loop until all `TrackedWindow`s are closed. pub fn run(&mut self, event_loop: &mut EventLoop<()>) { if !self.windows.is_empty() { event_loop.run_return(|event, _, flow| { *flow = ControlFlow::Wait; for option in &mut self.windows { if let Some(window) = option.as_mut() { if !window.handle_event(&event) { option.take(); } } } self.windows.retain(Option::is_some); if self.windows.is_empty() { *flow = ControlFlow::Exit; } }); } } } /// A basic window that allows you to draw in it. An example of how to implement a `TrackedWindow`. struct DrawWindow { pub breakout: GlutinBreakout, pub buffer: Vec<[u8; 4]>, pub buffer_size: LogicalSize, pub bg: [u8; 4], pub fg: [u8; 4], mouse_state: ElementState, line_start: Option>, } impl DrawWindow { fn window(&self) -> &Window { self.breakout.context.window() } pub fn matches_id(&self, id: WindowId) -> bool { id == self.window().id() } /// Updates the window's buffer. Should only be done inside of RedrawRequested events; outside /// of them, use `request_redraw` instead. fn redraw(&mut self) { self.breakout.fb.update_buffer(&self.buffer); self.breakout.context.swap_buffers().unwrap(); } /// Requests a redraw event for this window. fn request_redraw(&self) { self.window().request_redraw(); } /// Resizes the window's buffer to a new size, attempting to preserve the current content as /// much as possible. Fills new space with the background color, and deletes overflowing space. fn resize(&mut self, new_size: LogicalSize) { let mut new_buffer = vec![self.bg; new_size.width as usize * new_size.height as usize]; if self.buffer_size.width > 0 { // use rchunks for inverted y for (old_line, new_line) in self.buffer.chunks_exact(self.buffer_size.width as usize) .zip(new_buffer.chunks_exact_mut(new_size.width as usize)) { if old_line.len() <= new_line.len() { new_line[0..old_line.len()].copy_from_slice(old_line) } else { new_line.copy_from_slice(&old_line[0..new_line.len()]) } } } self.buffer = new_buffer; self.buffer_size = new_size; self.breakout.fb.resize_buffer(new_size.width, new_size.height); } fn plot(&mut self, position: LogicalPosition) { if position.x < 0 || position.x >= self.buffer_size.width as i32 || position.y < 0 || position.y >= self.buffer_size.height as i32 { return } let position = position.cast::(); let index = (position.x + position.y * self.buffer_size.width) as usize; self.buffer[index] = self.fg; } // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm fn plot_line(&mut self, start: LogicalPosition, end: LogicalPosition) { let (mut x0, mut y0): (i32, i32) = start.into(); let (x1, y1): (i32, i32) = end.into(); let dx = (x1 - x0).abs(); let sx = if x0 < x1 { 1 } else { -1 }; let dy = -(y1 - y0).abs(); let sy = if y0 < y1 { 1 } else { -1 }; let mut err = dx + dy; while x0 != x1 || y0 != y1 { self.plot(LogicalPosition::new(x0, y0)); let e2 = err * 2; if e2 > dy { err += dy; x0 += sx; } if e2 <= dx { err += dx; y0 += sy; } } self.plot(end); } /// Creates a new `DrawWindow` for the specified event loop, using the specified background and /// foreground colors. pub fn new(event_loop: &EventLoop<()>, bg: [u8; 4], fg: [u8; 4]) -> Self { let mut new = Self { breakout: get_fancy(config! { resizable: true, invert_y: false }, &event_loop).glutin_breakout(), buffer: vec![], buffer_size: LogicalSize::new(0, 0), bg, fg, mouse_state: ElementState::Released, line_start: None, }; new.resize(new.window().inner_size().to_logical(new.window().scale_factor() * SCALE_FACTOR)); new.window().set_cursor_icon(CursorIcon::Crosshair); new } } impl TrackedWindow for DrawWindow { fn handle_event(&mut self, event: &Event<()>) -> bool { match *event { Event::WindowEvent { window_id: id, event: WindowEvent::CloseRequested, .. } if self.matches_id(id) => { return false; } Event::WindowEvent { window_id: id, event: WindowEvent::KeyboardInput { input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::Escape), state: ElementState::Pressed, .. }, .. }, .. } if self.matches_id(id) => { if let Some(_) = self.window().fullscreen() { self.window().set_fullscreen(None); } else { return false; } } Event::RedrawRequested(id) if self.matches_id(id) => { unsafe { self.breakout.make_current().unwrap(); } self.redraw(); } Event::WindowEvent { window_id: id, event: WindowEvent::Resized(size), .. } if self.matches_id(id) => { unsafe { self.breakout.make_current().unwrap(); } self.breakout.fb.resize_viewport(size.width, size.height); self.resize(size.to_logical(self.window().scale_factor() * SCALE_FACTOR)); self.request_redraw(); } Event::WindowEvent { window_id: id, event: WindowEvent::MouseInput { button: MouseButton::Left, state, .. }, .. } if self.matches_id(id) => { self.mouse_state = state; self.line_start = None; } Event::WindowEvent { window_id: id, event: WindowEvent::CursorMoved { position, .. }, .. } if self.matches_id(id) => { if self.mouse_state == ElementState::Pressed { let inner_size = self.window().inner_size(); let position = LogicalPosition::new( ((position.x / inner_size.width as f64) * self.buffer_size.width as f64).floor(), ((position.y / inner_size.height as f64) * self.buffer_size.height as f64).floor() ).cast::(); if let Some(line_start) = self.line_start { self.plot_line(line_start, position); } else { self.plot(position); } self.line_start = Some(position); self.request_redraw(); } } _ => {} } true } } fn main() { let mut event_loop = EventLoop::new(); let mut multi_window = MultiWindow::new(); multi_window.add(Box::new(DrawWindow::new(&event_loop, [25u8, 33, 40, 255], [54u8, 165, 209, 255]))); multi_window.add(Box::new(DrawWindow::new(&event_loop, [25u8, 40, 33, 255], [54u8, 209, 82, 255]))); multi_window.add(Box::new(DrawWindow::new(&event_loop, [40u8, 33, 25, 255], [209u8, 82, 54, 255]))); multi_window.run(&mut event_loop); }