-
Notifications
You must be signed in to change notification settings - Fork 8
/
multi_window.rs
273 lines (243 loc) · 9.58 KB
/
multi_window.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#[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);
}