mini_gl_fb/examples/multi_window.rs
LoganDark 02b3b62520
Update glutin, docs, and adds a few other features (including multi window support) (#5)
Upgrades glutin, docs, and adds a few other features (including multi window support). But not the readme. That comes next.
2021-01-20 22:12:52 -05:00

274 lines
9.6 KiB
Rust

#[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<Option<Box<dyn TrackedWindow>>>,
}
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<dyn TrackedWindow>) {
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<u32>,
pub bg: [u8; 4],
pub fg: [u8; 4],
mouse_state: ElementState,
line_start: Option<LogicalPosition<i32>>,
}
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<u32>) {
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<i32>) {
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::<u32>();
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<i32>, end: LogicalPosition<i32>) {
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::<i32>();
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);
}