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