chargrid_core/
lib.rs

1pub use chargrid_input as input;
2use grid_2d::Grid;
3pub use grid_2d::{Coord, Size};
4use input::{Input, InputPolicy, KeyboardInput, MouseInput};
5pub use rgb_int;
6pub use rgb_int::Rgba32;
7#[cfg(feature = "serialize")]
8use serde::{Deserialize, Serialize};
9use std::time::Duration;
10
11#[derive(Clone, Copy, Debug)]
12pub struct BoundingBox {
13    top_left: Coord,
14    bottom_right: Coord,
15}
16
17impl BoundingBox {
18    pub fn default_with_size(size: Size) -> Self {
19        Self {
20            top_left: Coord::new(0, 0),
21            bottom_right: size.to_coord().unwrap(),
22        }
23    }
24
25    pub fn top_left(&self) -> Coord {
26        self.top_left
27    }
28
29    pub fn bottom_right(&self) -> Coord {
30        self.bottom_right
31    }
32
33    pub fn size(&self) -> Size {
34        (self.bottom_right - self.top_left).to_size().unwrap()
35    }
36
37    pub fn coord_relative_to_absolute(&self, coord: Coord) -> Option<Coord> {
38        if coord.x < 0 || coord.y < 0 {
39            return None;
40        }
41        let absolute_coord = self.top_left + coord;
42        if absolute_coord.x < self.bottom_right.x && absolute_coord.y < self.bottom_right.y {
43            Some(absolute_coord)
44        } else {
45            None
46        }
47    }
48
49    pub fn coord_absolute_to_relative(&self, coord: Coord) -> Option<Coord> {
50        if coord.x < self.top_left.x
51            || coord.y < self.top_left.y
52            || coord.x >= self.bottom_right.x
53            || coord.y >= self.bottom_right.y
54        {
55            return None;
56        }
57        Some(coord - self.top_left)
58    }
59
60    /// Move the top-left corner of the bounding box inwards by the specified offset, leaving the
61    /// bottom-right corner of the bounding box in place (ie. box shrinks - it does not move).
62    pub fn add_offset(self, offset: Coord) -> Self {
63        let top_left = Coord {
64            x: (self.top_left.x + offset.x).min(self.bottom_right.x),
65            y: (self.top_left.y + offset.y).min(self.bottom_right.y),
66        };
67        Self { top_left, ..self }
68    }
69
70    pub fn add_x(self, x: i32) -> Self {
71        self.add_offset(Coord { x, y: 0 })
72    }
73
74    pub fn add_y(self, y: i32) -> Self {
75        self.add_offset(Coord { x: 0, y })
76    }
77
78    pub fn add_xy(self, x: i32, y: i32) -> Self {
79        self.add_offset(Coord { x, y })
80    }
81
82    pub fn constrain_size_by(self, by: Coord) -> Self {
83        let bottom_right = Coord {
84            x: (self.bottom_right.x - by.x).max(self.top_left.x),
85            y: (self.bottom_right.y - by.y).max(self.top_left.y),
86        };
87        Self {
88            bottom_right,
89            ..self
90        }
91    }
92
93    pub fn set_size(self, size: Size) -> Self {
94        Self {
95            bottom_right: self.top_left + size.to_coord().unwrap(),
96            ..self
97        }
98    }
99
100    pub fn set_width(self, width: u32) -> Self {
101        self.set_size(self.size().set_width(width))
102    }
103
104    pub fn set_height(self, height: u32) -> Self {
105        self.set_size(self.size().set_height(height))
106    }
107
108    pub fn add_size(self, size: Size) -> Self {
109        self.set_size(self.size() + size)
110    }
111
112    pub fn contains_coord(&self, coord: Coord) -> bool {
113        (coord - self.top_left).is_valid(self.size())
114    }
115}
116
117#[derive(Clone, Copy)]
118pub struct FrameBufferCell {
119    pub character: char,
120    pub bold: bool,
121    pub underline: bool,
122    pub foreground: Rgba32,
123    pub background: Rgba32,
124    foreground_depth: i8,
125    background_depth: i8,
126}
127
128pub type FrameBufferIter<'a> = grid_2d::GridIter<'a, FrameBufferCell>;
129pub type FrameBufferEnumerate<'a> = grid_2d::GridEnumerate<'a, FrameBufferCell>;
130pub type FrameBufferRows<'a> = grid_2d::GridRows<'a, FrameBufferCell>;
131
132impl FrameBufferCell {
133    const BLANK: Self = Self {
134        character: ' ',
135        bold: false,
136        underline: false,
137        foreground: Rgba32::new_rgb(255, 255, 255),
138        background: Rgba32::new_rgb(0, 0, 0),
139        foreground_depth: i8::MIN,
140        background_depth: i8::MIN,
141    };
142    fn set_character(&mut self, character: char, depth: i8) {
143        if depth >= self.foreground_depth {
144            self.character = character;
145            self.foreground_depth = depth;
146        }
147    }
148    fn set_bold(&mut self, bold: bool, depth: i8) {
149        if depth >= self.foreground_depth {
150            self.bold = bold;
151            self.foreground_depth = depth;
152        }
153    }
154    fn set_underline(&mut self, underline: bool, depth: i8) {
155        if depth >= self.foreground_depth {
156            self.underline = underline;
157            self.foreground_depth = depth;
158        }
159    }
160    fn set_foreground(&mut self, foreground: Rgba32, depth: i8) {
161        if depth >= self.foreground_depth {
162            self.foreground = foreground;
163            self.foreground_depth = depth;
164        }
165    }
166    fn set_background(&mut self, background: Rgba32, depth: i8) {
167        if depth >= self.background_depth {
168            self.background = background;
169            self.background_depth = depth;
170        }
171    }
172}
173
174pub struct FrameBuffer {
175    grid: Grid<FrameBufferCell>,
176}
177
178impl FrameBuffer {
179    pub fn new(size: Size) -> Self {
180        Self {
181            grid: Grid::new_copy(size, FrameBufferCell::BLANK),
182        }
183    }
184
185    pub fn size(&self) -> Size {
186        self.grid.size()
187    }
188
189    pub fn resize(&mut self, size: Size) {
190        self.grid = Grid::new_copy(size, FrameBufferCell::BLANK);
191    }
192
193    pub fn clear_with_background(&mut self, background: Rgba32) {
194        for cell in self.grid.iter_mut() {
195            *cell = FrameBufferCell {
196                background,
197                ..FrameBufferCell::BLANK
198            };
199        }
200    }
201
202    pub fn clear(&mut self) {
203        for cell in self.grid.iter_mut() {
204            *cell = FrameBufferCell::BLANK;
205        }
206    }
207
208    pub fn enumerate(&self) -> FrameBufferEnumerate {
209        self.grid.enumerate()
210    }
211
212    pub fn iter(&self) -> FrameBufferIter {
213        self.grid.iter()
214    }
215
216    pub fn rows(&self) -> FrameBufferRows {
217        self.grid.rows()
218    }
219
220    pub fn set_cell(&mut self, coord: Coord, depth: i8, render_cell: RenderCell) {
221        if let Some(cell) = self.grid.get_mut(coord) {
222            if cell.foreground_depth <= depth || cell.background_depth <= depth {
223                if let Some(character) = render_cell.character {
224                    cell.set_character(character, depth);
225                }
226                if let Some(bold) = render_cell.style.bold {
227                    cell.set_bold(bold, depth);
228                }
229                if let Some(underline) = render_cell.style.underline {
230                    cell.set_underline(underline, depth);
231                }
232                if let Some(foreground) = render_cell.style.foreground {
233                    // alpha composite the foreground colour over the existing background colour
234                    let foreground_blended = foreground.alpha_composite(cell.background);
235                    cell.set_foreground(foreground_blended, depth);
236                }
237                if let Some(background) = render_cell.style.background {
238                    let background_blended = background.alpha_composite(cell.background);
239                    cell.set_background(background_blended, depth);
240                }
241            }
242        }
243    }
244
245    pub fn default_ctx<'a>(&self) -> Ctx<'a> {
246        Ctx::default_with_bounding_box_size(self.size())
247    }
248
249    /**
250     * Update a cell in the frame buffer
251     */
252    pub fn set_cell_relative_to_ctx<'a>(
253        &mut self,
254        ctx: Ctx<'a>,
255        coord: Coord,
256        depth: i8,
257        render_cell: RenderCell,
258    ) {
259        if let Some(absolute_coord) = ctx.bounding_box.coord_relative_to_absolute(coord) {
260            let absolute_depth = depth + ctx.depth;
261            self.set_cell(
262                absolute_coord,
263                absolute_depth,
264                render_cell.apply_tint(ctx.tint),
265            );
266        }
267    }
268}
269
270#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
271#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
272pub struct Style {
273    pub bold: Option<bool>,
274    pub underline: Option<bool>,
275    pub foreground: Option<Rgba32>,
276    pub background: Option<Rgba32>,
277}
278
279impl Style {
280    pub const DEFAULT: Self = Self {
281        bold: None,
282        underline: None,
283        foreground: None,
284        background: None,
285    };
286
287    fn apply_tint(self, tint: &dyn Tint) -> Self {
288        Self {
289            foreground: self.foreground.map(|r| tint.tint(r)),
290            background: self.background.map(|r| tint.tint(r)),
291            ..self
292        }
293    }
294
295    pub const fn new() -> Self {
296        Self::DEFAULT
297    }
298
299    pub const fn with_bold(self, bold: bool) -> Self {
300        Self {
301            bold: Some(bold),
302            ..self
303        }
304    }
305    pub const fn with_underline(self, underline: bool) -> Self {
306        Self {
307            underline: Some(underline),
308            ..self
309        }
310    }
311    pub const fn with_foreground(self, foreground: Rgba32) -> Self {
312        Self {
313            foreground: Some(foreground),
314            ..self
315        }
316    }
317    pub const fn with_background(self, background: Rgba32) -> Self {
318        Self {
319            background: Some(background),
320            ..self
321        }
322    }
323    pub const fn without_bold(self) -> Self {
324        Self { bold: None, ..self }
325    }
326    pub const fn without_underline(self) -> Self {
327        Self {
328            underline: None,
329            ..self
330        }
331    }
332    pub const fn without_foreground(self) -> Self {
333        Self {
334            foreground: None,
335            ..self
336        }
337    }
338    pub const fn without_background(self) -> Self {
339        Self {
340            background: None,
341            ..self
342        }
343    }
344    pub const fn with_foreground_option(self, foreground: Option<Rgba32>) -> Self {
345        Self { foreground, ..self }
346    }
347    pub const fn with_background_option(self, background: Option<Rgba32>) -> Self {
348        Self { background, ..self }
349    }
350    pub fn coalesce(self, other: Self) -> Self {
351        Self {
352            bold: (self.bold.or(other.bold)),
353            underline: (self.underline.or(other.underline)),
354            foreground: (self.foreground.or(other.foreground)),
355            background: (self.background.or(other.background)),
356        }
357    }
358    pub const fn plain_text() -> Self {
359        Self {
360            bold: Some(false),
361            underline: Some(false),
362            foreground: Some(Rgba32::new_grey(255)),
363            background: None,
364        }
365    }
366}
367
368impl Default for Style {
369    fn default() -> Self {
370        Self::DEFAULT
371    }
372}
373
374#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
375#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
376pub struct RenderCell {
377    pub character: Option<char>,
378    pub style: Style,
379}
380
381impl RenderCell {
382    pub const BLANK: Self = Self {
383        character: None,
384        style: Style::DEFAULT,
385    };
386
387    fn apply_tint(self, tint: &dyn Tint) -> Self {
388        Self {
389            style: self.style.apply_tint(tint),
390            ..self
391        }
392    }
393
394    pub const fn character(&self) -> Option<char> {
395        self.character
396    }
397    pub const fn bold(&self) -> Option<bool> {
398        self.style.bold
399    }
400    pub const fn underline(&self) -> Option<bool> {
401        self.style.underline
402    }
403    pub const fn foreground(&self) -> Option<Rgba32> {
404        self.style.foreground
405    }
406    pub const fn background(&self) -> Option<Rgba32> {
407        self.style.background
408    }
409    pub const fn with_character(self, character: char) -> Self {
410        Self {
411            character: Some(character),
412            ..self
413        }
414    }
415    pub const fn with_bold(self, bold: bool) -> Self {
416        Self {
417            style: self.style.with_bold(bold),
418            ..self
419        }
420    }
421    pub const fn with_underline(self, underline: bool) -> Self {
422        Self {
423            style: self.style.with_underline(underline),
424            ..self
425        }
426    }
427    pub const fn with_foreground(self, foreground: Rgba32) -> Self {
428        Self {
429            style: self.style.with_foreground(foreground),
430            ..self
431        }
432    }
433    pub const fn with_background(self, background: Rgba32) -> Self {
434        Self {
435            style: self.style.with_background(background),
436            ..self
437        }
438    }
439    pub const fn without_character(self) -> Self {
440        Self {
441            character: None,
442            ..self
443        }
444    }
445    pub const fn without_bold(self) -> Self {
446        Self {
447            style: self.style.without_bold(),
448            ..self
449        }
450    }
451    pub const fn without_underline(self) -> Self {
452        Self {
453            style: self.style.without_underline(),
454            ..self
455        }
456    }
457    pub const fn without_foreground(self) -> Self {
458        Self {
459            style: self.style.without_foreground(),
460            ..self
461        }
462    }
463    pub const fn without_background(self) -> Self {
464        Self {
465            style: self.style.without_background(),
466            ..self
467        }
468    }
469    pub const fn with_character_option(self, character: Option<char>) -> Self {
470        Self { character, ..self }
471    }
472    pub const fn with_foreground_option(self, foreground: Option<Rgba32>) -> Self {
473        Self {
474            style: self.style.with_foreground_option(foreground),
475            ..self
476        }
477    }
478    pub const fn with_background_option(self, background: Option<Rgba32>) -> Self {
479        Self {
480            style: self.style.with_background_option(background),
481            ..self
482        }
483    }
484    pub const fn with_style(self, style: Style) -> Self {
485        Self { style, ..self }
486    }
487}
488
489impl Default for RenderCell {
490    fn default() -> Self {
491        Self::BLANK
492    }
493}
494
495pub trait Tint {
496    fn tint(&self, rgba32: Rgba32) -> Rgba32;
497}
498
499pub struct TintIdentity;
500impl Tint for TintIdentity {
501    fn tint(&self, rgba32: Rgba32) -> Rgba32 {
502        rgba32
503    }
504}
505
506impl<F: Fn(Rgba32) -> Rgba32> Tint for F {
507    fn tint(&self, rgba32: Rgba32) -> Rgba32 {
508        (&self)(rgba32)
509    }
510}
511
512pub struct TintDim(pub u8);
513impl Tint for TintDim {
514    fn tint(&self, rgba32: Rgba32) -> Rgba32 {
515        rgba32.normalised_scalar_mul(self.0)
516    }
517}
518
519pub struct TintDynCompose<'a, T: Tint> {
520    pub outer: &'a dyn Tint,
521    pub inner: &'a T,
522}
523impl<'a, T: Tint> Tint for TintDynCompose<'a, T> {
524    fn tint(&self, rgba32: Rgba32) -> Rgba32 {
525        self.outer.tint(self.inner.tint(rgba32))
526    }
527}
528
529#[derive(Clone, Copy)]
530pub struct Ctx<'a> {
531    pub tint: &'a dyn Tint,
532    pub depth: i8,
533    pub bounding_box: BoundingBox,
534}
535
536#[macro_export]
537macro_rules! ctx_tint {
538    ($ctx:expr, $tint:expr) => {{
539        $ctx.with_tint(&$ctx.compose_tint(&$tint))
540    }};
541}
542
543impl<'a> Ctx<'a> {
544    pub fn compose_tint<T: Tint>(&self, tint: &'a T) -> TintDynCompose<T> {
545        TintDynCompose {
546            outer: self.tint,
547            inner: tint,
548        }
549    }
550    pub fn with_tint(self, tint: &'a dyn Tint) -> Self {
551        Self { tint, ..self }
552    }
553    pub fn default_with_bounding_box_size(size: Size) -> Self {
554        Self {
555            tint: &TintIdentity,
556            depth: 0,
557            bounding_box: BoundingBox::default_with_size(size),
558        }
559    }
560
561    /// Move the top-left corner of the bounding box inwards by the specified offset, leaving the
562    /// bottom-right corner of the bounding box in place (ie. box shrinks - it does not move).
563    pub fn add_offset(self, offset: Coord) -> Self {
564        Self {
565            bounding_box: self.bounding_box.add_offset(offset),
566            ..self
567        }
568    }
569
570    pub fn add_x(self, x: i32) -> Self {
571        self.add_offset(Coord { x, y: 0 })
572    }
573
574    pub fn add_y(self, y: i32) -> Self {
575        self.add_offset(Coord { x: 0, y })
576    }
577
578    pub fn add_xy(self, x: i32, y: i32) -> Self {
579        self.add_offset(Coord { x, y })
580    }
581
582    pub fn add_depth(self, depth_delta: i8) -> Self {
583        Self {
584            depth: self.depth + depth_delta,
585            ..self
586        }
587    }
588
589    pub fn constrain_size_by(self, by: Coord) -> Self {
590        Self {
591            bounding_box: self.bounding_box.constrain_size_by(by),
592            ..self
593        }
594    }
595
596    pub fn set_size(self, size: Size) -> Self {
597        Self {
598            bounding_box: self.bounding_box.set_size(size),
599            ..self
600        }
601    }
602
603    pub fn set_width(self, width: u32) -> Self {
604        Self {
605            bounding_box: self.bounding_box.set_width(width),
606            ..self
607        }
608    }
609
610    pub fn set_height(self, height: u32) -> Self {
611        Self {
612            bounding_box: self.bounding_box.set_height(height),
613            ..self
614        }
615    }
616
617    pub fn add_size(self, size: Size) -> Self {
618        Self {
619            bounding_box: self.bounding_box.add_size(size),
620            ..self
621        }
622    }
623
624    pub fn top_left(self) -> Coord {
625        self.bounding_box.top_left
626    }
627}
628
629#[derive(Debug, Clone, Copy)]
630pub enum Event {
631    Input(Input),
632    Tick(Duration),
633    Peek,
634}
635
636impl Event {
637    pub fn input(self) -> Option<Input> {
638        if let Self::Input(input) = self {
639            Some(input)
640        } else {
641            None
642        }
643    }
644
645    pub fn tick(self) -> Option<Duration> {
646        if let Self::Tick(duration) = self {
647            Some(duration)
648        } else {
649            None
650        }
651    }
652
653    pub fn is_peek(self) -> bool {
654        if let Self::Peek = self {
655            true
656        } else {
657            false
658        }
659    }
660
661    pub fn is_escape(self) -> bool {
662        if let Self::Input(Input::Keyboard(input::keys::ESCAPE)) = self {
663            true
664        } else {
665            false
666        }
667    }
668
669    #[cfg(feature = "gamepad")]
670    pub fn is_start(self) -> bool {
671        if let Self::Input(Input::Gamepad(input::GamepadInput {
672            button: input::GamepadButton::Start,
673            ..
674        })) = self
675        {
676            true
677        } else {
678            false
679        }
680    }
681
682    #[cfg(feature = "gamepad")]
683    pub fn is_escape_or_start(self) -> bool {
684        self.is_escape() || self.is_start()
685    }
686
687    pub fn keyboard_input(self) -> Option<KeyboardInput> {
688        self.input().and_then(Input::keyboard)
689    }
690
691    pub fn mouse_input(self) -> Option<MouseInput> {
692        self.input().and_then(Input::mouse)
693    }
694
695    #[cfg(feature = "gamepad")]
696    pub fn gamepad(self) -> Option<input::GamepadInput> {
697        self.input().and_then(Input::gamepad)
698    }
699
700    pub fn input_policy(self) -> Option<InputPolicy> {
701        self.input().and_then(Input::policy)
702    }
703}
704
705/// A tick-based UI element which can be rendered, respond to events, and yield values. Typically,
706/// a component will be re-rendered (via its `render` method) each frame. Each time an input event
707/// occurs, a component may update its internal state or its external state (the `State` type).
708/// Additionally, in response to an input, a component yields a value (the `Output` type).
709pub trait Component {
710    /// The type yielded by the component in response to an event. Typical components only yield
711    /// meaningful results when certain conditions have been met (e.g. an item is chosen from a
712    /// menu), thus it's common for this type to be `Option<_>`.
713    type Output;
714
715    /// The type of the external state of this component. This allows multiple different components
716    /// to share the same piece of state. For components whose entire state is contained within the
717    /// component itself, set this to `()`.
718    type State: ?Sized;
719
720    /// Render the component to a frame buffer
721    fn render(&self, state: &Self::State, ctx: Ctx, fb: &mut FrameBuffer);
722
723    /// Update the internal and extnal state of this component in response to an event, and yield a
724    /// value.
725    fn update(&mut self, state: &mut Self::State, ctx: Ctx, event: Event) -> Self::Output;
726
727    /// Return the current size (in cells) of this component. This allows decorators to account for
728    /// the size of the components they decorate (e.g. when drawing a border around a component, its
729    /// size must be known).
730    fn size(&self, state: &Self::State, ctx: Ctx) -> Size;
731}
732
733/// A wrapper of `Component` implementation which erases its specific by placing it inside a `Box`
734pub struct BoxedComponent<O, S>(pub Box<dyn Component<Output = O, State = S>>);
735
736impl<O, S> Component for BoxedComponent<O, S> {
737    type Output = O;
738    type State = S;
739    fn render(&self, state: &Self::State, ctx: Ctx, fb: &mut FrameBuffer) {
740        self.0.render(state, ctx, fb)
741    }
742    fn update(&mut self, state: &mut Self::State, ctx: Ctx, event: Event) -> Self::Output {
743        self.0.update(state, ctx, event)
744    }
745    fn size(&self, state: &Self::State, ctx: Ctx) -> Size {
746        self.0.size(state, ctx)
747    }
748}
749
750pub mod app {
751    #[derive(Clone, Copy, Debug)]
752    pub struct Exit;
753    pub type Output = Option<Exit>;
754}
755
756/// types/traits/modules useful for implementing `Component` and friends
757pub mod prelude {
758    #[cfg(feature = "gamepad")]
759    pub use super::input::{GamepadButton, GamepadInput};
760    pub use super::{
761        app, ctx_tint, input, input::Input, input::KeyboardInput, input::MouseButton,
762        input::MouseInput, input::ScrollDirection, Component, Coord, Ctx, Event, FrameBuffer,
763        RenderCell, Rgba32, Size, Style, Tint,
764    };
765    pub use std::time::Duration;
766}