use crate::{
command::{Command, CommandContext, CommandStack, CommandTrait},
fyrox::{
asset::Resource,
core::{
futures::executor::block_on, math::curve::Curve, pool::Handle, type_traits::prelude::*,
visitor::prelude::*,
},
engine::Engine,
gui::{
border::BorderBuilder,
button::{ButtonBuilder, ButtonMessage},
curve::{CurveEditorBuilder, CurveEditorMessage},
file_browser::FileSelectorMessage,
grid::{Column, GridBuilder, Row},
menu::{MenuBuilder, MenuItemBuilder, MenuItemContent, MenuItemMessage},
message::UiMessage,
messagebox::{MessageBoxBuilder, MessageBoxResult},
stack_panel::StackPanelBuilder,
widget::{WidgetBuilder, WidgetMessage},
window::{WindowBuilder, WindowMessage, WindowTitle},
BuildContext, HorizontalAlignment, Orientation, Thickness, UserInterface,
},
resource::curve::{CurveResource, CurveResourceState},
},
menu::create_menu_item,
plugin::EditorPlugin,
utils::create_file_selector,
Editor, MessageBoxButtons, MessageBoxMessage,
};
use fyrox::asset::manager::ResourceManager;
use fyrox::core::some_or_return;
use fyrox::gui::button::Button;
use fyrox::gui::curve::CurveEditor;
use fyrox::gui::file_browser::{FileSelector, FileSelectorMode, FileType};
use fyrox::gui::menu::MenuItem;
use fyrox::gui::messagebox::MessageBox;
use fyrox::gui::style::resource::StyleResourceExt;
use fyrox::gui::style::Style;
use fyrox::gui::window::{Window, WindowAlignment};
use std::{fmt::Debug, path::PathBuf};
#[derive(Debug, ComponentProvider)]
pub struct CurveEditorContext {}
impl CommandContext for CurveEditorContext {}
#[derive(Debug)]
struct ModifyCurveCommand {
curve_resource: CurveResource,
curve: Curve,
}
impl ModifyCurveCommand {
fn swap(&mut self) {
std::mem::swap(&mut self.curve_resource.data_ref().curve, &mut self.curve);
}
}
impl CommandTrait for ModifyCurveCommand {
fn name(&mut self, _: &dyn CommandContext) -> String {
"Modify Curve".to_owned()
}
fn execute(&mut self, _: &mut dyn CommandContext) {
self.swap();
}
fn revert(&mut self, _: &mut dyn CommandContext) {
self.swap();
}
}
struct FileMenu {
new: Handle<MenuItem>,
save: Handle<MenuItem>,
load: Handle<MenuItem>,
}
struct EditMenu {
undo: Handle<MenuItem>,
redo: Handle<MenuItem>,
}
struct Menu {
file: FileMenu,
edit: EditMenu,
}
pub struct CurveEditorWindow {
window: Handle<Window>,
curve_editor: Handle<CurveEditor>,
ok: Handle<Button>,
cancel: Handle<Button>,
curve_resource: Option<CurveResource>,
command_stack: CommandStack,
menu: Menu,
load_file_selector: Handle<FileSelector>,
save_file_selector: Handle<FileSelector>,
path: PathBuf,
save_changes_message_box: Handle<MessageBox>,
cancel_message_box: Handle<MessageBox>,
modified: bool,
backup: Curve,
}
impl CurveEditorWindow {
pub fn new(ctx: &mut BuildContext) -> Self {
let file_type = FileType::new()
.with_extension("crv")
.with_description("Curve");
let load_file_selector =
create_file_selector(ctx, file_type.clone(), FileSelectorMode::Open);
let save_file_selector = create_file_selector(
ctx,
file_type,
FileSelectorMode::Save {
default_file_name: PathBuf::from("unnamed.crv"),
},
);
let save_changes_message_box = MessageBoxBuilder::new(
WindowBuilder::new(WidgetBuilder::new())
.open(false)
.with_title(WindowTitle::text("Unsaved Changes")),
)
.with_text(
"You have unsaved changes, do you want to save it before closing the curve editor?",
)
.with_buttons(MessageBoxButtons::YesNoCancel)
.build(ctx);
let cancel_message_box = MessageBoxBuilder::new(
WindowBuilder::new(WidgetBuilder::new())
.open(false)
.with_title(WindowTitle::text("Unsaved Changes")),
)
.with_text("You have unsaved changes, do you want to quit the curve editor without saving?")
.with_buttons(MessageBoxButtons::YesNo)
.build(ctx);
let curve_editor;
let ok;
let cancel;
let new;
let save;
let load;
let undo;
let redo;
let window = WindowBuilder::new(WidgetBuilder::new().with_width(400.0).with_height(300.0))
.open(false)
.with_content(
GridBuilder::new(
WidgetBuilder::new()
.with_child(
MenuBuilder::new(WidgetBuilder::new())
.with_items(vec![
MenuItemBuilder::new(WidgetBuilder::new())
.with_content(MenuItemContent::text("File"))
.with_items(vec![
{
new = MenuItemBuilder::new(WidgetBuilder::new())
.with_content(
MenuItemContent::text_with_shortcut(
"New", "Ctrl+N",
),
)
.build(ctx);
new
},
{
load = MenuItemBuilder::new(WidgetBuilder::new())
.with_content(
MenuItemContent::text_with_shortcut(
"Load", "Ctrl+L",
),
)
.build(ctx);
load
},
{
save = MenuItemBuilder::new(WidgetBuilder::new())
.with_content(
MenuItemContent::text_with_shortcut(
"Save", "Ctrl+S",
),
)
.build(ctx);
save
},
])
.build(ctx),
MenuItemBuilder::new(WidgetBuilder::new())
.with_content(MenuItemContent::text("Edit"))
.with_items(vec![
{
undo = MenuItemBuilder::new(WidgetBuilder::new())
.with_content(
MenuItemContent::text_with_shortcut(
"Undo", "Ctrl+Z",
),
)
.build(ctx);
undo
},
{
redo = MenuItemBuilder::new(WidgetBuilder::new())
.with_content(
MenuItemContent::text_with_shortcut(
"Redo", "Ctrl+Y",
),
)
.build(ctx);
redo
},
])
.build(ctx),
])
.build(ctx),
)
.with_child(
BorderBuilder::new(
WidgetBuilder::new()
.on_row(1)
.on_column(0)
.with_background(ctx.style.property(Style::BRUSH_DARKEST))
.with_child({
curve_editor = CurveEditorBuilder::new(
WidgetBuilder::new().with_enabled(false),
)
.build(ctx);
curve_editor
}),
)
.build(ctx),
)
.with_child(
StackPanelBuilder::new(
WidgetBuilder::new()
.on_row(2)
.on_column(0)
.with_horizontal_alignment(HorizontalAlignment::Right)
.with_child({
ok = ButtonBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::uniform(1.0))
.with_width(100.0),
)
.with_text("OK")
.build(ctx);
ok
})
.with_child({
cancel = ButtonBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::uniform(1.0))
.with_width(100.0),
)
.with_text("Cancel")
.build(ctx);
cancel
}),
)
.with_orientation(Orientation::Horizontal)
.build(ctx),
),
)
.add_row(Row::strict(25.0))
.add_row(Row::stretch())
.add_row(Row::strict(25.0))
.add_column(Column::stretch())
.build(ctx),
)
.with_remove_on_close(true)
.with_title(WindowTitle::text("Curve Editor"))
.with_tab_label("Curve")
.build(ctx);
Self {
window,
curve_editor,
ok,
cancel,
curve_resource: None,
command_stack: CommandStack::new(false, 2048),
menu: Menu {
file: FileMenu { new, save, load },
edit: EditMenu { undo, redo },
},
load_file_selector,
save_file_selector,
path: Default::default(),
save_changes_message_box,
modified: false,
backup: Default::default(),
cancel_message_box,
}
}
fn destroy(self, ui: &UserInterface) {
ui.send(self.cancel_message_box, WidgetMessage::Remove);
ui.send(self.save_changes_message_box, WidgetMessage::Remove);
ui.send(self.load_file_selector, WidgetMessage::Remove);
ui.send(self.save_file_selector, WidgetMessage::Remove);
ui.send(self.window, WindowMessage::Close);
}
pub fn open(&self, ui: &UserInterface) {
ui.send(
self.window,
WindowMessage::Open {
alignment: WindowAlignment::Center,
modal: true,
focus_content: true,
},
);
}
fn sync_to_model(&mut self, ui: &UserInterface) {
if let Some(curve_resource) = self.curve_resource.as_ref() {
ui.send_sync(
self.curve_editor,
CurveEditorMessage::Sync(vec![curve_resource.data_ref().curve.clone()]),
);
}
}
fn save(&self) {
if let Some(curve_resource) = self.curve_resource.as_ref() {
if let Some(state) = curve_resource.state().data() {
let mut visitor = Visitor::new();
state.curve.visit("Curve", &mut visitor).unwrap();
visitor.save_binary_to_file(&self.path).unwrap();
}
}
}
fn set_curve(
&mut self,
resource_manager: &ResourceManager,
curve: CurveResource,
ui: &UserInterface,
) {
self.backup = curve.data_ref().curve.clone();
self.curve_resource = Some(curve);
ui.send(self.curve_editor, WidgetMessage::Enabled(true));
self.sync_to_model(ui);
self.sync_title(resource_manager, ui);
self.modified = false;
self.command_stack.clear(&mut CurveEditorContext {});
}
fn sync_title(&self, resource_manager: &ResourceManager, ui: &UserInterface) {
let title = if let Some(curve_resource) = self.curve_resource.as_ref() {
match resource_manager.resource_path(curve_resource.as_ref()) {
Some(path) => {
format!("Curve Editor - {}", path.display())
}
None => "Curve Editor - Unnamed Curve".to_string(),
}
} else {
"Curve Editor".to_string()
};
ui.send(self.window, WindowMessage::Title(WindowTitle::text(title)));
}
fn revert(&self) {
if let Some(curve_resource) = self.curve_resource.as_ref() {
curve_resource.data_ref().curve = self.backup.clone();
}
}
fn open_save_file_dialog(&self, resource_manager: &ResourceManager, ui: &UserInterface) {
ui.send(
self.save_file_selector,
FileSelectorMessage::Root(Some(resource_manager.registry_folder())),
);
ui.send(
self.save_file_selector,
WindowMessage::Open {
alignment: WindowAlignment::Center,
modal: true,
focus_content: true,
},
);
}
pub fn handle_ui_message(mut self, message: &UiMessage, engine: &mut Engine) -> Option<Self> {
let ui = &engine.user_interfaces.first_mut();
if let Some(ButtonMessage::Click) = message.data() {
if message.destination() == self.cancel {
if self.modified && self.curve_resource.is_some() {
ui.send(
self.cancel_message_box,
MessageBoxMessage::Open {
text: None,
title: None,
},
);
} else {
self.destroy(ui);
return None;
}
} else if message.destination() == self.ok {
if self.modified && self.curve_resource.is_some() {
if self.path == PathBuf::default() {
ui.send(
self.save_changes_message_box,
MessageBoxMessage::Open {
text: None,
title: None,
},
);
} else {
self.save();
self.destroy(ui);
return None;
}
} else {
self.destroy(ui);
return None;
}
}
} else if let Some(CurveEditorMessage::Sync(curve)) = message.data_from(self.curve_editor) {
if let Some(curve_resource) = self.curve_resource.as_ref() {
self.command_stack.do_command(
Command::new(ModifyCurveCommand {
curve_resource: curve_resource.clone(),
curve: curve.first().cloned().unwrap(),
}),
&mut CurveEditorContext {},
);
self.modified = true;
}
} else if let Some(MenuItemMessage::Click) = message.data() {
if message.destination() == self.menu.edit.undo {
self.command_stack.undo(&mut CurveEditorContext {});
self.sync_to_model(ui);
} else if message.destination() == self.menu.edit.redo {
self.command_stack.redo(&mut CurveEditorContext {});
self.sync_to_model(ui);
} else if message.destination() == self.menu.file.load {
ui.send(
self.load_file_selector,
FileSelectorMessage::Root(Some(engine.resource_manager.registry_folder())),
);
ui.send(
self.load_file_selector,
WindowMessage::Open {
alignment: WindowAlignment::Center,
modal: true,
focus_content: true,
},
);
} else if message.destination() == self.menu.file.new {
self.path = Default::default();
self.set_curve(
&engine.resource_manager,
Resource::new_embedded(CurveResourceState::default()),
ui,
);
} else if message.destination() == self.menu.file.save {
if self.path == PathBuf::default() {
self.open_save_file_dialog(&engine.resource_manager, ui);
} else {
self.save();
}
}
} else if let Some(FileSelectorMessage::Commit(path)) = message.data() {
if message.destination() == self.load_file_selector {
if let Ok(curve) =
block_on(engine.resource_manager.request::<CurveResourceState>(path))
{
self.path.clone_from(path);
self.set_curve(&engine.resource_manager, curve, ui);
}
} else if message.destination() == self.save_file_selector {
self.path.clone_from(path);
self.save();
}
} else if let Some(MessageBoxMessage::Close(result)) = message.data() {
if message.destination() == self.save_changes_message_box {
match result {
MessageBoxResult::No => {
self.revert();
self.destroy(ui);
return None;
}
MessageBoxResult::Yes => {
if self.path == PathBuf::default() {
self.open_save_file_dialog(&engine.resource_manager, ui);
} else {
self.save();
self.destroy(ui);
return None;
}
}
_ => (),
}
} else if message.destination() == self.cancel_message_box {
if let MessageBoxResult::Yes = result {
self.revert();
self.destroy(ui);
return None;
}
}
}
Some(self)
}
}
#[derive(Default)]
pub struct CurveEditorPlugin {
curve_editor_window: Option<CurveEditorWindow>,
open_curve_editor: Handle<MenuItem>,
}
impl CurveEditorPlugin {
pub const CURVE_EDITOR: Uuid = uuid!("20705d17-741d-45bf-a9ba-ec3cee34ac2b");
fn on_open_curve_editor_clicked(&mut self, editor: &mut Editor) {
let ui = editor.engine.user_interfaces.first_mut();
let ctx = &mut ui.build_ctx();
let curve_editor = self
.curve_editor_window
.get_or_insert_with(|| CurveEditorWindow::new(ctx));
curve_editor.open(ui);
}
}
impl EditorPlugin for CurveEditorPlugin {
fn on_start(&mut self, editor: &mut Editor) {
let ui = editor.engine.user_interfaces.first_mut();
let ctx = &mut ui.build_ctx();
self.open_curve_editor = create_menu_item("Curve Editor", Self::CURVE_EDITOR, vec![], ctx);
ui.send(
editor.menu.utils_menu.menu,
MenuItemMessage::AddItem(self.open_curve_editor),
);
}
fn on_ui_message(&mut self, message: &mut UiMessage, editor: &mut Editor) {
if let Some(MenuItemMessage::Click) = message.data() {
if message.destination() == self.open_curve_editor {
self.on_open_curve_editor_clicked(editor)
}
}
let curve_editor = some_or_return!(self.curve_editor_window.take());
self.curve_editor_window = curve_editor.handle_ui_message(message, &mut editor.engine);
}
}