ui/
ui.rs

1use std::io;
2use crossterm::event::{Event, KeyCode};
3use teng::components::Component;
4use teng::rendering::pixel::Pixel;
5use teng::rendering::render::Render;
6use teng::rendering::renderer::Renderer;
7use teng::{install_panic_handler, terminal_cleanup, terminal_setup, BreakingAction, Game, SetupInfo, SharedState, UpdateInfo};
8use teng::components::ui::{UiComponent, UiElement};
9
10struct MyWindow {
11    title_bar_height: usize,
12    width: usize,
13    height: usize,
14    text_box: String,
15    background_color: [u8; 3],
16}
17
18impl MyWindow {
19    fn new() -> Self {
20        let background_color = [rand::random::<u8>(), rand::random::<u8>(), rand::random::<u8>()];
21        Self {
22            title_bar_height: 1,
23            width: 20,
24            height: 10,
25            text_box: String::new(),
26            background_color,
27        }
28    }
29}
30
31impl UiElement for MyWindow {
32    fn is_hover_drag(&self, x: usize, y: usize) -> bool {
33        y < self.title_bar_height
34    }
35
36    fn is_resizing_drag(&self, x: usize, y: usize) -> bool {
37        x >= self.width - 1 && y >= self.height - 1
38    }
39
40    fn on_resize(&mut self, width: usize, height: usize, shared_state: &mut SharedState<()>) {
41        self.width = width;
42        self.height = height;
43    }
44
45    fn get_size(&self) -> (usize, usize) {
46        (self.width, self.height)
47    }
48
49    fn on_event(&mut self, event: Event, shared_state: &mut SharedState<()>) -> Option<BreakingAction> {
50        if let Event::Key(key_event) = event {
51            if let KeyCode::Char(c) = key_event.code {
52                self.text_box.push(c);
53            }
54            if key_event.code == KeyCode::Backspace {
55                self.text_box.pop();
56            }
57            if key_event.code == KeyCode::Enter {
58                self.text_box.push('\n');
59            }
60        }
61        None
62    }
63
64    fn render(&self, renderer: &mut dyn Renderer, depth_base: i32) {
65        let depth_text_box = depth_base + 1;
66        let depth_title_bar = depth_base + 2;
67
68        let pixel = Pixel::new(' ').with_bg_color(self.background_color);
69
70        for x in 0..self.width {
71            for y in 1..self.height {
72                renderer.render_pixel(x, y, pixel, depth_base);
73            }
74        }
75
76        // text box
77        self.text_box.render(renderer, 0, 1, depth_text_box);
78
79        // title bar
80        for x in 0..self.width {
81            renderer.render_pixel(x, 0, Pixel::new(' ').with_bg_color([150, 150, 150]), depth_base);
82        }
83        "My Window".render(renderer, 0, 0, depth_title_bar);
84        
85        // the resize corner symbol
86        let corner_expand_pixel = Pixel::new('⤡').with_bg_color([150, 150, 150]);
87        renderer.render_pixel(self.width - 1, self.height - 1, corner_expand_pixel, depth_text_box);
88    }
89}
90
91struct Setup;
92
93impl Component for Setup {
94    fn setup(&mut self, setup_info: &SetupInfo, shared_state: &mut SharedState<()>) {
95        // add some UI components
96        let width = shared_state.display_info.width();
97        let height = shared_state.display_info.height();
98
99        shared_state.ui.add_window(30, 15, Box::new(MyWindow::new()));
100
101    }
102
103    fn update(&mut self, update_info: UpdateInfo, shared_state: &mut SharedState<()>) {
104        if shared_state.pressed_keys.did_press_char_ignore_case(' ') {
105            let width = shared_state.display_info.width();
106            let height = shared_state.display_info.height();
107            
108            let anchor_x = rand::random::<usize>() % width;
109            let anchor_y = rand::random::<usize>() % height;
110            
111            shared_state.ui.add_window(anchor_x, anchor_y, Box::new(MyWindow::new()));
112        }
113    }
114}
115
116fn main() -> io::Result<()> {
117    terminal_setup()?;
118    install_panic_handler();
119
120    let mut game = Game::new_with_custom_buf_writer();
121    // If you don't install the recommended components, you will need to have your own
122    // component that exits the process, since Ctrl-C does not work in raw mode.
123    game.install_recommended_components();
124    game.add_component(Box::new(UiComponent::new()));
125    game.add_component(Box::new(Setup));
126    game.run()?;
127
128    terminal_cleanup()?;
129
130    Ok(())
131}