chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,586 @@
|
||||
// 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::{
|
||||
command::{Command, CommandContext, CommandStack, CommandTrait},
|
||||
fyrox::{
|
||||
asset::manager::ResourceManager,
|
||||
asset::Resource,
|
||||
core::{
|
||||
futures::executor::block_on, math::curve::Curve, pool::Handle, reflect::prelude::*,
|
||||
some_or_return, 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,
|
||||
},
|
||||
gui::{
|
||||
button::Button,
|
||||
curve::CurveEditor,
|
||||
file_browser::{FileSelector, FileSelectorMode, FileType},
|
||||
menu::MenuItem,
|
||||
messagebox::MessageBox,
|
||||
style::resource::StyleResourceExt,
|
||||
style::Style,
|
||||
window::{Window, WindowAlignment},
|
||||
},
|
||||
resource::curve::{CurveResource, CurveResourceState},
|
||||
},
|
||||
menu::create_menu_item,
|
||||
plugin::EditorPlugin,
|
||||
utils::create_file_selector,
|
||||
Editor, MessageBoxButtons, MessageBoxMessage,
|
||||
};
|
||||
use std::{fmt::Debug, path::PathBuf};
|
||||
|
||||
#[derive(PartialEq, Reflect, Clone, Debug)]
|
||||
#[reflect(type_uuid = "f29aa9c7-e0c4-4602-8588-3e8a20ab7cdc")]
|
||||
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_from(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 let Some(MessageBoxMessage::Close(MessageBoxResult::Yes)) =
|
||||
message.data_from(self.cancel_message_box)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user