// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. use crate::fyrox::gui::text::TextMessage; use crate::fyrox::{ core::{ algebra::Vector2, pool::Handle, scope_profile, visitor::{Visit, VisitResult, Visitor}, }, gui::{ border::BorderBuilder, button::{ButtonBuilder, ButtonMessage}, decorator::DecoratorBuilder, file_browser::{FileSelectorBuilder, FileSelectorMessage, Filter}, formatted_text::WrapMode, grid::{Column, GridBuilder, Row}, list_view::{ListViewBuilder, ListViewMessage}, message::{MessageDirection, UiMessage}, stack_panel::StackPanelBuilder, text::TextBuilder, text_box::TextBoxBuilder, widget::{WidgetBuilder, WidgetMessage}, window::{WindowBuilder, WindowMessage, WindowTitle}, BuildContext, HorizontalAlignment, Orientation, Thickness, UiNode, VerticalAlignment, }, }; use crate::message::MessageSender; use crate::{Engine, Message}; use std::{ env, path::{Path, PathBuf}, }; #[derive(Default, Eq, PartialEq, Visit)] struct HistoryEntry { work_dir: PathBuf, } pub const HISTORY_PATH: &str = "history.bin"; pub struct Configurator { pub window: Handle, work_dir_browser: Handle, select_work_dir: Handle, ok: Handle, sender: MessageSender, work_dir: PathBuf, tb_work_dir: Handle, lv_history: Handle, history: Vec, } fn make_history_entry_widget(ctx: &mut BuildContext, entry: &HistoryEntry) -> Handle { DecoratorBuilder::new(BorderBuilder::new( WidgetBuilder::new() .with_height(18.0) .with_margin(Thickness { left: 1.0, top: 0.0, right: 1.0, bottom: 1.0, }) .with_child( TextBuilder::new(WidgetBuilder::new().with_margin(Thickness::left(5.0))) .with_text(format!("{}", entry.work_dir.display(),)) .with_vertical_text_alignment(VerticalAlignment::Center) .build(ctx), ), )) .build(ctx) } impl Configurator { pub fn new(sender: MessageSender, ctx: &mut BuildContext) -> Self { let select_work_dir; let ok; let tb_work_dir; let current_path = env::current_dir().unwrap(); let filter = Filter::new(|p: &Path| p.is_dir()); let folder_browser = FileSelectorBuilder::new( WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(400.0)) .open(false) .with_title(WindowTitle::text("Select Working Directory")), ) .with_filter(filter) .build(ctx); // Load history. let mut history: Vec = Vec::new(); if let Ok(mut visitor) = fyrox::core::futures::executor::block_on(Visitor::load_binary(HISTORY_PATH)) { history.visit("History", &mut visitor).unwrap(); } // Remove entries with invalid paths. history = history .into_iter() .filter(|e| e.work_dir.exists()) .collect::>(); let message = "Please select the working directory of \ your current project. In most cases it will be the root folder \ of your project"; let lv_history; let window = WindowBuilder::new( WidgetBuilder::new() .with_width(370.0) .with_height(250.0) .with_min_size(Vector2::new(370.0, 250.0)), ) .with_title(WindowTitle::text("Configure Editor")) .open(false) .can_close(false) .with_content( GridBuilder::new( WidgetBuilder::new() .with_margin(Thickness::uniform(1.0)) .with_child( TextBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(1.0))) .with_text(message) .with_wrap(WrapMode::Word) .build(ctx), ) .with_child( GridBuilder::new( WidgetBuilder::new() .on_row(1) .with_child( TextBuilder::new( WidgetBuilder::new() .on_row(0) .on_column(0) .with_margin(Thickness::uniform(1.0)) .with_vertical_alignment(VerticalAlignment::Center), ) .with_text("Working Directory") .build(ctx), ) .with_child({ tb_work_dir = TextBoxBuilder::new( WidgetBuilder::new() .on_row(0) .on_column(1) .with_margin(Thickness::uniform(1.0)) .with_enabled(false), ) .with_text( (current_path.clone()) .into_os_string() .into_string() .unwrap(), ) .with_vertical_text_alignment(VerticalAlignment::Center) .build(ctx); tb_work_dir }) .with_child({ select_work_dir = ButtonBuilder::new( WidgetBuilder::new() .with_tab_index(Some(0)) .on_row(0) .on_column(2) .with_margin(Thickness::uniform(1.0)), ) .with_text("...") .build(ctx); select_work_dir }), ) .add_row(Row::strict(25.0)) .add_column(Column::strict(120.0)) .add_column(Column::stretch()) .add_column(Column::strict(25.0)) .build(ctx), ) .with_child( TextBuilder::new( WidgetBuilder::new() .with_margin(Thickness::uniform(5.0)) .on_row(2), ) .with_text("Previous Configurations") .with_horizontal_text_alignment(HorizontalAlignment::Center) .build(ctx), ) .with_child({ lv_history = ListViewBuilder::new( WidgetBuilder::new().with_tab_index(Some(1)).on_row(3), ) .with_items( history .iter() .map(|entry| make_history_entry_widget(ctx, entry)) .collect(), ) .build(ctx); lv_history }) .with_child( StackPanelBuilder::new( WidgetBuilder::new() .on_row(4) .with_horizontal_alignment(HorizontalAlignment::Right) .with_vertical_alignment(VerticalAlignment::Bottom) .with_child({ ok = ButtonBuilder::new( WidgetBuilder::new() .with_tab_index(Some(2)) .with_enabled(true) // Enabled by default. .with_width(80.0) .with_height(25.0) .with_margin(Thickness::uniform(1.0)), ) .with_text("OK") .build(ctx); ok }), ) .with_orientation(Orientation::Horizontal) .build(ctx), ), ) .add_row(Row::auto()) .add_row(Row::auto()) .add_row(Row::auto()) .add_row(Row::strict(80.0)) .add_row(Row::stretch()) .add_column(Column::stretch()) .build(ctx), ) .build(ctx); Self { window, work_dir_browser: folder_browser, select_work_dir, ok, sender, tb_work_dir, work_dir: current_path, lv_history, history, } } fn validate(&mut self, engine: &mut Engine) { let is_valid_scene_path = self.work_dir.exists(); engine .user_interfaces .first_mut() .send_message(WidgetMessage::enabled( self.ok, MessageDirection::ToWidget, is_valid_scene_path, )); } pub fn handle_ui_message(&mut self, message: &UiMessage, engine: &mut Engine) { scope_profile!(); if let Some(WindowMessage::Close) = message.data::() { if message.destination() == self.window { // Save history for next editor runs. let mut visitor = Visitor::new(); self.history.visit("History", &mut visitor).unwrap(); visitor.save_binary(HISTORY_PATH).unwrap(); } } else if let Some(ListViewMessage::SelectionChanged(selected_indices)) = message.data::() { if let Some(index) = selected_indices.first().cloned() { if message.destination() == self.lv_history && message.direction() == MessageDirection::FromWidget { let entry = &self.history[index]; self.work_dir.clone_from(&entry.work_dir); engine .user_interfaces .first_mut() .send_message(TextMessage::text( self.tb_work_dir, MessageDirection::ToWidget, self.work_dir.to_string_lossy().to_string(), )); self.validate(engine); } } } else if let Some(FileSelectorMessage::Commit(path)) = message.data::() { if message.destination() == self.work_dir_browser { if let Ok(work_dir) = path.clone().canonicalize() { self.work_dir = work_dir; engine .user_interfaces .first_mut() .send_message(TextMessage::text( self.tb_work_dir, MessageDirection::ToWidget, self.work_dir.to_string_lossy().to_string(), )); self.validate(engine); } } } else if let Some(ButtonMessage::Click) = message.data::() { if message.destination() == self.ok { self.sender.send(Message::Configure { working_directory: self.work_dir.clone(), }); let new_entry = HistoryEntry { work_dir: self.work_dir.clone(), }; if !self.history.iter().any(|e| e == &new_entry) { self.history.push(new_entry); let widget = make_history_entry_widget( &mut engine.user_interfaces.first_mut().build_ctx(), self.history.last().unwrap(), ); engine .user_interfaces .first_mut() .send_message(ListViewMessage::add_item( self.lv_history, MessageDirection::ToWidget, widget, )); } engine .user_interfaces .first_mut() .send_message(WindowMessage::close( self.window, MessageDirection::ToWidget, )); } else if message.destination() == self.select_work_dir { engine .user_interfaces .first_mut() .send_message(WindowMessage::open_modal( self.work_dir_browser, MessageDirection::ToWidget, true, true, )); } } } }