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 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 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 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 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
705pub trait Component {
710 type Output;
714
715 type State: ?Sized;
719
720 fn render(&self, state: &Self::State, ctx: Ctx, fb: &mut FrameBuffer);
722
723 fn update(&mut self, state: &mut Self::State, ctx: Ctx, event: Event) -> Self::Output;
726
727 fn size(&self, state: &Self::State, ctx: Ctx) -> Size;
731}
732
733pub 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
756pub 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}