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