Skip to main content

fyrox_ui/file_browser/
field.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::button::Button;
22use crate::file_browser::FileSelector;
23use crate::text_box::TextBox;
24use crate::{
25    button::{ButtonBuilder, ButtonMessage},
26    core::{
27        pool::Handle, reflect::prelude::*, uuid_provider, visitor::prelude::*, ComponentProvider,
28    },
29    define_widget_deref,
30    file_browser::{FileSelectorBuilder, FileSelectorMessage, FileSelectorMode},
31    grid::{Column, GridBuilder, Row},
32    message::{MessageData, MessageDirection, UiMessage},
33    text::TextMessage,
34    text_box::TextBoxBuilder,
35    widget::{Widget, WidgetBuilder, WidgetMessage},
36    window::{WindowAlignment, WindowBuilder, WindowMessage},
37    BuildContext, Control, UiNode, UserInterface, VerticalAlignment,
38};
39use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
40use std::path::{Path, PathBuf};
41
42#[derive(Debug, Clone, PartialEq)]
43pub enum FileSelectorFieldMessage {
44    Path(PathBuf),
45}
46
47impl MessageData for FileSelectorFieldMessage {}
48
49define_widget_deref!(FileSelectorField);
50
51uuid_provider!(FileSelectorField = "2dbda730-8a60-4f62-aee8-2ff0ccd15bf2");
52
53#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
54#[reflect(derived_type = "UiNode")]
55pub struct FileSelectorField {
56    widget: Widget,
57    path: PathBuf,
58    path_field: Handle<TextBox>,
59    select: Handle<Button>,
60    file_selector: Handle<FileSelector>,
61}
62
63impl ConstructorProvider<UiNode, UserInterface> for FileSelectorField {
64    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
65        GraphNodeConstructor::new::<Self>()
66            .with_variant("File Selector Field", |ui| {
67                FileSelectorFieldBuilder::new(WidgetBuilder::new().with_name("File Selector Field"))
68                    .build(&mut ui.build_ctx())
69                    .to_base()
70                    .into()
71            })
72            .with_group("File System")
73    }
74}
75
76impl Control for FileSelectorField {
77    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
78        self.widget.handle_routed_message(ui, message);
79
80        if let Some(TextMessage::Text(text)) = message.data() {
81            if message.destination() == self.path_field
82                && message.direction() == MessageDirection::FromWidget
83                && Path::new(text.as_str()) != self.path
84            {
85                ui.send(self.handle, FileSelectorFieldMessage::Path(text.into()));
86            }
87        } else if let Some(ButtonMessage::Click) = message.data() {
88            if message.destination() == self.select {
89                let file_selector = FileSelectorBuilder::new(
90                    WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(400.0))
91                        .open(false)
92                        .can_minimize(false),
93                )
94                .with_path(self.path.clone())
95                .with_root(std::env::current_dir().unwrap_or_default())
96                .with_mode(FileSelectorMode::Open)
97                .build(&mut ui.build_ctx());
98
99                self.file_selector = file_selector;
100
101                ui.send(
102                    file_selector,
103                    WindowMessage::Open {
104                        alignment: WindowAlignment::Center,
105                        modal: true,
106                        focus_content: true,
107                    },
108                );
109            }
110        } else if let Some(FileSelectorFieldMessage::Path(new_path)) = message.data_for(self.handle)
111        {
112            if &self.path != new_path {
113                self.path.clone_from(new_path);
114                ui.send(
115                    self.path_field,
116                    TextMessage::Text(self.path.to_string_lossy().to_string()),
117                );
118
119                ui.send_message(message.reverse());
120            }
121        }
122    }
123
124    fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
125        if let Some(FileSelectorMessage::Commit(new_path)) = message.data() {
126            if message.destination() == self.file_selector {
127                ui.send(
128                    self.handle,
129                    FileSelectorFieldMessage::Path(new_path.clone()),
130                );
131            }
132        } else if let Some(WindowMessage::Close) = message.data() {
133            if message.destination() == self.file_selector {
134                ui.send(self.file_selector, WidgetMessage::Remove);
135            }
136        }
137    }
138}
139
140pub struct FileSelectorFieldBuilder {
141    widget_builder: WidgetBuilder,
142    path: PathBuf,
143}
144
145impl FileSelectorFieldBuilder {
146    pub fn new(widget_builder: WidgetBuilder) -> Self {
147        Self {
148            widget_builder,
149            path: Default::default(),
150        }
151    }
152
153    pub fn with_path(mut self, path: PathBuf) -> Self {
154        self.path = path;
155        self
156    }
157
158    pub fn build(self, ctx: &mut BuildContext) -> Handle<FileSelectorField> {
159        let select;
160        let path_field;
161        let field = FileSelectorField {
162            widget: self
163                .widget_builder
164                .with_preview_messages(true)
165                .with_child(
166                    GridBuilder::new(
167                        WidgetBuilder::new()
168                            .with_child({
169                                path_field = TextBoxBuilder::new(WidgetBuilder::new().on_column(0))
170                                    .with_text(self.path.to_string_lossy())
171                                    .with_vertical_text_alignment(VerticalAlignment::Center)
172                                    .build(ctx);
173                                path_field
174                            })
175                            .with_child({
176                                select = ButtonBuilder::new(
177                                    WidgetBuilder::new().on_column(1).with_width(25.0),
178                                )
179                                .with_text("...")
180                                .build(ctx);
181                                select
182                            }),
183                    )
184                    .add_row(Row::stretch())
185                    .add_column(Column::stretch())
186                    .add_column(Column::auto())
187                    .build(ctx),
188                )
189                .build(ctx),
190            path: self.path,
191            path_field,
192            select,
193            file_selector: Default::default(),
194        };
195
196        ctx.add(field)
197    }
198}