1use chargrid_input::{keys, Input, KeyboardInput, MouseButton, MouseInput, ScrollDirection};
2use chargrid_runtime::{app, on_frame, on_input, Component, Coord, FrameBuffer, Rgba32, Size};
3use ggez::winit;
4use std::time::Instant;
5
6const FONT_NAME_NORMAL: &'static str = "normal";
7const FONT_NAME_BOLD: &'static str = "bold";
8
9pub struct FontBytes {
10 pub normal: Vec<u8>,
11 pub bold: Vec<u8>,
12}
13
14#[derive(Clone, Copy, Debug)]
15pub struct Dimensions<T> {
16 pub width: T,
17 pub height: T,
18}
19
20pub struct Config {
21 pub title: String,
22 pub font_bytes: FontBytes,
23 pub window_dimensions_px: Dimensions<f64>,
24 pub cell_dimensions_px: Dimensions<f64>,
25 pub font_scale: Dimensions<f64>,
26 pub underline_width_cell_ratio: f64,
27 pub underline_top_offset_cell_ratio: f64,
28 pub resizable: bool,
29}
30
31pub struct Context {
32 config: Config,
33}
34
35struct Fonts {
36 normal: ggez::graphics::FontData,
37 bold: ggez::graphics::FontData,
38}
39
40struct GgezApp<C>
41where
42 C: 'static + Component<State = (), Output = app::Output>,
43{
44 chargrid_core: C,
45 chargrid_frame_buffer: FrameBuffer,
46 last_frame: Instant,
47 font_scale: ggez::graphics::PxScale,
48 underline_mesh: ggez::graphics::Mesh,
49 background_mesh: ggez::graphics::Mesh,
50 cell_width: f32,
51 cell_height: f32,
52 current_mouse_button: Option<MouseButton>,
53 current_mouse_position: Coord,
54 #[cfg(feature = "gamepad")]
55 gamepad_id_to_integer_id: hashbrown::HashMap<ggez::event::GamepadId, u64>,
56}
57
58impl<C> GgezApp<C>
59where
60 C: 'static + Component<State = (), Output = app::Output>,
61{
62 fn convert_mouse_position(&self, x: f32, y: f32) -> Coord {
63 Coord {
64 x: (x / self.cell_width) as i32,
65 y: (y / self.cell_height) as i32,
66 }
67 }
68
69 fn convert_mouse_button(button: ggez::event::MouseButton) -> Option<MouseButton> {
70 match button {
71 ggez::input::mouse::MouseButton::Left => Some(MouseButton::Left),
72 ggez::input::mouse::MouseButton::Right => Some(MouseButton::Right),
73 ggez::input::mouse::MouseButton::Middle => Some(MouseButton::Middle),
74 ggez::input::mouse::MouseButton::Other(_) => None,
75 }
76 }
77}
78
79impl<C> ggez::event::EventHandler<ggez::GameError> for GgezApp<C>
80where
81 C: 'static + Component<State = (), Output = app::Output>,
82{
83 fn update(&mut self, ctx: &mut ggez::Context) -> ggez::GameResult {
84 const DESIRED_FPS: u32 = 60;
85 while ctx.time.check_update_time(DESIRED_FPS) {
86 let now = Instant::now();
87 if let Some(app::Exit) = on_frame(
88 &mut self.chargrid_core,
89 now - self.last_frame,
90 &mut self.chargrid_frame_buffer,
91 ) {
92 ctx.request_quit();
93 }
94 self.last_frame = now;
95 }
96 Ok(())
97 }
98
99 fn draw(&mut self, ctx: &mut ggez::Context) -> ggez::GameResult {
100 let mut canvas =
101 ggez::graphics::Canvas::from_frame(ctx, ggez::graphics::Color::from([0., 0., 0., 1.]));
102 for (coord, cell) in self.chargrid_frame_buffer.enumerate() {
103 if cell.character != ' ' {
104 let mut text = ggez::graphics::Text::new(cell.character);
105 let font = if cell.bold {
106 FONT_NAME_BOLD
107 } else {
108 FONT_NAME_NORMAL
109 };
110 text.set_font(font);
111 text.set_scale(self.font_scale);
112 canvas.draw(
113 &text,
114 ggez::graphics::DrawParam::new()
115 .dest([
116 coord.x as f32 * self.cell_width,
117 coord.y as f32 * self.cell_height,
118 ])
119 .color(cell.foreground.to_f32_array_01())
120 .z(1),
121 );
122 }
123 if cell.background != Rgba32::new(0, 0, 0, 255) {
124 canvas.draw(
125 &self.background_mesh,
126 ggez::graphics::DrawParam::new()
127 .dest([
128 coord.x as f32 * self.cell_width,
129 coord.y as f32 * self.cell_height,
130 ])
131 .color(cell.background.to_f32_array_01()),
132 );
133 }
134 if cell.underline {
135 canvas.draw(
136 &self.underline_mesh,
137 ggez::graphics::DrawParam::default()
138 .dest(ggez::mint::Point2 {
139 x: coord.x as f32 * self.cell_width,
140 y: coord.y as f32 * self.cell_height,
141 })
142 .color(cell.foreground.to_f32_array_01()),
143 );
144 }
145 }
146 canvas.finish(ctx).unwrap();
147 ggez::timer::yield_now();
148 Ok(())
149 }
150
151 fn resize_event(
152 &mut self,
153 _ctx: &mut ggez::Context,
154 _width: f32,
155 _height: f32,
156 ) -> Result<(), ggez::GameError> {
157 Ok(())
158 }
159
160 fn key_down_event(
161 &mut self,
162 ctx: &mut ggez::Context,
163 input: ggez::input::keyboard::KeyInput,
164 _repeat: bool,
165 ) -> Result<(), ggez::GameError> {
166 if let Some(keycode) = input.keycode {
167 use winit::event::VirtualKeyCode;
168 let key_char_shift = |lower: char, upper: char| {
169 KeyboardInput::Char(
170 if input.mods.contains(ggez::input::keyboard::KeyMods::SHIFT) {
171 upper
172 } else {
173 lower
174 },
175 )
176 };
177 let keyboard_input = match keycode {
178 VirtualKeyCode::A => key_char_shift('a', 'A'),
179 VirtualKeyCode::B => key_char_shift('b', 'B'),
180 VirtualKeyCode::C => key_char_shift('c', 'C'),
181 VirtualKeyCode::D => key_char_shift('d', 'D'),
182 VirtualKeyCode::E => key_char_shift('e', 'E'),
183 VirtualKeyCode::F => key_char_shift('f', 'F'),
184 VirtualKeyCode::G => key_char_shift('g', 'G'),
185 VirtualKeyCode::H => key_char_shift('h', 'H'),
186 VirtualKeyCode::I => key_char_shift('i', 'I'),
187 VirtualKeyCode::J => key_char_shift('j', 'J'),
188 VirtualKeyCode::K => key_char_shift('k', 'K'),
189 VirtualKeyCode::L => key_char_shift('l', 'L'),
190 VirtualKeyCode::M => key_char_shift('m', 'M'),
191 VirtualKeyCode::N => key_char_shift('n', 'N'),
192 VirtualKeyCode::O => key_char_shift('o', 'O'),
193 VirtualKeyCode::P => key_char_shift('p', 'P'),
194 VirtualKeyCode::Q => key_char_shift('q', 'Q'),
195 VirtualKeyCode::R => key_char_shift('r', 'R'),
196 VirtualKeyCode::S => key_char_shift('s', 'S'),
197 VirtualKeyCode::T => key_char_shift('t', 'T'),
198 VirtualKeyCode::U => key_char_shift('u', 'U'),
199 VirtualKeyCode::V => key_char_shift('v', 'V'),
200 VirtualKeyCode::W => key_char_shift('w', 'W'),
201 VirtualKeyCode::X => key_char_shift('x', 'X'),
202 VirtualKeyCode::Y => key_char_shift('y', 'Y'),
203 VirtualKeyCode::Z => key_char_shift('z', 'Z'),
204 VirtualKeyCode::Key1 => KeyboardInput::Char('1'),
205 VirtualKeyCode::Key2 => KeyboardInput::Char('2'),
206 VirtualKeyCode::Key3 => KeyboardInput::Char('3'),
207 VirtualKeyCode::Key4 => KeyboardInput::Char('4'),
208 VirtualKeyCode::Key5 => KeyboardInput::Char('5'),
209 VirtualKeyCode::Key6 => KeyboardInput::Char('6'),
210 VirtualKeyCode::Key7 => KeyboardInput::Char('7'),
211 VirtualKeyCode::Key8 => KeyboardInput::Char('8'),
212 VirtualKeyCode::Key9 => KeyboardInput::Char('9'),
213 VirtualKeyCode::Key0 => KeyboardInput::Char('0'),
214 VirtualKeyCode::Numpad1 => KeyboardInput::Char('1'),
215 VirtualKeyCode::Numpad2 => KeyboardInput::Char('2'),
216 VirtualKeyCode::Numpad3 => KeyboardInput::Char('3'),
217 VirtualKeyCode::Numpad4 => KeyboardInput::Char('4'),
218 VirtualKeyCode::Numpad5 => KeyboardInput::Char('5'),
219 VirtualKeyCode::Numpad6 => KeyboardInput::Char('6'),
220 VirtualKeyCode::Numpad7 => KeyboardInput::Char('7'),
221 VirtualKeyCode::Numpad8 => KeyboardInput::Char('8'),
222 VirtualKeyCode::Numpad9 => KeyboardInput::Char('9'),
223 VirtualKeyCode::Numpad0 => KeyboardInput::Char('0'),
224 VirtualKeyCode::F1 => KeyboardInput::Function(1),
225 VirtualKeyCode::F2 => KeyboardInput::Function(2),
226 VirtualKeyCode::F3 => KeyboardInput::Function(3),
227 VirtualKeyCode::F4 => KeyboardInput::Function(4),
228 VirtualKeyCode::F5 => KeyboardInput::Function(5),
229 VirtualKeyCode::F6 => KeyboardInput::Function(6),
230 VirtualKeyCode::F7 => KeyboardInput::Function(7),
231 VirtualKeyCode::F8 => KeyboardInput::Function(8),
232 VirtualKeyCode::F9 => KeyboardInput::Function(9),
233 VirtualKeyCode::F10 => KeyboardInput::Function(10),
234 VirtualKeyCode::F11 => KeyboardInput::Function(11),
235 VirtualKeyCode::F12 => KeyboardInput::Function(12),
236 VirtualKeyCode::F13 => KeyboardInput::Function(13),
237 VirtualKeyCode::F14 => KeyboardInput::Function(14),
238 VirtualKeyCode::F15 => KeyboardInput::Function(15),
239 VirtualKeyCode::F16 => KeyboardInput::Function(16),
240 VirtualKeyCode::F17 => KeyboardInput::Function(17),
241 VirtualKeyCode::F18 => KeyboardInput::Function(18),
242 VirtualKeyCode::F19 => KeyboardInput::Function(19),
243 VirtualKeyCode::F20 => KeyboardInput::Function(20),
244 VirtualKeyCode::F21 => KeyboardInput::Function(21),
245 VirtualKeyCode::F22 => KeyboardInput::Function(22),
246 VirtualKeyCode::F23 => KeyboardInput::Function(23),
247 VirtualKeyCode::F24 => KeyboardInput::Function(24),
248 VirtualKeyCode::At => KeyboardInput::Char('@'),
249 VirtualKeyCode::Plus => KeyboardInput::Char('+'),
250 VirtualKeyCode::Minus => KeyboardInput::Char('-'),
251 VirtualKeyCode::Equals => key_char_shift('=', '+'),
252 VirtualKeyCode::Backslash => KeyboardInput::Char('\\'),
253 VirtualKeyCode::Grave => KeyboardInput::Char('`'),
254 VirtualKeyCode::Apostrophe => KeyboardInput::Char('\''),
255 VirtualKeyCode::LBracket => KeyboardInput::Char('['),
256 VirtualKeyCode::RBracket => KeyboardInput::Char(']'),
257 VirtualKeyCode::Period => KeyboardInput::Char('.'),
258 VirtualKeyCode::Comma => KeyboardInput::Char(','),
259 VirtualKeyCode::Slash => KeyboardInput::Char('/'),
260 VirtualKeyCode::NumpadAdd => KeyboardInput::Char('+'),
261 VirtualKeyCode::NumpadSubtract => KeyboardInput::Char('-'),
262 VirtualKeyCode::NumpadMultiply => KeyboardInput::Char('*'),
263 VirtualKeyCode::NumpadDivide => KeyboardInput::Char('/'),
264 VirtualKeyCode::PageUp => KeyboardInput::PageUp,
265 VirtualKeyCode::PageDown => KeyboardInput::PageDown,
266 VirtualKeyCode::Home => KeyboardInput::Home,
267 VirtualKeyCode::End => KeyboardInput::End,
268 VirtualKeyCode::Up => KeyboardInput::Up,
269 VirtualKeyCode::Down => KeyboardInput::Down,
270 VirtualKeyCode::Left => KeyboardInput::Left,
271 VirtualKeyCode::Right => KeyboardInput::Right,
272 VirtualKeyCode::Return => keys::RETURN,
273 VirtualKeyCode::Escape => keys::ESCAPE,
274 VirtualKeyCode::Space => KeyboardInput::Char(' '),
275 VirtualKeyCode::Back => keys::BACKSPACE,
276 VirtualKeyCode::Delete => KeyboardInput::Delete,
277 other => {
278 log::warn!("Unhandled input: {:?}", other);
279 return Ok(());
280 }
281 };
282 if let Some(app::Exit) = on_input(
283 &mut self.chargrid_core,
284 Input::Keyboard(keyboard_input),
285 &self.chargrid_frame_buffer,
286 ) {
287 ctx.request_quit();
288 }
289 }
290 Ok(())
291 }
292
293 fn mouse_button_down_event(
294 &mut self,
295 ctx: &mut ggez::Context,
296 button: ggez::event::MouseButton,
297 x: f32,
298 y: f32,
299 ) -> Result<(), ggez::GameError> {
300 if let Some(button) = Self::convert_mouse_button(button) {
301 self.current_mouse_button = Some(button);
302 let coord = self.convert_mouse_position(x, y);
303 self.current_mouse_position = coord;
304 let input = MouseInput::MousePress { button, coord };
305 if let Some(app::Exit) = on_input(
306 &mut self.chargrid_core,
307 Input::Mouse(input),
308 &self.chargrid_frame_buffer,
309 ) {
310 ctx.request_quit();
311 }
312 }
313 Ok(())
314 }
315
316 fn mouse_button_up_event(
317 &mut self,
318 ctx: &mut ggez::Context,
319 button: ggez::event::MouseButton,
320 x: f32,
321 y: f32,
322 ) -> Result<(), ggez::GameError> {
323 if let Some(button) = Self::convert_mouse_button(button) {
324 self.current_mouse_button = None;
325 let coord = self.convert_mouse_position(x, y);
326 self.current_mouse_position = coord;
327 let input = MouseInput::MouseRelease {
328 button: Ok(button),
329 coord,
330 };
331 if let Some(app::Exit) = on_input(
332 &mut self.chargrid_core,
333 Input::Mouse(input),
334 &self.chargrid_frame_buffer,
335 ) {
336 ctx.request_quit();
337 }
338 }
339 Ok(())
340 }
341
342 fn mouse_motion_event(
343 &mut self,
344 ctx: &mut ggez::Context,
345 x: f32,
346 y: f32,
347 _dx: f32,
348 _dy: f32,
349 ) -> Result<(), ggez::GameError> {
350 let coord = self.convert_mouse_position(x, y);
351 self.current_mouse_position = coord;
352 let input = MouseInput::MouseMove {
353 coord,
354 button: self.current_mouse_button,
355 };
356 if let Some(app::Exit) = on_input(
357 &mut self.chargrid_core,
358 Input::Mouse(input),
359 &self.chargrid_frame_buffer,
360 ) {
361 ctx.request_quit();
362 }
363 Ok(())
364 }
365
366 fn mouse_wheel_event(
367 &mut self,
368 ctx: &mut ggez::Context,
369 x: f32,
370 y: f32,
371 ) -> Result<(), ggez::GameError> {
372 let mut handle = |direction| {
373 let coord = self.current_mouse_position;
374 let input = MouseInput::MouseScroll { direction, coord };
375 if let Some(app::Exit) = on_input(
376 &mut self.chargrid_core,
377 Input::Mouse(input),
378 &self.chargrid_frame_buffer,
379 ) {
380 ctx.request_quit();
381 }
382 };
383 if x > 0.0 {
384 handle(ScrollDirection::Right);
385 }
386 if x < 0.0 {
387 handle(ScrollDirection::Left);
388 }
389 if y > 0.0 {
390 handle(ScrollDirection::Up);
391 }
392 if y < 0.0 {
393 handle(ScrollDirection::Down);
394 }
395 Ok(())
396 }
397
398 fn quit_event(&mut self, _ctx: &mut ggez::Context) -> Result<bool, ggez::GameError> {
399 Ok(false)
400 }
401
402 #[cfg(feature = "gamepad")]
403 fn gamepad_button_down_event(
404 &mut self,
405 ctx: &mut ggez::Context,
406 btn: ggez::event::Button,
407 id: ggez::event::GamepadId,
408 ) -> Result<(), ggez::GameError> {
409 use chargrid_input::{GamepadButton, GamepadInput};
410 let num_gamepad_ids = self.gamepad_id_to_integer_id.len() as u64;
411 let &mut integer_id = self
412 .gamepad_id_to_integer_id
413 .entry(id)
414 .or_insert(num_gamepad_ids);
415 let button = match btn {
416 ggez::event::Button::DPadUp => GamepadButton::DPadUp,
417 ggez::event::Button::DPadRight => GamepadButton::DPadRight,
418 ggez::event::Button::DPadDown => GamepadButton::DPadDown,
419 ggez::event::Button::DPadLeft => GamepadButton::DPadLeft,
420 ggez::event::Button::North => GamepadButton::North,
421 ggez::event::Button::East => GamepadButton::East,
422 ggez::event::Button::South => GamepadButton::South,
423 ggez::event::Button::West => GamepadButton::West,
424 ggez::event::Button::Start => GamepadButton::Start,
425 ggez::event::Button::Select => GamepadButton::Select,
426 ggez::event::Button::LeftTrigger => GamepadButton::LeftBumper,
427 ggez::event::Button::RightTrigger => GamepadButton::RightBumper,
428 other => {
429 log::warn!("Unhandled input: {:?}", other);
430 return Ok(());
431 }
432 };
433 let input = GamepadInput {
434 button,
435 id: integer_id,
436 };
437 if let Some(app::Exit) = on_input(
438 &mut self.chargrid_core,
439 Input::Gamepad(input),
440 &self.chargrid_frame_buffer,
441 ) {
442 ctx.request_quit();
443 }
444 Ok(())
445 }
446}
447
448impl Context {
449 pub fn new(config: Config) -> Self {
450 Self { config }
451 }
452
453 pub fn run<C>(self, component: C) -> !
454 where
455 C: 'static + Component<State = (), Output = app::Output>,
456 {
457 let Self { config } = self;
458 let grid_size = Size::new(
459 (config.window_dimensions_px.width as f64 / config.cell_dimensions_px.width) as u32,
460 (config.window_dimensions_px.height as f64 / config.cell_dimensions_px.height) as u32,
461 );
462 let chargrid_frame_buffer = FrameBuffer::new(grid_size);
463 let (mut ctx, events_loop) =
464 ggez::ContextBuilder::new(config.title.as_str(), "chargrid_ggez")
465 .window_setup(ggez::conf::WindowSetup::default().title(config.title.as_str()))
466 .window_mode(
467 ggez::conf::WindowMode::default()
468 .dimensions(
469 config.window_dimensions_px.width as f32,
470 config.window_dimensions_px.height as f32,
471 )
472 .resizable(config.resizable),
473 )
474 .build()
475 .expect("failed to initialize ggez");
476 let fonts = Fonts {
477 normal: ggez::graphics::FontData::from_vec(config.font_bytes.normal.to_vec())
478 .expect("failed to load normal font"),
479 bold: ggez::graphics::FontData::from_vec(config.font_bytes.bold.to_vec())
480 .expect("failed to load bold font"),
481 };
482 let underline_mesh = {
483 let underline_mid_cell_ratio =
484 config.underline_top_offset_cell_ratio + config.underline_width_cell_ratio / 2.0;
485 let underline_cell_position =
486 (underline_mid_cell_ratio * config.cell_dimensions_px.height) as f32;
487 let underline_width =
488 (config.underline_width_cell_ratio * config.cell_dimensions_px.height) as f32;
489 let points = [
490 ggez::mint::Point2 {
491 x: 0.0,
492 y: underline_cell_position,
493 },
494 ggez::mint::Point2 {
495 x: config.cell_dimensions_px.width as f32,
496 y: underline_cell_position,
497 },
498 ];
499 let mesh = ggez::graphics::Mesh::new_line(
500 &mut ctx,
501 &points,
502 underline_width,
503 [1., 1., 1., 1.].into(),
504 )
505 .expect("failed to build mesh for underline");
506 mesh
507 };
508 let background_mesh = {
509 let rect = ggez::graphics::Rect {
510 x: 0.0,
511 y: 0.0,
512 w: config.cell_dimensions_px.width as f32,
513 h: config.cell_dimensions_px.height as f32,
514 };
515 let mesh = ggez::graphics::Mesh::new_rectangle(
516 &mut ctx,
517 ggez::graphics::DrawMode::fill(),
518 rect,
519 [1., 1., 1., 1.].into(),
520 )
521 .expect("failed to build mesh for background");
522 mesh
523 };
524 ctx.gfx.add_font(FONT_NAME_NORMAL, fonts.normal);
525 ctx.gfx.add_font(FONT_NAME_BOLD, fonts.bold);
526 ggez::event::run(
527 ctx,
528 events_loop,
529 GgezApp {
530 chargrid_core: component,
531 chargrid_frame_buffer,
532 last_frame: Instant::now(),
533 font_scale: ggez::graphics::PxScale {
534 x: config.font_scale.width as f32,
535 y: config.font_scale.height as f32,
536 },
537 underline_mesh,
538 background_mesh,
539 cell_width: config.cell_dimensions_px.width as f32,
540 cell_height: config.cell_dimensions_px.height as f32,
541 current_mouse_button: None,
542 current_mouse_position: Coord::new(0, 0),
543 #[cfg(feature = "gamepad")]
544 gamepad_id_to_integer_id: hashbrown::HashMap::default(),
545 },
546 )
547 }
548}