cvars_console_fyrox/
lib.rs1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use fyrox_ui::{
5 border::BorderBuilder,
6 brush::Brush,
7 core::{color::Color, pool::Handle},
8 formatted_text::WrapMode,
9 message::{KeyCode, MessageDirection, UiMessage},
10 stack_panel::StackPanelBuilder,
11 text::{TextBuilder, TextMessage},
12 text_box::{TextBoxBuilder, TextCommitMode},
13 widget::{WidgetBuilder, WidgetMessage},
14 Orientation, UiNode, UserInterface, VerticalAlignment,
15};
16
17use cvars::SetGet;
18use cvars_console::Console;
19
20pub struct FyroxConsole {
22 is_open: bool,
23 first_open: bool,
24 was_mouse_grabbed: bool,
25 console: Console,
26 height: f32,
27 history: Handle<UiNode>,
28 prompt_text_box: Handle<UiNode>,
29 layout: Handle<UiNode>,
30}
31
32impl FyroxConsole {
33 pub fn new(ui: &mut UserInterface) -> Self {
35 let history = TextBuilder::new(WidgetBuilder::new())
36 .with_wrap(WrapMode::Letter)
38 .build(&mut ui.build_ctx());
39
40 let prompt_arrow = TextBuilder::new(WidgetBuilder::new())
41 .with_text("> ")
42 .build(&mut ui.build_ctx());
43
44 let prompt_text_box = TextBoxBuilder::new(WidgetBuilder::new())
45 .with_text_commit_mode(TextCommitMode::Immediate)
46 .with_skip_chars(vec!['-', '_'])
47 .build(&mut ui.build_ctx());
48
49 let prompt_line = StackPanelBuilder::new(
50 WidgetBuilder::new().with_children([prompt_arrow, prompt_text_box]),
51 )
52 .with_orientation(Orientation::Horizontal)
53 .build(&mut ui.build_ctx());
54
55 let layout = BorderBuilder::new(
57 WidgetBuilder::new()
58 .with_visibility(false)
59 .with_background(Brush::Solid(Color::BLACK.with_new_alpha(220)))
60 .with_child(
61 StackPanelBuilder::new(
62 WidgetBuilder::new()
63 .with_vertical_alignment(VerticalAlignment::Bottom)
64 .with_children([history, prompt_line]),
65 )
66 .with_orientation(Orientation::Vertical)
67 .build(&mut ui.build_ctx()),
68 ),
69 )
70 .build(&mut ui.build_ctx());
71
72 FyroxConsole {
73 is_open: false,
74 first_open: true,
75 was_mouse_grabbed: false,
76 console: Console::new(),
77 height: 0.0,
78 history,
79 prompt_text_box,
80 layout,
81 }
82 }
83
84 pub fn resized(&mut self, ui: &mut UserInterface, width: f32, height: f32) {
86 ui.send_message(WidgetMessage::width(
87 self.layout,
88 MessageDirection::ToWidget,
89 width,
90 ));
91
92 self.height = height / 2.0;
93 ui.send_message(WidgetMessage::height(
94 self.layout,
95 MessageDirection::ToWidget,
96 self.height,
97 ));
98
99 ui.send_message(WidgetMessage::width(
103 self.prompt_text_box,
104 MessageDirection::ToWidget,
105 width,
106 ));
107
108 self.update_ui_history(ui);
110 }
111
112 pub fn ui_message(&mut self, ui: &mut UserInterface, cvars: &mut impl SetGet, msg: &UiMessage) {
122 if !self.is_open || msg.destination != self.prompt_text_box {
123 return;
124 }
125
126 if let Some(TextMessage::Text(text)) = msg.data() {
138 self.console.prompt = text.to_owned();
139 }
140
141 match msg.data() {
142 Some(WidgetMessage::Unfocus) => {
143 ui.send_message(WidgetMessage::focus(
145 self.prompt_text_box,
146 MessageDirection::ToWidget,
147 ));
148 }
149 Some(WidgetMessage::KeyDown(KeyCode::ArrowUp)) => {
150 self.console.history_back();
151 self.update_ui_prompt(ui);
152 }
153 Some(WidgetMessage::KeyDown(KeyCode::ArrowDown)) => {
154 self.console.history_forward();
155 self.update_ui_prompt(ui);
156 }
157 Some(WidgetMessage::KeyDown(KeyCode::PageUp)) => {
158 self.console.history_scroll_up(10);
159 self.update_ui_history(ui);
160 }
161 Some(WidgetMessage::KeyDown(KeyCode::PageDown)) => {
162 self.console.history_scroll_down(10);
163 self.update_ui_history(ui);
164 }
165 Some(WidgetMessage::KeyDown(KeyCode::Enter | KeyCode::NumpadEnter)) => {
166 self.console.enter(cvars);
167 self.update_ui_prompt(ui);
168 self.update_ui_history(ui);
169 }
170 _ => (),
171 }
172 }
173
174 fn update_ui_prompt(&mut self, ui: &mut UserInterface) {
175 ui.send_message(TextMessage::text(
176 self.prompt_text_box,
177 MessageDirection::ToWidget,
178 self.console.prompt.clone(),
179 ));
180 }
181
182 fn update_ui_history(&mut self, ui: &mut UserInterface) {
183 let line_height = 14;
185 let max_lines = (self.height as usize / line_height).saturating_sub(1);
188
189 let hi = self.console.history_view_end;
190 let lo = hi.saturating_sub(max_lines);
191
192 let mut hist = String::new();
193 for line in &self.console.history[lo..hi] {
194 if line.is_input {
195 hist.push_str("> ");
196 }
197 hist.push_str(&line.text);
198 hist.push('\n');
199 }
200
201 ui.send_message(TextMessage::text(
202 self.history,
203 MessageDirection::ToWidget,
204 hist,
205 ));
206 }
207
208 pub fn is_open(&self) -> bool {
210 self.is_open
211 }
212
213 pub fn open(&mut self, ui: &mut UserInterface, was_mouse_grabbed: bool) {
218 self.is_open = true;
219 self.was_mouse_grabbed = was_mouse_grabbed;
220
221 ui.send_message(WidgetMessage::visibility(
222 self.layout,
223 MessageDirection::ToWidget,
224 true,
225 ));
226
227 ui.send_message(WidgetMessage::focus(
228 self.prompt_text_box,
229 MessageDirection::ToWidget,
230 ));
231
232 if self.first_open {
233 self.first_open = false;
238 self.console.print("Type 'help' or '?' for basic info");
239 self.update_ui_history(ui);
240 }
241 }
242
243 #[must_use]
248 pub fn close(&mut self, ui: &mut UserInterface) -> bool {
249 ui.send_message(WidgetMessage::visibility(
250 self.layout,
251 MessageDirection::ToWidget,
252 false,
253 ));
254 ui.send_message(WidgetMessage::unfocus(
255 self.prompt_text_box,
256 MessageDirection::ToWidget,
257 ));
258
259 self.is_open = false;
260 self.was_mouse_grabbed
261 }
262}