1#![warn(missing_docs)]
25
26use crate::{
27 button::{ButtonBuilder, ButtonMessage},
28 core::{pool::Handle, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*},
29 define_constructor,
30 file_browser::{FileSelectorBuilder, FileSelectorMessage},
31 grid::{Column, GridBuilder, Row},
32 message::{MessageDirection, UiMessage},
33 text::TextMessage,
34 text_box::TextBoxBuilder,
35 widget::{Widget, WidgetBuilder, WidgetMessage},
36 window::{WindowBuilder, WindowMessage, WindowTitle},
37 BuildContext, Control, Thickness, UiNode, UserInterface,
38};
39
40use fyrox_core::uuid_provider;
41use fyrox_core::variable::InheritableVariable;
42use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
43use std::{
44 ops::{Deref, DerefMut},
45 path::Path,
46 path::PathBuf,
47};
48
49#[derive(Debug, Clone, PartialEq)]
51pub enum PathEditorMessage {
52 Path(PathBuf),
54}
55
56impl PathEditorMessage {
57 define_constructor!(
58 PathEditorMessage:Path => fn path(PathBuf), layout: false
60 );
61}
62
63#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
86#[reflect(derived_type = "UiNode")]
87pub struct PathEditor {
88 pub widget: Widget,
90 pub text_field: InheritableVariable<Handle<UiNode>>,
92 pub select: InheritableVariable<Handle<UiNode>>,
94 pub selector: InheritableVariable<Handle<UiNode>>,
96 pub path: InheritableVariable<PathBuf>,
98}
99
100impl ConstructorProvider<UiNode, UserInterface> for PathEditor {
101 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
102 GraphNodeConstructor::new::<Self>()
103 .with_variant("Path Editor", |ui| {
104 PathEditorBuilder::new(WidgetBuilder::new().with_name("Path Editor"))
105 .build(&mut ui.build_ctx())
106 .into()
107 })
108 .with_group("Input")
109 }
110}
111
112crate::define_widget_deref!(PathEditor);
113
114uuid_provider!(PathEditor = "51cfe7ec-ec31-4354-9578-047004b213a1");
115
116impl Control for PathEditor {
117 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
118 self.widget.handle_routed_message(ui, message);
119
120 if let Some(ButtonMessage::Click) = message.data() {
121 if message.destination() == *self.select {
122 self.selector.set_value_and_mark_modified(
123 FileSelectorBuilder::new(
124 WindowBuilder::new(
125 WidgetBuilder::new().with_width(300.0).with_height(450.0),
126 )
127 .open(false)
128 .with_title(WindowTitle::text("Select a Path")),
129 )
130 .build(&mut ui.build_ctx()),
131 );
132
133 ui.send_message(FileSelectorMessage::path(
134 *self.selector,
135 MessageDirection::ToWidget,
136 (*self.path).clone(),
137 ));
138 ui.send_message(WindowMessage::open_modal(
139 *self.selector,
140 MessageDirection::ToWidget,
141 true,
142 true,
143 ));
144 ui.send_message(FileSelectorMessage::focus_current_path(
145 *self.selector,
146 MessageDirection::ToWidget,
147 ));
148 }
149 } else if let Some(PathEditorMessage::Path(path)) = message.data() {
150 if message.destination() == self.handle
151 && message.direction() == MessageDirection::ToWidget
152 && &*self.path != path
153 {
154 self.path.set_value_and_mark_modified(path.clone());
155
156 ui.send_message(TextMessage::text(
157 *self.text_field,
158 MessageDirection::ToWidget,
159 path.to_string_lossy().to_string(),
160 ));
161 ui.send_message(message.reverse());
162 }
163 }
164 }
165
166 fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
167 if let Some(FileSelectorMessage::Commit(path)) = message.data() {
168 if message.destination() == *self.selector && &*self.path != path {
169 ui.send_message(WidgetMessage::remove(
170 *self.selector,
171 MessageDirection::ToWidget,
172 ));
173
174 ui.send_message(PathEditorMessage::path(
175 self.handle,
176 MessageDirection::ToWidget,
177 path.clone(),
178 ));
179 }
180 }
181 }
182}
183
184pub struct PathEditorBuilder {
186 widget_builder: WidgetBuilder,
187 path: PathBuf,
188}
189
190impl PathEditorBuilder {
191 pub fn new(widget_builder: WidgetBuilder) -> Self {
193 Self {
194 widget_builder,
195 path: Default::default(),
196 }
197 }
198
199 pub fn with_path<P: AsRef<Path>>(mut self, path: P) -> Self {
201 path.as_ref().clone_into(&mut self.path);
202 self
203 }
204
205 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
207 let text_field;
208 let select;
209 let grid = GridBuilder::new(
210 WidgetBuilder::new()
211 .with_child({
212 text_field = TextBoxBuilder::new(
213 WidgetBuilder::new()
214 .on_column(0)
215 .with_margin(Thickness::uniform(1.0)),
216 )
217 .with_text(self.path.to_string_lossy())
218 .with_editable(false)
219 .build(ctx);
220 text_field
221 })
222 .with_child({
223 select = ButtonBuilder::new(
224 WidgetBuilder::new()
225 .on_column(1)
226 .with_width(30.0)
227 .with_margin(Thickness::uniform(1.0)),
228 )
229 .with_text("...")
230 .build(ctx);
231 select
232 }),
233 )
234 .add_row(Row::stretch())
235 .add_column(Column::stretch())
236 .add_column(Column::auto())
237 .build(ctx);
238
239 let canvas = PathEditor {
240 widget: self
241 .widget_builder
242 .with_child(grid)
243 .with_preview_messages(true)
244 .build(ctx),
245 text_field: text_field.into(),
246 select: select.into(),
247 selector: Default::default(),
248 path: self.path.into(),
249 };
250 ctx.add_node(UiNode::new(canvas))
251 }
252}
253
254#[cfg(test)]
255mod test {
256 use crate::path::PathEditorBuilder;
257 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
258
259 #[test]
260 fn test_deletion() {
261 test_widget_deletion(|ctx| PathEditorBuilder::new(WidgetBuilder::new()).build(ctx));
262 }
263}