epkard/
frontend.rs

1use std::{
2    io::{stdin, stdout, BufRead, Write},
3    ops::Neg,
4};
5
6use colored::Colorize;
7
8use crate::{Node, Runtime};
9
10/// Colors used for frontends
11#[allow(missing_docs)]
12pub enum Color {
13    White,
14    Black,
15    Red,
16    Green,
17    Blue,
18    Cyan,
19    Magenta,
20    Yellow,
21}
22
23impl Neg for Color {
24    type Output = Self;
25    fn neg(self) -> Self::Output {
26        use Color::*;
27        match self {
28            White => Black,
29            Black => White,
30            Red => Cyan,
31            Cyan => Red,
32            Green => Magenta,
33            Magenta => Green,
34            Blue => Yellow,
35            Yellow => Blue,
36        }
37    }
38}
39
40/**
41The frontend for a narrative that a user can interact with
42*/
43pub trait Frontend {
44    /// Get the background color
45    fn background_color(&self) -> Color;
46    /// Set the background color
47    fn set_background_color(&mut self, color: Color);
48    /// Clear the display
49    fn clear(&mut self);
50    /// Print text to the display with the given color
51    fn print_colored<S: ToString>(&mut self, text: S, color: Color);
52    /// Print a newline to the display
53    fn newline(&mut self);
54    /// Get input from the user
55    fn input(&mut self) -> String;
56    /// Print text to the display
57    fn print<S: ToString>(&mut self, text: S) {
58        self.print_colored(text, -self.background_color());
59    }
60    /// Print text and a newline to the display
61    fn println<S: ToString>(&mut self, text: S) {
62        self.print(text);
63        self.newline();
64    }
65}
66
67/// A frontend that uses stdin and stdout
68pub struct CliFrontend {
69    background_color: colored::Color,
70}
71
72impl CliFrontend {
73    /// Create a new `CliFrontend`
74    pub fn new() -> Self {
75        Self::default()
76    }
77    /// Create a new `CliFrontend` with the given background color
78    pub fn with_bg_color(color: Color) -> Self {
79        let mut clif = Self::new();
80        clif.set_background_color(color);
81        clif
82    }
83}
84
85impl Default for CliFrontend {
86    fn default() -> Self {
87        CliFrontend {
88            background_color: colored::Color::Black,
89        }
90    }
91}
92
93impl Frontend for CliFrontend {
94    fn background_color(&self) -> Color {
95        use Color::*;
96        match self.background_color {
97            colored::Color::Black => Black,
98            colored::Color::White => White,
99            colored::Color::Red => Red,
100            colored::Color::Green => Green,
101            colored::Color::Blue => Blue,
102            colored::Color::Cyan => Cyan,
103            colored::Color::Magenta => Magenta,
104            colored::Color::Yellow => Yellow,
105            _ => Black,
106        }
107    }
108    fn set_background_color(&mut self, color: Color) {
109        use Color::*;
110        self.background_color = match color {
111            White => colored::Color::Black,
112            Black => colored::Color::White,
113            Red => colored::Color::Red,
114            Green => colored::Color::Green,
115            Blue => colored::Color::Blue,
116            Cyan => colored::Color::Cyan,
117            Magenta => colored::Color::Magenta,
118            Yellow => colored::Color::Yellow,
119        };
120    }
121    fn clear(&mut self) {
122        let (_, height) = terminal_size::terminal_size()
123            .unwrap_or_else(|| panic!("{}", "Unable to get terminal size".bright_red()));
124        println!("{}", (0..height.0).map(|_| '\n').collect::<String>());
125    }
126    fn print_colored<S: ToString>(&mut self, text: S, color: Color) {
127        use Color::*;
128        let color = match color {
129            Black => Colorize::black,
130            White => Colorize::white,
131            Red => Colorize::red,
132            Green => Colorize::green,
133            Blue => Colorize::blue,
134            Cyan => Colorize::cyan,
135            Magenta => Colorize::magenta,
136            Yellow => Colorize::yellow,
137        };
138        print!(
139            "{}",
140            color(text.to_string().as_ref()).on_color(self.background_color)
141        );
142        let _ = stdout().flush();
143    }
144    fn newline(&mut self) {
145        println!();
146    }
147    fn input(&mut self) -> String {
148        stdin()
149            .lock()
150            .lines()
151            .next()
152            .and_then(Result::ok)
153            .unwrap_or_else(|| panic!("{}", "Failed to read line".bright_red()))
154    }
155}
156
157impl<'a, N, F> Frontend for Runtime<'a, N, F>
158where
159    N: Node,
160    F: Frontend,
161{
162    fn background_color(&self) -> Color {
163        self.frontend.background_color()
164    }
165    fn set_background_color(&mut self, color: Color) {
166        self.frontend.set_background_color(color)
167    }
168    fn clear(&mut self) {
169        self.frontend.clear()
170    }
171    fn print_colored<S: ToString>(&mut self, text: S, color: Color) {
172        self.frontend.print_colored(text, color)
173    }
174    fn newline(&mut self) {
175        self.frontend.newline()
176    }
177    fn input(&mut self) -> String {
178        self.frontend.input()
179    }
180}