#![forbid(unsafe_code)]
#![allow(irrefutable_let_patterns)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::large_enum_variant)]
#![allow(clippy::mixed_read_write_in_expression)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::inconsistent_struct_constructor)]
#[macro_use]
extern crate lazy_static;
mod absm;
mod animation;
mod asset;
mod audio;
mod build;
mod camera;
mod command;
mod configurator;
mod curve_editor;
mod gui;
mod inspector;
mod interaction;
mod light;
mod log;
mod material;
mod menu;
mod overlay;
mod particle;
mod preview;
mod scene;
mod scene_viewer;
mod settings;
mod utils;
mod world;
use crate::{
absm::AbsmEditor,
animation::AnimationEditor,
asset::{item::AssetItem, item::AssetKind, AssetBrowser},
audio::AudioPanel,
build::BuildWindow,
command::{panel::CommandStackViewer, Command, CommandStack},
configurator::Configurator,
curve_editor::CurveEditorWindow,
inspector::{editors::handle::HandlePropertyEditorMessage, Inspector},
interaction::{
move_mode::MoveInteractionMode,
navmesh::{EditNavmeshMode, NavmeshPanel},
rotate_mode::RotateInteractionMode,
scale_mode::ScaleInteractionMode,
select_mode::SelectInteractionMode,
terrain::TerrainInteractionMode,
InteractionMode, InteractionModeKind,
},
light::LightPanel,
log::LogPanel,
material::MaterialEditor,
menu::{Menu, MenuContext, Panels},
overlay::OverlayRenderPass,
particle::ParticleSystemPreviewControlPanel,
scene::{
commands::{
graph::AddModelCommand, make_delete_selection_command, mesh::SetMeshTextureCommand,
ChangeSelectionCommand, CommandGroup, PasteCommand, SceneCommand, SceneContext,
},
is_scene_needs_to_be_saved,
settings::SceneSettingsWindow,
EditorScene, Selection,
},
scene_viewer::SceneViewer,
settings::{camera::SceneCameraSettings, Settings},
utils::path_fixer::PathFixer,
world::{graph::selection::GraphSelection, WorldViewer},
};
use fyrox::{
core::{
algebra::{Matrix3, Vector2},
color::Color,
futures::executor::block_on,
pool::{ErasedHandle, Handle},
scope_profile,
sstorage::ImmutableString,
visitor::Visitor,
},
dpi::LogicalSize,
engine::{resource_manager::ResourceManager, Engine, EngineInitParams, SerializationContext},
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
fxhash::FxHashMap,
gui::{
brush::Brush,
dock::{DockingManagerBuilder, TileBuilder, TileContent},
draw,
dropdown_list::DropdownListBuilder,
file_browser::{FileBrowserMode, FileSelectorBuilder, FileSelectorMessage, Filter},
formatted_text::WrapMode,
grid::{Column, GridBuilder, Row},
key::HotKey,
message::{MessageDirection, UiMessage},
messagebox::{MessageBoxBuilder, MessageBoxButtons, MessageBoxMessage, MessageBoxResult},
ttf::Font,
widget::{WidgetBuilder, WidgetMessage},
window::{WindowBuilder, WindowMessage, WindowTitle},
BuildContext, UiNode, UserInterface, VerticalAlignment,
},
material::{shader::Shader, Material, PropertyValue, SharedMaterial},
plugin::PluginConstructor,
resource::texture::{CompressionOptions, Texture, TextureKind},
scene::{
camera::{Camera, Projection},
mesh::Mesh,
node::Node,
particle_system::ParticleSystem,
Scene, SceneLoader,
},
utils::{
into_gui_texture,
log::{Log, MessageKind},
translate_cursor_icon, translate_event,
watcher::FileSystemWatcher,
},
};
use std::{
any::TypeId,
io::{BufRead, BufReader},
path::{Path, PathBuf},
process::Stdio,
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self, channel, Receiver, Sender},
Arc,
},
time::{Duration, Instant},
};
pub const FIXED_TIMESTEP: f32 = 1.0 / 60.0;
pub const MSG_SYNC_FLAG: u64 = 1;
pub fn send_sync_message(ui: &UserInterface, mut msg: UiMessage) {
msg.flags = MSG_SYNC_FLAG;
ui.send_message(msg);
}
type GameEngine = fyrox::engine::Engine;
pub fn load_image(data: &[u8]) -> Option<draw::SharedTexture> {
Some(into_gui_texture(
Texture::load_from_memory(data, CompressionOptions::NoCompression, false).ok()?,
))
}
lazy_static! {
static ref GIZMO_SHADER: Shader = {
Shader::from_str(
include_str!("../resources/embed/shaders/gizmo.shader",),
PathBuf::default(),
)
.unwrap()
};
}
pub fn make_color_material(color: Color) -> SharedMaterial {
let mut material = Material::from_shader(GIZMO_SHADER.clone(), None);
material
.set_property(
&ImmutableString::new("diffuseColor"),
PropertyValue::Color(color),
)
.unwrap();
SharedMaterial::new(material)
}
pub fn set_mesh_diffuse_color(mesh: &mut Mesh, color: Color) {
for surface in mesh.surfaces() {
surface
.material()
.lock()
.set_property(
&ImmutableString::new("diffuseColor"),
PropertyValue::Color(color),
)
.unwrap();
}
}
pub fn create_terrain_layer_material() -> SharedMaterial {
let mut material = Material::standard_terrain();
material
.set_property(
&ImmutableString::new("texCoordScale"),
PropertyValue::Vector2(Vector2::new(10.0, 10.0)),
)
.unwrap();
SharedMaterial::new(material)
}
#[derive(Debug)]
pub enum BuildProfile {
Debug,
Release,
}
#[derive(Debug)]
pub enum Message {
DoSceneCommand(SceneCommand),
UndoSceneCommand,
RedoSceneCommand,
ClearSceneCommandStack,
SelectionChanged {
old_selection: Selection,
},
SaveScene(PathBuf),
LoadScene(PathBuf),
CloseScene,
SetInteractionMode(InteractionModeKind),
Configure {
working_directory: PathBuf,
},
NewScene,
Exit {
force: bool,
},
OpenSettings,
OpenAnimationEditor,
OpenAbsmEditor,
OpenMaterialEditor(SharedMaterial),
ShowInAssetBrowser(PathBuf),
SetWorldViewerFilter(String),
LocateObject {
type_id: TypeId,
handle: ErasedHandle,
},
SelectObject {
type_id: TypeId,
handle: ErasedHandle,
},
SetEditorCameraProjection(Projection),
SwitchToPlayMode,
SwitchToEditMode,
SwitchMode,
OpenLoadSceneDialog,
OpenSaveSceneDialog,
OpenSaveSceneConfirmationDialog(SaveSceneConfirmationDialogAction),
SetBuildProfile(BuildProfile),
SaveSelectionAsPrefab(PathBuf),
SyncNodeHandleName {
view: Handle<UiNode>,
handle: Handle<Node>,
},
ForceSync,
}
impl Message {
pub fn do_scene_command<C: Command>(cmd: C) -> Self {
Self::DoSceneCommand(SceneCommand::new(cmd))
}
}
pub fn make_scene_file_filter() -> Filter {
Filter::new(|p: &Path| {
if let Some(ext) = p.extension() {
ext.to_string_lossy().as_ref() == "rgs"
} else {
p.is_dir()
}
})
}
pub fn make_save_file_selector(ctx: &mut BuildContext) -> Handle<UiNode> {
FileSelectorBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(400.0))
.with_title(WindowTitle::Text("Save Scene As".into()))
.open(false),
)
.with_mode(FileBrowserMode::Save {
default_file_name: PathBuf::from("unnamed.rgs"),
})
.with_path("./")
.with_filter(make_scene_file_filter())
.build(ctx)
}
pub enum Mode {
Edit,
Build {
process: std::process::Child,
},
Play {
process: std::process::Child,
active: Arc<AtomicBool>,
},
}
impl Mode {
pub fn is_edit(&self) -> bool {
matches!(self, Mode::Edit { .. })
}
}
pub struct GameLoopData {
clock: Instant,
lag: f32,
}
pub struct StartupData {
pub working_directory: PathBuf,
pub scene: PathBuf,
}
#[derive(Debug)]
pub enum SaveSceneConfirmationDialogAction {
None,
OpenLoadSceneDialog,
LoadScene(PathBuf),
MakeNewScene,
CloseScene,
}
struct SaveSceneConfirmationDialog {
save_message_box: Handle<UiNode>,
action: SaveSceneConfirmationDialogAction,
}
impl SaveSceneConfirmationDialog {
pub fn new(ctx: &mut BuildContext) -> Self {
let save_message_box = MessageBoxBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(100.0))
.can_close(false)
.can_minimize(false)
.open(false)
.with_title(WindowTitle::Text("Unsaved changes".to_owned())),
)
.with_text("There are unsaved changes. Do you wish to save them before continue?")
.with_buttons(MessageBoxButtons::YesNoCancel)
.build(ctx);
Self {
save_message_box,
action: SaveSceneConfirmationDialogAction::None,
}
}
pub fn open(&mut self, ui: &UserInterface, action: SaveSceneConfirmationDialogAction) {
ui.send_message(MessageBoxMessage::open(
self.save_message_box,
MessageDirection::ToWidget,
None,
None,
));
self.action = action;
}
pub fn handle_ui_message(
&mut self,
message: &UiMessage,
sender: &Sender<Message>,
editor_scene: Option<&EditorScene>,
) {
if let Some(MessageBoxMessage::Close(result)) = message.data() {
if message.destination() == self.save_message_box {
match result {
MessageBoxResult::No => match self.action {
SaveSceneConfirmationDialogAction::None => {}
SaveSceneConfirmationDialogAction::OpenLoadSceneDialog => {
sender.send(Message::OpenLoadSceneDialog).unwrap()
}
SaveSceneConfirmationDialogAction::MakeNewScene => {
sender.send(Message::NewScene).unwrap()
}
SaveSceneConfirmationDialogAction::CloseScene => {
sender.send(Message::CloseScene).unwrap()
}
SaveSceneConfirmationDialogAction::LoadScene(ref path) => {
sender.send(Message::LoadScene(path.clone())).unwrap()
}
},
MessageBoxResult::Yes => {
if let Some(editor_scene) = editor_scene {
if let Some(path) = editor_scene.path.clone() {
sender.send(Message::SaveScene(path)).unwrap();
match self.action {
SaveSceneConfirmationDialogAction::None => {}
SaveSceneConfirmationDialogAction::OpenLoadSceneDialog => {
sender.send(Message::OpenLoadSceneDialog).unwrap()
}
SaveSceneConfirmationDialogAction::MakeNewScene => {
sender.send(Message::NewScene).unwrap()
}
SaveSceneConfirmationDialogAction::CloseScene => {
sender.send(Message::CloseScene).unwrap()
}
SaveSceneConfirmationDialogAction::LoadScene(ref path) => {
sender.send(Message::LoadScene(path.clone())).unwrap()
}
}
self.action = SaveSceneConfirmationDialogAction::None;
} else {
match self.action {
SaveSceneConfirmationDialogAction::None => {}
SaveSceneConfirmationDialogAction::OpenLoadSceneDialog
| SaveSceneConfirmationDialogAction::LoadScene(_)
| SaveSceneConfirmationDialogAction::MakeNewScene
| SaveSceneConfirmationDialogAction::CloseScene => {
sender.send(Message::OpenSaveSceneDialog).unwrap()
}
}
}
}
}
_ => (),
}
}
}
}
fn handle_message(&mut self, message: &Message, sender: &Sender<Message>) {
if let Message::SaveScene(_) = message {
match std::mem::replace(&mut self.action, SaveSceneConfirmationDialogAction::None) {
SaveSceneConfirmationDialogAction::None => {}
SaveSceneConfirmationDialogAction::OpenLoadSceneDialog => {
sender.send(Message::OpenLoadSceneDialog).unwrap();
}
SaveSceneConfirmationDialogAction::MakeNewScene => {
sender.send(Message::NewScene).unwrap()
}
SaveSceneConfirmationDialogAction::CloseScene => {
sender.send(Message::CloseScene).unwrap();
}
SaveSceneConfirmationDialogAction::LoadScene(path) => {
sender.send(Message::LoadScene(path)).unwrap()
}
}
}
}
}
pub struct Editor {
game_loop_data: GameLoopData,
engine: Engine,
scene: Option<EditorScene>,
command_stack: CommandStack,
message_sender: Sender<Message>,
message_receiver: Receiver<Message>,
interaction_modes: Vec<Box<dyn InteractionMode>>,
current_interaction_mode: Option<InteractionModeKind>,
world_viewer: WorldViewer,
root_grid: Handle<UiNode>,
scene_viewer: SceneViewer,
asset_browser: AssetBrowser,
exit_message_box: Handle<UiNode>,
save_file_selector: Handle<UiNode>,
save_scene_dialog: SaveSceneConfirmationDialog,
light_panel: LightPanel,
menu: Menu,
exit: bool,
configurator: Configurator,
log: LogPanel,
command_stack_viewer: CommandStackViewer,
validation_message_box: Handle<UiNode>,
navmesh_panel: NavmeshPanel,
settings: Settings,
path_fixer: PathFixer,
material_editor: MaterialEditor,
pub inspector: Inspector,
curve_editor: CurveEditorWindow,
audio_panel: AudioPanel,
absm_editor: AbsmEditor,
mode: Mode,
build_window: BuildWindow,
build_profile: BuildProfile,
scene_settings: SceneSettingsWindow,
animation_editor: AnimationEditor,
particle_system_control_panel: ParticleSystemPreviewControlPanel,
}
impl Editor {
pub fn new(event_loop: &EventLoop<()>, startup_data: Option<StartupData>) -> Self {
let (log_message_sender, log_message_receiver) = channel();
Log::add_listener(log_message_sender);
let inner_size = if let Some(primary_monitor) = event_loop.primary_monitor() {
let mut monitor_dimensions = primary_monitor.size();
monitor_dimensions.height = (monitor_dimensions.height as f32 * 0.7) as u32;
monitor_dimensions.width = (monitor_dimensions.width as f32 * 0.7) as u32;
monitor_dimensions.to_logical::<f32>(primary_monitor.scale_factor())
} else {
LogicalSize::new(1024.0, 768.0)
};
let window_builder = fyrox::window::WindowBuilder::new()
.with_inner_size(inner_size)
.with_title("Fyroxed")
.with_resizable(true);
let serialization_context = Arc::new(SerializationContext::new());
let mut engine = Engine::new(EngineInitParams {
window_builder,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
events_loop: event_loop,
vsync: true,
headless: false,
})
.unwrap();
let logical_size = engine
.get_window()
.inner_size()
.to_logical(engine.get_window().scale_factor());
set_ui_scaling(
&engine.user_interface,
engine.get_window().scale_factor() as f32,
);
let overlay_pass = OverlayRenderPass::new(engine.renderer.pipeline_state());
engine.renderer.add_render_pass(overlay_pass);
let (message_sender, message_receiver) = mpsc::channel();
engine.user_interface.default_font.set(
Font::from_memory(
include_bytes!("../resources/embed/arial.ttf").as_slice(),
14.0,
Font::default_char_set(),
)
.unwrap(),
);
let configurator = Configurator::new(
message_sender.clone(),
&mut engine.user_interface.build_ctx(),
);
let mut settings = Settings::default();
match Settings::load() {
Ok(s) => {
settings = s;
println!("Editor settings were loaded successfully!");
match engine
.renderer
.set_quality_settings(&settings.graphics.quality)
{
Ok(_) => {
println!("Graphics settings were applied successfully!");
}
Err(e) => {
println!("Failed to apply graphics settings! Reason: {:?}", e)
}
}
}
Err(e) => {
println!(
"Failed to load settings, fallback to default. Reason: {:?}",
e
)
}
}
let scene_viewer = SceneViewer::new(&mut engine, message_sender.clone());
let asset_browser = AssetBrowser::new(&mut engine);
let menu = Menu::new(&mut engine, message_sender.clone(), &settings);
let light_panel = LightPanel::new(&mut engine);
let audio_panel = AudioPanel::new(&mut engine);
let ctx = &mut engine.user_interface.build_ctx();
let navmesh_panel = NavmeshPanel::new(ctx, message_sender.clone());
let world_outliner = WorldViewer::new(ctx, message_sender.clone(), &settings);
let command_stack_viewer = CommandStackViewer::new(ctx, message_sender.clone());
let log = LogPanel::new(ctx, log_message_receiver);
let inspector = Inspector::new(ctx, message_sender.clone());
let animation_editor = AnimationEditor::new(ctx);
let absm_editor = AbsmEditor::new(ctx, message_sender.clone());
let particle_system_control_panel = ParticleSystemPreviewControlPanel::new(ctx);
let root_grid = GridBuilder::new(
WidgetBuilder::new()
.with_width(logical_size.width)
.with_height(logical_size.height)
.with_child(menu.menu)
.with_child(
DockingManagerBuilder::new(WidgetBuilder::new().on_row(1).with_child({
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::VerticalTiles {
splitter: 0.75,
tiles: [
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::HorizontalTiles {
splitter: 0.25,
tiles: [
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::Window(
world_outliner.window,
))
.build(ctx),
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::HorizontalTiles {
splitter: 0.66,
tiles: [
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::Window(
scene_viewer.window(),
))
.build(ctx),
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::Window(
inspector.window,
))
.build(ctx),
],
})
.build(ctx),
],
})
.build(ctx),
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::HorizontalTiles {
splitter: 0.66,
tiles: [
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::HorizontalTiles {
splitter: 0.80,
tiles: [
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::Window(
asset_browser.window,
))
.build(ctx),
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::Window(
command_stack_viewer.window,
))
.build(ctx),
],
})
.build(ctx),
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::HorizontalTiles {
splitter: 0.5,
tiles: [
TileBuilder::new(WidgetBuilder::new())
.with_content(TileContent::Window(
log.window,
))
.build(ctx),
TileBuilder::new(WidgetBuilder::new())
.with_content(
TileContent::HorizontalTiles {
splitter: 0.5,
tiles: [
TileBuilder::new(
WidgetBuilder::new(
),
)
.with_content(
TileContent::Window(
navmesh_panel
.window,
),
)
.build(ctx),
TileBuilder::new(
WidgetBuilder::new(
),
)
.with_content(
TileContent::Window(
audio_panel
.window,
),
)
.build(ctx),
],
},
)
.build(ctx),
],
})
.build(ctx),
],
})
.build(ctx),
],
})
.build(ctx)
}))
.with_floating_windows(vec![
animation_editor.window,
absm_editor.window,
particle_system_control_panel.window,
])
.build(ctx),
),
)
.add_row(Row::strict(25.0))
.add_row(Row::stretch())
.add_column(Column::stretch())
.build(ctx);
let save_file_selector = make_save_file_selector(ctx);
let exit_message_box = MessageBoxBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(100.0))
.can_close(false)
.can_minimize(false)
.open(false)
.with_title(WindowTitle::Text("Unsaved changes".to_owned())),
)
.with_text("There are unsaved changes. Do you wish to save them before exit?")
.with_buttons(MessageBoxButtons::YesNoCancel)
.build(ctx);
let validation_message_box = MessageBoxBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(400.0).with_height(500.0))
.can_close(false)
.can_minimize(false)
.open(false)
.with_title(WindowTitle::Text("Validation failed!".to_owned())),
)
.with_buttons(MessageBoxButtons::Ok)
.build(ctx);
let path_fixer = PathFixer::new(ctx);
let curve_editor = CurveEditorWindow::new(ctx);
let save_scene_dialog = SaveSceneConfirmationDialog::new(ctx);
let build_window = BuildWindow::new(ctx);
let scene_settings = SceneSettingsWindow::new(ctx, message_sender.clone());
let material_editor = MaterialEditor::new(&mut engine);
let mut editor = Self {
animation_editor,
engine,
navmesh_panel,
scene_viewer,
scene: None,
command_stack: CommandStack::new(false),
message_sender,
message_receiver,
interaction_modes: Default::default(),
current_interaction_mode: None,
world_viewer: world_outliner,
root_grid,
menu,
exit: false,
asset_browser,
exit_message_box,
save_file_selector,
configurator,
log,
light_panel,
command_stack_viewer,
validation_message_box,
settings,
path_fixer,
material_editor,
inspector,
curve_editor,
audio_panel,
save_scene_dialog,
mode: Mode::Edit,
game_loop_data: GameLoopData {
clock: Instant::now(),
lag: 0.0,
},
absm_editor,
build_window,
build_profile: BuildProfile::Debug,
scene_settings,
particle_system_control_panel,
};
editor.set_interaction_mode(Some(InteractionModeKind::Move));
if let Some(data) = startup_data {
editor
.message_sender
.send(Message::Configure {
working_directory: if data.working_directory == PathBuf::default() {
std::env::current_dir().unwrap()
} else {
data.working_directory
},
})
.unwrap();
if data.scene != PathBuf::default() {
editor
.message_sender
.send(Message::LoadScene(data.scene))
.unwrap();
}
} else {
editor
.engine
.user_interface
.send_message(WindowMessage::open_modal(
editor.configurator.window,
MessageDirection::ToWidget,
true,
));
}
editor
}
fn reload_settings(&mut self) {
match Settings::load() {
Ok(settings) => {
self.settings = settings;
Log::info("Editor settings were reloaded successfully!");
}
Err(e) => {
self.settings = Default::default();
Log::info(format!(
"Failed to load settings, fallback to default. Reason: {:?}",
e
))
}
}
self.menu
.file_menu
.update_recent_files_list(&mut self.engine.user_interface, &self.settings);
match self
.engine
.renderer
.set_quality_settings(&self.settings.graphics.quality)
{
Ok(_) => {
Log::info("Graphics settings were applied successfully!");
}
Err(e) => Log::info(format!(
"Failed to apply graphics settings! Reason: {:?}",
e
)),
}
}
fn set_scene(&mut self, mut scene: Scene, path: Option<PathBuf>) {
if let Some(previous_editor_scene) = self.scene.as_ref() {
self.engine.scenes.remove(previous_editor_scene.scene);
}
self.scene = None;
self.sync_to_model();
self.poll_ui_messages();
for mut interaction_mode in self.interaction_modes.drain(..) {
interaction_mode.on_drop(&mut self.engine);
}
scene.render_target = Some(Texture::new_render_target(0, 0));
self.scene_viewer
.set_render_target(&self.engine.user_interface, scene.render_target.clone());
let editor_scene =
EditorScene::from_native_scene(scene, &mut self.engine, path.clone(), &self.settings);
self.interaction_modes = vec![
Box::new(SelectInteractionMode::new(
self.scene_viewer.frame(),
self.scene_viewer.selection_frame(),
self.message_sender.clone(),
)),
Box::new(MoveInteractionMode::new(
&editor_scene,
&mut self.engine,
self.message_sender.clone(),
)),
Box::new(ScaleInteractionMode::new(
&editor_scene,
&mut self.engine,
self.message_sender.clone(),
)),
Box::new(RotateInteractionMode::new(
&editor_scene,
&mut self.engine,
self.message_sender.clone(),
)),
Box::new(EditNavmeshMode::new(
&editor_scene,
&mut self.engine,
self.message_sender.clone(),
)),
Box::new(TerrainInteractionMode::new(
&editor_scene,
&mut self.engine,
self.message_sender.clone(),
)),
];
self.command_stack = CommandStack::new(false);
self.scene = Some(editor_scene);
self.set_interaction_mode(Some(InteractionModeKind::Move));
if let Some(path) = path.as_ref() {
if !self.settings.recent.scenes.contains(path) {
self.settings.recent.scenes.push(path.clone());
Log::verify(self.settings.save());
self.menu
.file_menu
.update_recent_files_list(&mut self.engine.user_interface, &self.settings);
}
}
self.scene_viewer.set_title(
&self.engine.user_interface,
format!(
"Scene Preview - {}",
path.map_or("Unnamed Scene".to_string(), |p| p
.to_string_lossy()
.to_string())
),
);
self.scene_viewer
.reset_camera_projection(&self.engine.user_interface);
self.engine.renderer.flush();
}
fn set_interaction_mode(&mut self, mode: Option<InteractionModeKind>) {
let engine = &mut self.engine;
if let Some(editor_scene) = self.scene.as_ref() {
if self.current_interaction_mode != mode {
if let Some(current_mode) = self.current_interaction_mode {
self.interaction_modes[current_mode as usize].deactivate(editor_scene, engine);
}
self.current_interaction_mode = mode;
if let Some(current_mode) = self.current_interaction_mode {
self.interaction_modes[current_mode as usize].activate(editor_scene, engine);
}
}
}
}
pub fn handle_hotkeys(&mut self, message: &UiMessage) {
if message.handled() {
return;
}
let modifiers = self.engine.user_interface.keyboard_modifiers();
let sender = self.message_sender.clone();
let engine = &mut self.engine;
if let Some(WidgetMessage::KeyDown(key)) = message.data() {
let hot_key = HotKey::Some {
code: *key,
modifiers,
};
let key_bindings = &self.settings.key_bindings;
if hot_key == key_bindings.redo {
sender.send(Message::RedoSceneCommand).unwrap();
} else if hot_key == key_bindings.undo {
sender.send(Message::UndoSceneCommand).unwrap();
} else if hot_key == key_bindings.enable_select_mode {
sender
.send(Message::SetInteractionMode(InteractionModeKind::Select))
.unwrap();
} else if hot_key == key_bindings.enable_move_mode {
sender
.send(Message::SetInteractionMode(InteractionModeKind::Move))
.unwrap();
} else if hot_key == key_bindings.enable_rotate_mode {
sender
.send(Message::SetInteractionMode(InteractionModeKind::Rotate))
.unwrap();
} else if hot_key == key_bindings.enable_scale_mode {
sender
.send(Message::SetInteractionMode(InteractionModeKind::Scale))
.unwrap();
} else if hot_key == key_bindings.enable_navmesh_mode {
sender
.send(Message::SetInteractionMode(InteractionModeKind::Navmesh))
.unwrap();
} else if hot_key == key_bindings.enable_terrain_mode {
sender
.send(Message::SetInteractionMode(InteractionModeKind::Terrain))
.unwrap();
} else if hot_key == key_bindings.load_scene {
sender.send(Message::OpenLoadSceneDialog).unwrap();
} else if hot_key == key_bindings.save_scene {
if let Some(scene) = self.scene.as_ref() {
if let Some(path) = scene.path.as_ref() {
self.message_sender
.send(Message::SaveScene(path.clone()))
.unwrap();
} else {
engine
.user_interface
.send_message(WindowMessage::open_modal(
self.save_file_selector,
MessageDirection::ToWidget,
true,
));
}
}
} else if hot_key == key_bindings.copy_selection {
if let Some(editor_scene) = self.scene.as_mut() {
if let Selection::Graph(graph_selection) = &editor_scene.selection {
editor_scene.clipboard.fill_from_selection(
graph_selection,
editor_scene.scene,
engine,
);
}
}
} else if hot_key == key_bindings.paste {
if let Some(editor_scene) = self.scene.as_mut() {
if !editor_scene.clipboard.is_empty() {
sender
.send(Message::do_scene_command(PasteCommand::new(
engine.scenes[editor_scene.scene].graph.get_root(),
)))
.unwrap();
}
}
} else if hot_key == key_bindings.new_scene {
sender.send(Message::NewScene).unwrap();
} else if hot_key == key_bindings.close_scene {
sender.send(Message::CloseScene).unwrap();
} else if hot_key == key_bindings.remove_selection {
if let Some(editor_scene) = self.scene.as_mut() {
if !editor_scene.selection.is_empty() {
if let Selection::Graph(_) = editor_scene.selection {
sender
.send(Message::DoSceneCommand(make_delete_selection_command(
editor_scene,
engine,
)))
.unwrap();
}
}
}
}
}
}
pub fn handle_ui_message(&mut self, message: &mut UiMessage) {
scope_profile!();
if message.has_flags(MSG_SYNC_FLAG) {
return;
}
let engine = &mut self.engine;
self.save_scene_dialog.handle_ui_message(
message,
&self.message_sender,
self.scene.as_ref(),
);
self.configurator.handle_ui_message(message, engine);
self.menu.handle_ui_message(
message,
MenuContext {
engine,
editor_scene: self.scene.as_mut(),
panels: Panels {
inspector_window: self.inspector.window,
world_outliner_window: self.world_viewer.window,
asset_window: self.asset_browser.window,
light_panel: self.light_panel.window,
log_panel: self.log.window,
navmesh_panel: self.navmesh_panel.window,
audio_panel: self.audio_panel.window,
configurator_window: self.configurator.window,
path_fixer: self.path_fixer.window,
curve_editor: &self.curve_editor,
absm_editor: &self.absm_editor,
command_stack_panel: self.command_stack_viewer.window,
scene_settings: &self.scene_settings,
animation_editor: &self.animation_editor,
},
settings: &mut self.settings,
},
);
self.build_window
.handle_ui_message(message, &self.message_sender, &engine.user_interface);
self.log.handle_ui_message(message, engine);
self.asset_browser
.handle_ui_message(message, engine, self.message_sender.clone());
self.command_stack_viewer.handle_ui_message(message);
self.curve_editor.handle_ui_message(message, engine);
self.path_fixer.handle_ui_message(
message,
&mut engine.user_interface,
engine.serialization_context.clone(),
engine.resource_manager.clone(),
);
self.scene_viewer.handle_ui_message(
message,
engine,
self.scene.as_mut(),
self.current_interaction_mode
.and_then(|i| self.interaction_modes.get_mut(i as usize)),
&self.settings,
&self.mode,
);
self.animation_editor.handle_ui_message(
message,
self.scene.as_mut(),
engine,
&self.message_sender,
);
if let Some(editor_scene) = self.scene.as_mut() {
self.particle_system_control_panel
.handle_ui_message(message, editor_scene, engine);
self.absm_editor
.handle_ui_message(message, engine, &self.message_sender, editor_scene);
self.audio_panel
.handle_ui_message(message, editor_scene, &self.message_sender, engine);
self.scene_settings
.handle_ui_message(message, &self.message_sender);
self.navmesh_panel.handle_message(
message,
editor_scene,
engine,
if let Some(edit_mode) = self.interaction_modes
[InteractionModeKind::Navmesh as usize]
.as_any_mut()
.downcast_mut()
{
edit_mode
} else {
unreachable!()
},
);
self.inspector
.handle_ui_message(message, editor_scene, engine, &self.message_sender);
if let Some(current_im) = self.current_interaction_mode {
self.interaction_modes[current_im as usize].handle_ui_message(
message,
editor_scene,
engine,
);
}
self.world_viewer
.handle_ui_message(message, editor_scene, engine, &mut self.settings);
self.light_panel
.handle_ui_message(message, editor_scene, engine);
self.material_editor
.handle_ui_message(message, engine, &self.message_sender);
if let Some(MessageBoxMessage::Close(result)) = message.data::<MessageBoxMessage>() {
if message.destination() == self.exit_message_box {
match result {
MessageBoxResult::No => {
self.message_sender
.send(Message::Exit { force: true })
.unwrap();
}
MessageBoxResult::Yes => {
if let Some(scene) = self.scene.as_ref() {
if let Some(path) = scene.path.as_ref() {
self.message_sender
.send(Message::SaveScene(path.clone()))
.unwrap();
self.message_sender
.send(Message::Exit { force: true })
.unwrap();
} else {
engine
.user_interface
.send_message(WindowMessage::open_modal(
self.save_file_selector,
MessageDirection::ToWidget,
true,
));
}
}
}
_ => {}
}
}
} else if let Some(FileSelectorMessage::Commit(path)) =
message.data::<FileSelectorMessage>()
{
if message.destination() == self.save_file_selector {
self.message_sender
.send(Message::SaveScene(path.clone()))
.unwrap();
self.message_sender
.send(Message::Exit { force: true })
.unwrap();
}
}
}
self.handle_hotkeys(message);
}
fn set_play_mode(&mut self) {
if let Some(scene) = self.scene.as_ref() {
if let Some(path) = scene.path.as_ref().cloned() {
self.save_current_scene(path.clone());
let mut process = std::process::Command::new("cargo");
process
.stdout(Stdio::piped())
.arg("run")
.arg("--package")
.arg("executor");
if let BuildProfile::Release = self.build_profile {
process.arg("--release");
};
process.arg("--").arg("--override-scene").arg(path);
match process.spawn() {
Ok(mut process) => {
let active = Arc::new(AtomicBool::new(true));
let mut stdout = process.stdout.take().unwrap();
let reader_active = active.clone();
std::thread::spawn(move || {
while reader_active.load(Ordering::SeqCst) {
for line in BufReader::new(&mut stdout).lines().take(10).flatten() {
Log::info(line);
}
}
});
self.mode = Mode::Play { active, process };
self.on_mode_changed();
}
Err(e) => Log::err(format!("Failed to enter play mode: {:?}", e)),
}
} else {
Log::err("Save you scene first!");
}
} else {
Log::err("Cannot enter build mode when there is no scene!");
}
}
fn set_build_mode(&mut self) {
if let Mode::Edit = self.mode {
if let Some(scene) = self.scene.as_ref() {
if scene.path.is_some() {
let mut process = std::process::Command::new("cargo");
process
.stdout(Stdio::piped())
.arg("build")
.arg("--package")
.arg("executor");
if let BuildProfile::Release = self.build_profile {
process.arg("--release");
}
match process.spawn() {
Ok(mut process) => {
self.build_window.listen(
process.stdout.take().unwrap(),
&self.engine.user_interface,
);
self.mode = Mode::Build { process };
self.on_mode_changed();
}
Err(e) => Log::err(format!("Failed to enter build mode: {:?}", e)),
}
} else {
Log::err("Save you scene first!");
}
} else {
Log::err("Cannot enter build mode when there is no scene!");
}
} else {
Log::err("Cannot enter build mode when from non-Edit mode!");
}
}
fn set_editor_mode(&mut self) {
if let Mode::Play { mut process, .. } | Mode::Build { mut process } =
std::mem::replace(&mut self.mode, Mode::Edit)
{
Log::verify(process.kill());
self.on_mode_changed();
}
}
fn on_mode_changed(&mut self) {
let engine = &mut self.engine;
let ui = &engine.user_interface;
self.scene_viewer.on_mode_changed(ui, &self.mode);
self.world_viewer.on_mode_changed(ui, &self.mode);
self.asset_browser.on_mode_changed(ui, &self.mode);
self.command_stack_viewer.on_mode_changed(ui, &self.mode);
self.inspector.on_mode_changed(ui, &self.mode);
self.audio_panel.on_mode_changed(ui, &self.mode);
self.navmesh_panel.on_mode_changed(ui, &self.mode);
self.menu.on_mode_changed(ui, &self.mode);
}
fn sync_to_model(&mut self) {
scope_profile!();
let engine = &mut self.engine;
self.menu
.sync_to_model(self.scene.as_ref(), &mut engine.user_interface);
if let Some(editor_scene) = self.scene.as_mut() {
self.animation_editor.sync_to_model(editor_scene, engine);
self.absm_editor.sync_to_model(editor_scene, engine);
self.scene_settings.sync_to_model(editor_scene, engine);
self.scene_viewer.sync_to_model(editor_scene, engine);
self.inspector.sync_to_model(editor_scene, engine);
self.navmesh_panel.sync_to_model(editor_scene, engine);
self.world_viewer.sync_to_model(editor_scene, engine);
self.material_editor
.sync_to_model(&mut engine.user_interface);
self.audio_panel.sync_to_model(editor_scene, engine);
self.command_stack_viewer.sync_to_model(
&mut self.command_stack,
&SceneContext {
scene: &mut engine.scenes[editor_scene.scene],
message_sender: self.message_sender.clone(),
editor_scene,
resource_manager: engine.resource_manager.clone(),
serialization_context: engine.serialization_context.clone(),
},
&mut engine.user_interface,
)
} else {
self.inspector.clear(&engine.user_interface);
self.world_viewer.clear(&engine.user_interface);
}
}
fn post_update(&mut self) {
if let Some(scene) = self.scene.as_mut() {
self.world_viewer
.post_update(scene, &mut self.engine, &self.settings);
}
}
fn handle_resize(&mut self) {
let engine = &mut self.engine;
if let Some(editor_scene) = self.scene.as_ref() {
let scene = &mut engine.scenes[editor_scene.scene];
if let TextureKind::Rectangle { width, height } =
scene.render_target.clone().unwrap().data_ref().kind()
{
let frame_size = self.scene_viewer.frame_bounds(&engine.user_interface).size;
if width != frame_size.x as u32 || height != frame_size.y as u32 {
scene.render_target = Some(Texture::new_render_target(
frame_size.x as u32,
frame_size.y as u32,
));
self.scene_viewer
.set_render_target(&engine.user_interface, scene.render_target.clone());
}
}
}
}
fn do_scene_command(&mut self, command: SceneCommand) -> bool {
let engine = &mut self.engine;
if let Some(editor_scene) = self.scene.as_mut() {
self.command_stack.do_command(
command.into_inner(),
SceneContext {
scene: &mut engine.scenes[editor_scene.scene],
message_sender: self.message_sender.clone(),
editor_scene,
resource_manager: engine.resource_manager.clone(),
serialization_context: engine.serialization_context.clone(),
},
);
editor_scene.has_unsaved_changes = true;
true
} else {
false
}
}
fn undo_scene_command(&mut self) -> bool {
let engine = &mut self.engine;
if let Some(editor_scene) = self.scene.as_mut() {
self.command_stack.undo(SceneContext {
scene: &mut engine.scenes[editor_scene.scene],
message_sender: self.message_sender.clone(),
editor_scene,
resource_manager: engine.resource_manager.clone(),
serialization_context: engine.serialization_context.clone(),
});
editor_scene.has_unsaved_changes = true;
true
} else {
false
}
}
fn redo_scene_command(&mut self) -> bool {
let engine = &mut self.engine;
if let Some(editor_scene) = self.scene.as_mut() {
self.command_stack.redo(SceneContext {
scene: &mut engine.scenes[editor_scene.scene],
message_sender: self.message_sender.clone(),
editor_scene,
resource_manager: engine.resource_manager.clone(),
serialization_context: engine.serialization_context.clone(),
});
editor_scene.has_unsaved_changes = true;
true
} else {
false
}
}
fn clear_scene_command_stack(&mut self) -> bool {
let engine = &mut self.engine;
if let Some(editor_scene) = self.scene.as_mut() {
self.command_stack.clear(SceneContext {
scene: &mut engine.scenes[editor_scene.scene],
message_sender: self.message_sender.clone(),
editor_scene,
resource_manager: engine.resource_manager.clone(),
serialization_context: engine.serialization_context.clone(),
});
true
} else {
false
}
}
fn save_current_scene(&mut self, path: PathBuf) {
let engine = &mut self.engine;
if let Some(editor_scene) = self.scene.as_mut() {
self.animation_editor
.try_leave_preview_mode(editor_scene, engine);
self.absm_editor
.try_leave_preview_mode(editor_scene, engine);
if !self.settings.recent.scenes.contains(&path) {
self.settings.recent.scenes.push(path.clone());
self.menu
.file_menu
.update_recent_files_list(&mut engine.user_interface, &self.settings);
}
match editor_scene.save(path.clone(), engine) {
Ok(message) => {
self.scene_viewer.set_title(
&engine.user_interface,
format!("Scene Preview - {}", path.display()),
);
Log::info(message);
editor_scene.has_unsaved_changes = false;
}
Err(message) => {
Log::err(message.clone());
engine.user_interface.send_message(MessageBoxMessage::open(
self.validation_message_box,
MessageDirection::ToWidget,
None,
Some(message),
));
}
}
}
}
fn load_scene(&mut self, scene_path: PathBuf) {
let engine = &mut self.engine;
let result = {
block_on(SceneLoader::from_file(
&scene_path,
engine.serialization_context.clone(),
))
};
match result {
Ok(loader) => {
let scene = block_on(loader.finish(engine.resource_manager.clone()));
self.set_scene(scene, Some(scene_path));
}
Err(e) => {
Log::err(e.to_string());
}
}
}
fn exit(&mut self, force: bool) {
let engine = &mut self.engine;
if force {
self.exit = true;
} else if is_scene_needs_to_be_saved(self.scene.as_ref()) {
engine.user_interface.send_message(MessageBoxMessage::open(
self.exit_message_box,
MessageDirection::ToWidget,
None,
None,
));
} else {
self.exit = true;
}
}
fn close_current_scene(&mut self) -> bool {
let engine = &mut self.engine;
if let Some(editor_scene) = self.scene.take() {
engine.scenes.remove(editor_scene.scene);
self.scene_viewer
.set_render_target(&engine.user_interface, None);
self.scene_viewer
.set_title(&engine.user_interface, "Scene Preview".to_string());
true
} else {
false
}
}
fn create_new_scene(&mut self) {
let mut scene = Scene::new();
scene.ambient_lighting_color = Color::opaque(200, 200, 200);
self.set_scene(scene, None);
}
fn configure(&mut self, working_directory: PathBuf) {
assert!(self.scene.is_none());
self.asset_browser.clear_preview(&mut self.engine);
std::env::set_current_dir(working_directory.clone()).unwrap();
self.reload_settings();
let engine = &mut self.engine;
engine
.get_window()
.set_title(&format!("Fyroxed: {}", working_directory.to_string_lossy()));
match FileSystemWatcher::new(&working_directory, Duration::from_secs(1)) {
Ok(watcher) => {
engine.resource_manager.state().set_watcher(Some(watcher));
}
Err(e) => {
Log::err(format!("Unable to create resource watcher. Reason {:?}", e));
}
}
engine.resource_manager.state().destroy_unused_resources();
engine.renderer.flush();
self.asset_browser
.set_working_directory(engine, &working_directory);
self.world_viewer
.on_configure(&engine.user_interface, &self.settings);
Log::info(format!(
"New working directory was successfully set: {:?}",
working_directory
));
}
fn select_object(&mut self, type_id: TypeId, handle: ErasedHandle) {
if let Some(scene) = self.scene.as_ref() {
let new_selection = if type_id == TypeId::of::<Node>() {
if self.engine.scenes[scene.scene]
.graph
.is_valid_handle(handle.into())
{
Some(Selection::Graph(GraphSelection::single_or_empty(
handle.into(),
)))
} else {
None
}
} else {
None
};
if let Some(new_selection) = new_selection {
self.message_sender
.send(Message::DoSceneCommand(SceneCommand::new(
ChangeSelectionCommand::new(new_selection, scene.selection.clone()),
)))
.unwrap()
}
}
}
fn open_material_editor(&mut self, material: SharedMaterial) {
let engine = &mut self.engine;
self.material_editor.set_material(Some(material), engine);
engine.user_interface.send_message(WindowMessage::open(
self.material_editor.window,
MessageDirection::ToWidget,
true,
));
}
fn poll_ui_messages(&mut self) -> usize {
scope_profile!();
let mut processed = 0;
while let Some(mut ui_message) = self.engine.user_interface.poll_message() {
self.handle_ui_message(&mut ui_message);
processed += 1;
}
processed
}
fn on_selection_changed(&mut self, old_selection: Selection) {
if let Some(editor_scene) = self.scene.as_mut() {
let scene = &self.engine.scenes[editor_scene.scene];
let node_overrides = editor_scene.graph_switches.node_overrides.as_mut().unwrap();
if let Selection::Graph(old_graph_selection) = old_selection {
for node_handle in old_graph_selection.nodes {
if scene
.graph
.try_get_of_type::<ParticleSystem>(node_handle)
.is_some()
{
assert!(node_overrides.remove(&node_handle));
}
}
}
if let Selection::Graph(ref new_graph_selection) = editor_scene.selection {
for &node_handle in &new_graph_selection.nodes {
if scene
.graph
.try_get_of_type::<ParticleSystem>(node_handle)
.is_some()
{
assert!(node_overrides.insert(node_handle));
}
}
}
}
}
fn update(&mut self, dt: f32) {
scope_profile!();
match self.mode {
Mode::Play {
ref mut process,
ref active,
} => {
match process.try_wait() {
Ok(status) => {
if let Some(status) = status {
active.store(false, Ordering::SeqCst);
self.mode = Mode::Edit;
self.on_mode_changed();
Log::info(format!("Game was closed: {:?}", status))
}
}
Err(err) => Log::err(format!("Failed to wait for game process: {:?}", err)),
}
}
Mode::Build { ref mut process } => {
self.build_window.update(&self.engine.user_interface);
match process.try_wait() {
Ok(status) => {
if let Some(status) = status {
self.build_window.reset(&self.engine.user_interface);
let err_code = 101;
let code = status.code().unwrap_or(err_code);
if code == err_code {
Log::info("Failed to build the game!");
self.mode = Mode::Edit;
self.on_mode_changed();
} else {
self.set_play_mode();
}
}
}
Err(err) => Log::err(format!("Failed to wait for game process: {:?}", err)),
}
}
_ => {}
}
self.log.update(&mut self.engine);
self.material_editor.update(&mut self.engine);
self.asset_browser.update(&mut self.engine);
if let Some(scene) = self.scene.as_ref() {
self.animation_editor.update(scene, &self.engine);
}
let mut iterations = 1;
while iterations > 0 {
iterations -= 1;
let ui_messages_processed_count = self.poll_ui_messages();
let mut needs_sync = false;
let mut editor_messages_processed_count = 0;
while let Ok(message) = self.message_receiver.try_recv() {
editor_messages_processed_count += 1;
self.path_fixer
.handle_message(&message, &self.engine.user_interface);
self.save_scene_dialog
.handle_message(&message, &self.message_sender);
if let Some(editor_scene) = self.scene.as_ref() {
self.particle_system_control_panel.handle_message(
&message,
editor_scene,
&mut self.engine,
);
self.inspector.handle_message(
&message,
editor_scene,
&mut self.engine,
&self.message_sender,
);
}
if let Some(scene) = self.scene.as_mut() {
self.animation_editor
.handle_message(&message, scene, &mut self.engine);
self.absm_editor
.handle_message(&message, scene, &mut self.engine);
}
self.scene_viewer.handle_message(&message, &mut self.engine);
match message {
Message::DoSceneCommand(command) => {
needs_sync |= self.do_scene_command(command);
}
Message::UndoSceneCommand => {
needs_sync |= self.undo_scene_command();
}
Message::RedoSceneCommand => {
needs_sync |= self.redo_scene_command();
}
Message::ClearSceneCommandStack => {
needs_sync |= self.clear_scene_command_stack();
}
Message::SelectionChanged { old_selection } => {
self.world_viewer.sync_selection = true;
self.on_selection_changed(old_selection);
}
Message::SaveScene(path) => self.save_current_scene(path),
Message::LoadScene(scene_path) => {
self.load_scene(scene_path);
needs_sync = true;
}
Message::SetInteractionMode(mode_kind) => {
self.set_interaction_mode(Some(mode_kind))
}
Message::Exit { force } => self.exit(force),
Message::CloseScene => {
needs_sync |= self.close_current_scene();
}
Message::NewScene => {
self.create_new_scene();
needs_sync = true;
}
Message::Configure { working_directory } => {
self.configure(working_directory);
needs_sync = true;
}
Message::OpenSettings => {
self.menu.file_menu.settings.open(
&mut self.engine.user_interface,
&self.settings,
&self.message_sender,
);
}
Message::OpenMaterialEditor(material) => self.open_material_editor(material),
Message::ShowInAssetBrowser(path) => {
self.asset_browser
.locate_path(&self.engine.user_interface, path);
}
Message::SetWorldViewerFilter(filter) => {
self.world_viewer
.set_filter(filter, &self.engine.user_interface);
}
Message::LocateObject { type_id, handle } => self
.world_viewer
.try_locate_object(type_id, handle, &self.engine),
Message::SelectObject { type_id, handle } => {
self.select_object(type_id, handle);
}
Message::SetEditorCameraProjection(projection) => {
if let Some(editor_scene) = self.scene.as_ref() {
editor_scene.camera_controller.set_projection(
&mut self.engine.scenes[editor_scene.scene].graph,
projection,
);
}
}
Message::SwitchMode => match self.mode {
Mode::Edit => self.set_build_mode(),
_ => self.set_editor_mode(),
},
Message::SwitchToPlayMode => self.set_play_mode(),
Message::SwitchToEditMode => self.set_editor_mode(),
Message::OpenLoadSceneDialog => {
self.menu
.open_load_file_selector(&mut self.engine.user_interface);
}
Message::OpenSaveSceneDialog => {
self.menu
.open_save_file_selector(&mut self.engine.user_interface);
}
Message::OpenSaveSceneConfirmationDialog(action) => {
self.save_scene_dialog
.open(&self.engine.user_interface, action);
}
Message::SetBuildProfile(profile) => {
self.build_profile = profile;
}
Message::SaveSelectionAsPrefab(path) => {
self.try_save_selection_as_prefab(path);
}
Message::SyncNodeHandleName { view, handle } => {
if let Some(editor_scene) = self.scene.as_ref() {
let scene = &self.engine.scenes[editor_scene.scene];
self.engine.user_interface.send_message(
HandlePropertyEditorMessage::name(
view,
MessageDirection::ToWidget,
scene.graph.try_get(handle).map(|n| n.name_owned()),
),
);
}
}
Message::ForceSync => {
needs_sync = true;
}
Message::OpenAnimationEditor => {
self.animation_editor.open(&self.engine.user_interface);
}
Message::OpenAbsmEditor => self.absm_editor.open(&self.engine.user_interface),
}
}
if needs_sync {
self.sync_to_model();
}
if ui_messages_processed_count > 0 || editor_messages_processed_count > 0 {
iterations += 1;
}
}
self.handle_resize();
if let Some(editor_scene) = self.scene.as_mut() {
editor_scene.update(&mut self.engine, dt, &self.settings);
self.absm_editor.update(editor_scene, &mut self.engine);
let scene = &self.engine.scenes[editor_scene.scene];
if let Some(path) = editor_scene.path.as_ref() {
let last_settings = SceneCameraSettings {
position: editor_scene.camera_controller.position(&scene.graph),
yaw: editor_scene.camera_controller.yaw,
pitch: editor_scene.camera_controller.pitch,
};
if let Some(entry) = self.settings.camera.camera_settings.get_mut(path) {
*entry = last_settings;
} else {
self.settings
.camera
.camera_settings
.insert(path.clone(), last_settings);
}
}
if let Some(mode) = self.current_interaction_mode {
self.interaction_modes[mode as usize].update(
editor_scene,
editor_scene.camera_controller.camera,
&mut self.engine,
&self.settings,
);
}
}
}
fn try_save_selection_as_prefab(&self, path: PathBuf) {
if let Some(editor_scene) = self.scene.as_ref() {
let source_scene = &self.engine.scenes[editor_scene.scene];
let mut dest_scene = Scene::new();
if let Selection::Graph(ref graph_selection) = editor_scene.selection {
for root_node in graph_selection.root_nodes(&source_scene.graph) {
source_scene
.graph
.copy_node(root_node, &mut dest_scene.graph, &mut |_, _| true);
}
let mut visitor = Visitor::new();
match dest_scene.save("Scene", &mut visitor) {
Err(e) => Log::err(format!(
"Failed to save selection as prefab! Reason: {:?}",
e
)),
Ok(_) => {
if let Err(e) = visitor.save_binary(&path) {
Log::err(format!(
"Failed to save selection as prefab! Reason: {:?}",
e
));
} else {
Log::info(format!(
"Selection was successfully saved as prefab to {:?}!",
path
))
}
}
}
} else {
Log::warn(
"Unable to selection to prefab, because selection is not scene selection!",
);
}
} else {
Log::warn("Unable to save selection to prefab, because there is no scene loaded!");
}
}
pub fn add_game_plugin<P>(&mut self, plugin: P)
where
P: PluginConstructor + 'static,
{
self.engine.add_plugin_constructor(plugin)
}
pub fn run(mut self, event_loop: EventLoop<()>) -> ! {
event_loop.run(move |event, _, control_flow| match event {
Event::MainEventsCleared => {
update(&mut self, control_flow);
if self.exit {
*control_flow = ControlFlow::Exit;
match self.mode {
Mode::Edit => {}
Mode::Build { ref mut process }
| Mode::Play {
ref mut process, ..
} => {
let _ = process.kill();
}
}
}
}
Event::RedrawRequested(_) => {
let mut camera_state = Vec::new();
if let Some(editor_scene) = self.scene.as_ref() {
let scene = &mut self.engine.scenes[editor_scene.scene];
let has_preview_camera =
scene.graph.is_valid_handle(editor_scene.preview_camera);
for (handle, camera) in scene.graph.pair_iter_mut().filter_map(|(h, n)| {
if has_preview_camera && h != editor_scene.preview_camera
|| !has_preview_camera && h != editor_scene.camera_controller.camera
{
n.cast_mut::<Camera>().map(|c| (h, c))
} else {
None
}
}) {
camera_state.push((handle, camera.is_enabled()));
camera.set_enabled(false);
}
}
self.engine.render().unwrap();
if let Some(scene) = self.scene.as_ref() {
for (handle, enabled) in camera_state {
self.engine.scenes[scene.scene].graph[handle]
.as_camera_mut()
.set_enabled(enabled);
}
}
}
Event::WindowEvent { ref event, .. } => {
match event {
WindowEvent::CloseRequested => {
self.message_sender
.send(Message::Exit { force: false })
.unwrap();
}
WindowEvent::Resized(size) => {
if let Err(e) = self.engine.set_frame_size((*size).into()) {
fyrox::utils::log::Log::writeln(
MessageKind::Error,
format!("Failed to set renderer size! Reason: {:?}", e),
);
}
let logical_size = size.to_logical(self.engine.get_window().scale_factor());
self.engine
.user_interface
.send_message(WidgetMessage::width(
self.root_grid,
MessageDirection::ToWidget,
logical_size.width,
));
self.engine
.user_interface
.send_message(WidgetMessage::height(
self.root_grid,
MessageDirection::ToWidget,
logical_size.height,
));
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
set_ui_scaling(&self.engine.user_interface, *scale_factor as f32);
}
_ => (),
}
if let Some(os_event) = translate_event(event) {
self.engine.user_interface.process_os_event(&os_event);
}
}
Event::LoopDestroyed => {
Log::verify(self.settings.save());
}
_ => *control_flow = ControlFlow::Poll,
});
}
}
fn set_ui_scaling(ui: &UserInterface, scale: f32) {
ui.send_message(WidgetMessage::render_transform(
ui.root(),
MessageDirection::ToWidget,
Matrix3::new_scaling(scale),
));
}
fn update(editor: &mut Editor, control_flow: &mut ControlFlow) {
scope_profile!();
let elapsed = editor.game_loop_data.clock.elapsed().as_secs_f32();
editor.game_loop_data.clock = Instant::now();
editor.game_loop_data.lag += elapsed;
while editor.game_loop_data.lag >= FIXED_TIMESTEP {
editor.game_loop_data.lag -= FIXED_TIMESTEP;
let mut switches = FxHashMap::default();
if let Some(scene) = editor.scene.as_ref() {
switches.insert(scene.scene, scene.graph_switches.clone());
}
editor.engine.pre_update(
FIXED_TIMESTEP,
control_flow,
&mut editor.game_loop_data.lag,
switches,
);
editor.update(FIXED_TIMESTEP);
editor.engine.post_update(FIXED_TIMESTEP);
editor.post_update();
if editor.game_loop_data.lag >= 1.5 * FIXED_TIMESTEP {
break;
}
}
let window = editor.engine.get_window();
window.set_cursor_icon(translate_cursor_icon(editor.engine.user_interface.cursor()));
window.request_redraw();
}