chargrid_ggez/
lib.rs

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}