1use crate::{
26 button::{Button, ButtonBuilder, ButtonMessage},
27 control_trait_proxy_impls,
28 core::{
29 algebra::Vector2, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
30 variable::InheritableVariable, visitor::prelude::*,
31 },
32 formatted_text::WrapMode,
33 grid::{Column, GridBuilder, Row},
34 message::{KeyCode, MessageData, UiMessage},
35 stack_panel::StackPanelBuilder,
36 text::{Text, TextBuilder, TextMessage},
37 text_box::{TextBox, TextBoxBuilder, TextCommitMode},
38 widget::{Widget, WidgetBuilder, WidgetMessage},
39 window::{Window, WindowAlignment, WindowBuilder, WindowMessage, WindowTitle},
40 BuildContext, Control, HorizontalAlignment, Orientation, Thickness, UiNode, UserInterface,
41 VerticalAlignment,
42};
43use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
44use std::ops::{Deref, DerefMut};
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum InputBoxMessage {
49 Open {
51 title: Option<String>,
53 text: Option<String>,
55 value: Option<String>,
57 },
58 Close(InputBoxResult),
61}
62impl MessageData for InputBoxMessage {}
63
64impl InputBoxMessage {
65 pub fn open_as_is() -> Self {
66 Self::Open {
67 title: None,
68 text: None,
69 value: None,
70 }
71 }
72}
73
74#[derive(Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug)]
76pub enum InputBoxResult {
77 Ok(String),
79 Cancel,
81}
82
83#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider, TypeUuidProvider)]
92#[type_uuid(id = "6b7b6b82-939b-4f98-9bb9-9bd19ce68b21")]
93#[reflect(derived_type = "UiNode")]
94pub struct InputBox {
95 #[component(include)]
97 pub window: Window,
98 pub ok: InheritableVariable<Handle<Button>>,
100 pub cancel: InheritableVariable<Handle<Button>>,
102 pub text: InheritableVariable<Handle<Text>>,
104 pub value_box: InheritableVariable<Handle<TextBox>>,
105 pub value: String,
106}
107
108impl ConstructorProvider<UiNode, UserInterface> for InputBox {
109 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
110 GraphNodeConstructor::new::<Self>()
111 .with_variant("Input Box", |ui| {
112 InputBoxBuilder::new(WindowBuilder::new(
113 WidgetBuilder::new().with_name("Input Box"),
114 ))
115 .build(&mut ui.build_ctx())
116 .to_base()
117 .into()
118 })
119 .with_group("Input")
120 }
121}
122
123impl Deref for InputBox {
124 type Target = Widget;
125
126 fn deref(&self) -> &Self::Target {
127 &self.window
128 }
129}
130
131impl DerefMut for InputBox {
132 fn deref_mut(&mut self) -> &mut Self::Target {
133 &mut self.window
134 }
135}
136
137impl InputBox {
138 fn close_ok(&self, ui: &UserInterface) {
139 ui.send(
140 self.handle(),
141 InputBoxMessage::Close(InputBoxResult::Ok(self.value.clone())),
142 );
143 }
144}
145
146impl Control for InputBox {
147 control_trait_proxy_impls!(window);
148
149 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
150 self.window.handle_routed_message(ui, message);
151
152 if let Some(ButtonMessage::Click) = message.data_from(*self.ok) {
153 self.close_ok(ui);
154 } else if let Some(ButtonMessage::Click) = message.data_from(*self.cancel) {
155 ui.send(
156 self.handle(),
157 InputBoxMessage::Close(InputBoxResult::Cancel),
158 );
159 } else if let Some(msg) = message.data_for::<InputBoxMessage>(self.handle) {
160 match msg {
161 InputBoxMessage::Open { title, text, value } => {
162 if let Some(title) = title {
163 ui.send(
164 self.handle(),
165 WindowMessage::Title(WindowTitle::text(title.clone())),
166 );
167 }
168
169 if let Some(text) = text {
170 ui.send(*self.text, TextMessage::Text(text.clone()));
171 }
172
173 if let Some(value) = value {
174 ui.send(*self.value_box, TextMessage::Text(value.clone()));
175 }
176
177 ui.send(
178 self.handle(),
179 WindowMessage::Open {
180 alignment: WindowAlignment::Center,
181 modal: true,
182 focus_content: false,
183 },
184 );
185
186 ui.send(*self.value_box, WidgetMessage::Focus);
187
188 ui.try_send_response(message);
189 }
190 InputBoxMessage::Close(_) => {
191 ui.send(self.handle(), WindowMessage::Close);
193
194 ui.try_send_response(message);
195 }
196 }
197 } else if let Some(TextMessage::Text(text)) = message.data_from(*self.value_box) {
198 self.value = text.clone();
199 } else if let Some(WidgetMessage::KeyDown(code)) = message.data() {
200 if matches!(*code, KeyCode::Enter | KeyCode::NumpadEnter) {
201 self.close_ok(ui);
202 }
203 }
204 }
205}
206
207pub struct InputBoxBuilder<'b> {
209 window_builder: WindowBuilder,
210 text: &'b str,
211 value: String,
212}
213
214impl<'b> InputBoxBuilder<'b> {
215 pub fn new(window_builder: WindowBuilder) -> Self {
217 Self {
218 window_builder,
219 text: "",
220 value: Default::default(),
221 }
222 }
223
224 pub fn with_text(mut self, text: &'b str) -> Self {
226 self.text = text;
227 self
228 }
229
230 pub fn with_value(mut self, value: String) -> Self {
232 self.value = value;
233 self
234 }
235
236 pub fn build(mut self, ctx: &mut BuildContext) -> Handle<InputBox> {
238 let ok;
239 let cancel;
240 let text;
241 let value_box;
242 let content = GridBuilder::new(
243 WidgetBuilder::new()
244 .with_child({
245 text =
246 TextBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(4.0)))
247 .with_text(self.text)
248 .with_wrap(WrapMode::Word)
249 .build(ctx);
250 text
251 })
252 .with_child({
253 value_box = TextBoxBuilder::new(
254 WidgetBuilder::new()
255 .with_margin(Thickness::top(4.0))
256 .with_tab_index(Some(0))
257 .on_row(1)
258 .with_min_size(Vector2::new(f32::INFINITY, 24.0)),
259 )
260 .with_vertical_text_alignment(VerticalAlignment::Center)
261 .with_text_commit_mode(TextCommitMode::Immediate)
262 .with_text(&self.value)
263 .build(ctx);
264 value_box
265 })
266 .with_child(
267 StackPanelBuilder::new(
268 WidgetBuilder::new()
269 .with_margin(Thickness::top(4.0))
270 .with_horizontal_alignment(HorizontalAlignment::Right)
271 .on_row(2)
272 .with_child({
273 ok = ButtonBuilder::new(
274 WidgetBuilder::new()
275 .with_tab_index(Some(1))
276 .with_margin(Thickness::uniform(1.0))
277 .with_width(80.0)
278 .with_horizontal_alignment(HorizontalAlignment::Center),
279 )
280 .with_text("OK")
281 .build(ctx);
282 ok
283 })
284 .with_child({
285 cancel = ButtonBuilder::new(
286 WidgetBuilder::new()
287 .with_tab_index(Some(2))
288 .with_margin(Thickness::uniform(1.0))
289 .with_width(80.0)
290 .with_horizontal_alignment(HorizontalAlignment::Center),
291 )
292 .with_text("Cancel")
293 .build(ctx);
294 cancel
295 }),
296 )
297 .with_orientation(Orientation::Horizontal)
298 .build(ctx),
299 )
300 .with_margin(Thickness::uniform(4.0)),
301 )
302 .add_row(Row::stretch())
303 .add_row(Row::auto())
304 .add_row(Row::strict(30.0))
305 .add_column(Column::stretch())
306 .build(ctx);
307
308 if self.window_builder.widget_builder.min_size.is_none() {
309 self.window_builder.widget_builder.min_size = Some(Vector2::new(200.0, 100.0));
310 }
311
312 self.window_builder.widget_builder.handle_os_events = true;
313
314 let input_box = InputBox {
315 window: self.window_builder.with_content(content).build_window(ctx),
316 ok: ok.into(),
317 cancel: cancel.into(),
318 text: text.into(),
319 value_box: value_box.into(),
320 value: self.value,
321 };
322
323 ctx.add(input_box)
324 }
325}