1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use macroquad::{
5 prelude::*,
6 ui::{
7 hash, root_ui,
8 widgets::{Group, Label},
9 Layout, Skin,
10 },
11};
12
13use cvars::SetGet;
14use cvars_console::Console;
15
16#[derive(Debug, Clone, Default)]
20pub struct MacroquadConsole {
21 is_open: bool,
22 console: Console,
23 config: Config,
24 input: ConsoleInput,
25 input_prev: ConsoleInput,
26}
27
28impl MacroquadConsole {
29 pub fn new() -> Self {
31 Self {
32 is_open: false,
33 console: Console::new(),
34 config: Config::default(),
35 input: ConsoleInput::new(),
36 input_prev: ConsoleInput::new(),
37 }
38 }
39
40 pub fn update(&mut self, cvars: &mut dyn SetGet) {
44 self.input_prev = self.input;
45 self.input = get_input();
46
47 self.open_close();
48
49 if self.is_open {
50 self.process_input();
51 self.draw_console();
52 if !self.input_prev.enter && self.input.enter && !self.console.prompt.is_empty() {
53 self.console.enter(cvars);
54 }
55 }
56 }
57
58 fn open_close(&mut self) {
60 let pressed_console = !self.input_prev.console && self.input.console;
61 let pressed_escape = !self.input_prev.escape && self.input.escape;
62 if !self.is_open && pressed_console {
63 self.is_open = true;
64 show_mouse(true);
65 } else if self.is_open && (pressed_console || pressed_escape) {
66 self.is_open = false;
67 show_mouse(false);
68 }
69 }
70
71 fn process_input(&mut self) {
73 self.console.prompt = self.console.prompt.replace(';', "");
81
82 let pressed_up = !self.input_prev.up && self.input.up;
85 let pressed_down = !self.input_prev.down && self.input.down;
86 let pressed_page_up = !self.input_prev.page_up && self.input.page_up;
87 let pressed_page_down = !self.input_prev.page_down && self.input.page_down;
88
89 if pressed_up {
90 self.console.history_back();
91 }
92
93 if pressed_down {
95 self.console.history_forward();
96 }
97
98 let count = 10; if pressed_page_up {
101 self.console.history_scroll_up(count);
102 }
103 if pressed_page_down {
104 self.console.history_scroll_down(count);
105 }
106 }
107
108 fn draw_console(&mut self) {
110 let console_height = (screen_height() * self.config.height_fraction).floor();
113 draw_rectangle(
114 0.0,
115 0.0,
116 screen_width(),
117 console_height,
118 Color::new(0.0, 0.0, 0.0, self.config.background_alpha),
119 );
120 draw_line(
121 0.0,
122 console_height,
123 screen_width(),
124 console_height,
125 1.0,
126 RED,
127 );
128
129 if self.console.history_view_end >= 1 {
133 let mut i = self.console.history_view_end - 1;
134 let mut y = console_height - self.config.history_y_offset;
135 loop {
136 let text = if self.console.history[i].is_input {
137 format!("> {}", self.console.history[i].text)
138 } else {
139 self.console.history[i].text.clone()
140 };
141 draw_text(
142 &text,
143 self.config.history_x,
144 y,
145 self.config.history_line_font_size,
146 WHITE,
147 );
148 if i == 0 || y < 0.0 {
149 break;
150 }
151 i -= 1;
152 y -= self.config.history_line_height;
153 }
154 }
155
156 let bg_image = Image::gen_image_color(1, 1, BLANK);
158 let style = root_ui()
159 .style_builder()
160 .background(bg_image)
161 .color(BLANK) .text_color(WHITE)
163 .build();
164 let skin = Skin {
165 label_style: style.clone(),
166 editbox_style: style.clone(),
167 group_style: style,
168 ..root_ui().default_skin()
169 };
170 root_ui().push_skin(&skin);
171
172 let id_prompt = 0;
174 let label_y = console_height - self.config.prompt_label_y_offset;
175 Label::new(">")
176 .position(vec2(self.config.prompt_label_x, label_y))
177 .ui(&mut root_ui());
178 let group_y =
180 screen_height() * self.config.height_fraction - self.config.prompt_group_y_offset;
181 Group::new(hash!(), vec2(screen_width() - 8.0, 20.0))
182 .position(vec2(self.config.prompt_group_x, group_y))
183 .layout(Layout::Horizontal)
184 .ui(&mut root_ui(), |ui| {
185 ui.input_text(id_prompt, "", &mut self.console.prompt);
186 });
187
188 root_ui().set_input_focus(id_prompt);
190 }
191
192 pub fn is_open(&self) -> bool {
197 self.is_open
198 }
199}
200
201#[derive(Debug, Clone)]
203struct Config {
204 background_alpha: f32,
205 prompt_group_x: f32,
206 prompt_group_y_offset: f32,
207 height_fraction: f32,
208 history_line_font_size: f32,
209 history_line_height: f32,
210 history_x: f32,
211 history_y_offset: f32,
212 prompt_label_x: f32,
213 prompt_label_y_offset: f32,
214}
215
216impl Default for Config {
217 fn default() -> Self {
218 Self {
219 background_alpha: 0.8,
220 prompt_group_x: 16.0,
221 prompt_group_y_offset: 26.0,
222 height_fraction: 0.45,
223 history_line_font_size: 16.0,
224 history_line_height: 14.0,
225 history_x: 8.0,
226 history_y_offset: 25.0,
227 prompt_label_x: 8.0,
228 prompt_label_y_offset: 22.0,
229 }
230 }
231}
232
233#[derive(Debug, Clone, Copy, Default)]
234struct ConsoleInput {
235 console: bool,
236 escape: bool,
237 enter: bool,
238 up: bool,
239 down: bool,
240 page_up: bool,
241 page_down: bool,
242}
243
244impl ConsoleInput {
245 fn new() -> Self {
246 Self::default()
247 }
248}
249
250fn get_input() -> ConsoleInput {
251 let mut input = ConsoleInput::new();
252 if are_keys_pressed(&[KeyCode::GraveAccent, KeyCode::Semicolon]) {
253 input.console = true;
254 }
255 if are_keys_pressed(&[KeyCode::Escape]) {
256 input.escape = true;
257 }
258 if are_keys_pressed(&[KeyCode::Enter, KeyCode::KpEnter]) {
259 input.enter = true;
260 }
261 if are_keys_pressed(&[KeyCode::Up]) {
262 input.up = true;
263 }
264 if are_keys_pressed(&[KeyCode::Down]) {
265 input.down = true;
266 }
267 if are_keys_pressed(&[KeyCode::PageUp]) {
268 input.page_up = true;
269 }
270 if are_keys_pressed(&[KeyCode::PageDown]) {
271 input.page_down = true;
272 }
273 input
274}
275
276fn are_keys_pressed(key_codes: &[KeyCode]) -> bool {
277 for &key_code in key_codes {
278 if is_key_pressed(key_code) {
279 return true;
280 }
281 }
282 false
283}