diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs
index 0bd082c48..24fbfc965 100644
--- a/Source/Editor/Editor.cs
+++ b/Source/Editor/Editor.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
@@ -13,6 +14,7 @@ using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Modules;
using FlaxEditor.Modules.SourceCodeEditing;
using FlaxEditor.Options;
+using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.States;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
@@ -1278,6 +1280,69 @@ namespace FlaxEditor
Scene.MarkSceneEdited(scenes);
}
+ ///
+ /// Bakes all environmental probes in the scene.
+ ///
+ public void BakeAllEnvProbes()
+ {
+ Scene.ExecuteOnGraph(node =>
+ {
+ if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive)
+ {
+ ((EnvironmentProbe)envProbeNode.Actor).Bake();
+ node.ParentScene.IsEdited = true;
+ }
+ else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene)
+ {
+ skyLight.Bake();
+ node.ParentScene.IsEdited = true;
+ }
+
+ return node.IsActive;
+ });
+ }
+
+ ///
+ /// Builds CSG for all open scenes.
+ ///
+ public void BuildCSG()
+ {
+ var scenes = Level.Scenes;
+ scenes.ToList().ForEach(x => x.BuildCSG(0));
+ Scene.MarkSceneEdited(scenes);
+ }
+
+ ///
+ /// Builds Nav mesh for all open scenes.
+ ///
+ public void BuildNavMesh()
+ {
+ var scenes = Level.Scenes;
+ scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0));
+ Scene.MarkSceneEdited(scenes);
+ }
+
+ ///
+ /// Builds SDF for all static models in the scene.
+ ///
+ public void BuildAllMeshesSDF()
+ {
+ // TODO: async maybe with progress reporting?
+ Scene.ExecuteOnGraph(node =>
+ {
+ if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
+ {
+ if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null)
+ {
+ Log("Generating SDF for " + staticModel.Model);
+ if (!staticModel.Model.GenerateSDF())
+ staticModel.Model.Save();
+ }
+ }
+ return true;
+ });
+ }
+
#endregion
#region Internal Calls
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs
index f2e5d30e3..25f45a1f8 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using FlaxEditor.Options;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -269,6 +270,24 @@ namespace FlaxEditor.GUI.ContextMenu
return item;
}
+ ///
+ /// Adds the button.
+ ///
+ /// The text.
+ /// The input binding.
+ /// On button clicked event.
+ /// Created context menu item control.
+ public ContextMenuButton AddButton(string text, InputBinding binding, Action clicked)
+ {
+ var item = new ContextMenuButton(this, text, binding.ToString())
+ {
+ Parent = _panel
+ };
+ item.Clicked += clicked;
+ SortButtons();
+ return item;
+ }
+
///
/// Gets the child menu (with that name).
///
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 08ec09de8..1bb40469b 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -42,8 +42,10 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuFileSaveScenes;
private ContextMenuButton _menuFileCloseScenes;
+ private ContextMenuButton _menuFileOpenScriptsProject;
private ContextMenuButton _menuFileGenerateScriptsProjectFiles;
- private ContextMenuButton _menuSaveAll;
+ private ContextMenuButton _menuFileRecompileScripts;
+ private ContextMenuButton _menuFileSaveAll;
private ContextMenuButton _menuEditUndo;
private ContextMenuButton _menuEditRedo;
private ContextMenuButton _menuEditCut;
@@ -62,15 +64,19 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuGamePlayCurrentScenes;
private ContextMenuButton _menuGameStop;
private ContextMenuButton _menuGamePause;
+ private ContextMenuButton _menuGameCookAndRun;
+ private ContextMenuButton _menuGameRunCookedGame;
private ContextMenuButton _menuToolsBuildScenes;
private ContextMenuButton _menuToolsBakeLightmaps;
private ContextMenuButton _menuToolsClearLightmaps;
private ContextMenuButton _menuToolsBakeAllEnvProbes;
private ContextMenuButton _menuToolsBuildCSGMesh;
private ContextMenuButton _menuToolsBuildNavMesh;
- private ContextMenuButton _menuToolsBuildAllMesgesSDF;
+ private ContextMenuButton _menuToolsBuildAllMeshesSDF;
private ContextMenuButton _menuToolsCancelBuilding;
+ private ContextMenuButton _menuToolsProfilerWindow;
private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault;
+ private ContextMenuButton _menuToolsTakeScreenshot;
private ContextMenuChildMenu _menuWindowApplyWindowLayout;
private ToolStripButton _toolStripSaveAll;
@@ -510,13 +516,13 @@ namespace FlaxEditor.Modules
MenuFile = MainMenu.AddButton("File");
var cm = MenuFile.ContextMenu;
cm.VisibleChanged += OnMenuFileShowHide;
- _menuSaveAll = cm.AddButton("Save All", inputOptions.Save.ToString(), Editor.SaveAll);
- _menuFileSaveScenes = cm.AddButton("Save scenes", Editor.Scene.SaveScenes);
- _menuFileCloseScenes = cm.AddButton("Close scenes", Editor.Scene.CloseAllScenes);
+ _menuFileSaveAll = cm.AddButton("Save All", inputOptions.Save, Editor.SaveAll);
+ _menuFileSaveScenes = cm.AddButton("Save scenes", inputOptions.SaveScenes, Editor.Scene.SaveScenes);
+ _menuFileCloseScenes = cm.AddButton("Close scenes", inputOptions.CloseScenes, Editor.Scene.CloseAllScenes);
cm.AddSeparator();
- cm.AddButton("Open scripts project", Editor.CodeEditing.OpenSolution);
- _menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync);
- cm.AddButton("Recompile scripts", ScriptsBuilder.Compile);
+ _menuFileOpenScriptsProject = cm.AddButton("Open scripts project", inputOptions.OpenScriptsProject, Editor.CodeEditing.OpenSolution);
+ _menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync);
+ _menuFileRecompileScripts = cm.AddButton("Recompile scripts", inputOptions.RecompileScripts, ScriptsBuilder.Compile);
cm.AddSeparator();
cm.AddButton("Open project...", OpenProject);
cm.AddSeparator();
@@ -526,27 +532,27 @@ namespace FlaxEditor.Modules
MenuEdit = MainMenu.AddButton("Edit");
cm = MenuEdit.ContextMenu;
cm.VisibleChanged += OnMenuEditShowHide;
- _menuEditUndo = cm.AddButton(string.Empty, inputOptions.Undo.ToString(), Editor.PerformUndo);
- _menuEditRedo = cm.AddButton(string.Empty, inputOptions.Redo.ToString(), Editor.PerformRedo);
+ _menuEditUndo = cm.AddButton(string.Empty, inputOptions.Undo, Editor.PerformUndo);
+ _menuEditRedo = cm.AddButton(string.Empty, inputOptions.Redo, Editor.PerformRedo);
cm.AddSeparator();
- _menuEditCut = cm.AddButton("Cut", inputOptions.Cut.ToString(), Editor.SceneEditing.Cut);
- _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy.ToString(), Editor.SceneEditing.Copy);
- _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste.ToString(), Editor.SceneEditing.Paste);
+ _menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
+ _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy);
+ _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste);
cm.AddSeparator();
- _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete.ToString(), Editor.SceneEditing.Delete);
- _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate.ToString(), Editor.SceneEditing.Duplicate);
+ _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete);
+ _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
cm.AddSeparator();
- _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll.ToString(), Editor.SceneEditing.SelectAllScenes);
- _menuEditFind = cm.AddButton("Find", inputOptions.Search.ToString(), Editor.Windows.SceneWin.Search);
+ _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes);
+ _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search);
// Scene
MenuScene = MainMenu.AddButton("Scene");
cm = MenuScene.ContextMenu;
cm.VisibleChanged += OnMenuSceneShowHide;
- _menuSceneMoveActorToViewport = cm.AddButton("Move actor to viewport", MoveActorToViewport);
- _menuSceneAlignActorWithViewport = cm.AddButton("Align actor with viewport", AlignActorWithViewport);
- _menuSceneAlignViewportWithActor = cm.AddButton("Align viewport with actor", AlignViewportWithActor);
- _menuScenePilotActor = cm.AddButton("Pilot actor", PilotActor);
+ _menuSceneMoveActorToViewport = cm.AddButton("Move actor to viewport", inputOptions.MoveActorToViewport, MoveActorToViewport);
+ _menuSceneAlignActorWithViewport = cm.AddButton("Align actor with viewport", inputOptions.AlignActorWithViewport, AlignActorWithViewport);
+ _menuSceneAlignViewportWithActor = cm.AddButton("Align viewport with actor", inputOptions.AlignViewportWithActor, AlignViewportWithActor);
+ _menuScenePilotActor = cm.AddButton("Pilot actor", inputOptions.PilotActor, PilotActor);
cm.AddSeparator();
_menuSceneCreateTerrain = cm.AddButton("Create terrain", CreateTerrain);
@@ -555,39 +561,41 @@ namespace FlaxEditor.Modules
cm = MenuGame.ContextMenu;
cm.VisibleChanged += OnMenuGameShowHide;
- _menuGamePlayGame = cm.AddButton("Play Game", Editor.Simulation.RequestPlayGameOrStopPlay);
- _menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", Editor.Simulation.RequestPlayScenesOrStopPlay);
- _menuGameStop = cm.AddButton("Stop Game", Editor.Simulation.RequestStopPlay);
- _menuGamePause = cm.AddButton("Pause", inputOptions.Pause.ToString(), Editor.Simulation.RequestPausePlay);
+ _menuGamePlayGame = cm.AddButton("Play Game", inputOptions.Play, Editor.Simulation.RequestPlayGameOrStopPlay);
+ _menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", inputOptions.PlayCurrentScenes, Editor.Simulation.RequestPlayScenesOrStopPlay);
+ _menuGameStop = cm.AddButton("Stop Game", inputOptions.Play, Editor.Simulation.RequestStopPlay);
+ _menuGamePause = cm.AddButton("Pause", inputOptions.Pause, Editor.Simulation.RequestPausePlay);
cm.AddSeparator();
var numberOfClientsMenu = cm.AddChildMenu("Number of game clients");
_numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
cm.AddSeparator();
- cm.AddButton("Cook & Run", Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after.");
- cm.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first.");
+ _menuGameCookAndRun = cm.AddButton("Cook & Run", inputOptions.CookAndRun, Editor.Windows.GameCookerWin.BuildAndRun);
+ _menuGameCookAndRun.LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after.");
+ _menuGameRunCookedGame = cm.AddButton("Run cooked game", inputOptions.RunCookedGame, Editor.Windows.GameCookerWin.RunCooked);
+ _menuGameRunCookedGame.LinkTooltip("Runs the game build from the last cooking output. Use 'Cook & Run' or Game Cooker first.");
// Tools
MenuTools = MainMenu.AddButton("Tools");
cm = MenuTools.ContextMenu;
cm.VisibleChanged += OnMenuToolsShowHide;
- _menuToolsBuildScenes = cm.AddButton("Build scenes data", "Ctrl+F10", Editor.BuildScenesOrCancel);
+ _menuToolsBuildScenes = cm.AddButton("Build scenes data", inputOptions.BuildScenesData, Editor.BuildScenesOrCancel);
cm.AddSeparator();
- _menuToolsBakeLightmaps = cm.AddButton("Bake lightmaps", Editor.BakeLightmapsOrCancel);
- _menuToolsClearLightmaps = cm.AddButton("Clear lightmaps data", Editor.ClearLightmaps);
- _menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", BakeAllEnvProbes);
- _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", BuildCSG);
- _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", BuildNavMesh);
- _menuToolsBuildAllMesgesSDF = cm.AddButton("Build all meshes SDF", BuildAllMeshesSDF);
+ _menuToolsBakeLightmaps = cm.AddButton("Bake lightmaps", inputOptions.BakeLightmaps, Editor.BakeLightmapsOrCancel);
+ _menuToolsClearLightmaps = cm.AddButton("Clear lightmaps data", inputOptions.ClearLightmaps, Editor.ClearLightmaps);
+ _menuToolsBakeAllEnvProbes = cm.AddButton("Bake all env probes", inputOptions.BakeEnvProbes, Editor.BakeAllEnvProbes);
+ _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", inputOptions.BuildCSG, Editor.BuildCSG);
+ _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", inputOptions.BuildNav, Editor.BuildNavMesh);
+ _menuToolsBuildAllMeshesSDF = cm.AddButton("Build all meshes SDF", inputOptions.BuildSDF, Editor.BuildAllMeshesSDF);
cm.AddSeparator();
cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow);
_menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel());
cm.AddSeparator();
- cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow);
+ _menuToolsProfilerWindow = cm.AddButton("Profiler", inputOptions.ProfilerWindow, () => Editor.Windows.ProfilerWin.FocusOrShow());
cm.AddSeparator();
_menuToolsSetTheCurrentSceneViewAsDefault = cm.AddButton("Set current scene view as project default", SetTheCurrentSceneViewAsDefault);
- cm.AddButton("Take screenshot", "F12", Editor.Windows.TakeScreenshot);
+ _menuToolsTakeScreenshot = cm.AddButton("Take screenshot", inputOptions.TakeScreenshot, Editor.Windows.TakeScreenshot);
cm.AddSeparator();
cm.AddButton("Plugins", () => Editor.Windows.PluginsWin.Show());
cm.AddButton("Options", () => Editor.Windows.EditorOptionsWin.Show());
@@ -606,7 +614,7 @@ namespace FlaxEditor.Modules
cm.AddButton("Output Log", Editor.Windows.OutputLogWin.FocusOrShow);
cm.AddButton("Graphics Quality", Editor.Windows.GraphicsQualityWin.FocusOrShow);
cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow);
- cm.AddButton("Profiler", Editor.Windows.ProfilerWin.FocusOrShow);
+ cm.AddButton("Profiler", inputOptions.ProfilerWindow, Editor.Windows.ProfilerWin.FocusOrShow);
cm.AddButton("Content Search", Editor.ContentFinding.ShowSearch);
cm.AddButton("Visual Script Debugger", Editor.Windows.VisualScriptDebuggerWin.FocusOrShow);
cm.AddSeparator();
@@ -633,7 +641,12 @@ namespace FlaxEditor.Modules
{
var inputOptions = options.Input;
- _menuSaveAll.ShortKeys = inputOptions.Save.ToString();
+ _menuFileSaveAll.ShortKeys = inputOptions.Save.ToString();
+ _menuFileSaveScenes.ShortKeys = inputOptions.SaveScenes.ToString();
+ _menuFileCloseScenes.ShortKeys = inputOptions.CloseScenes.ToString();
+ _menuFileOpenScriptsProject.ShortKeys = inputOptions.OpenScriptsProject.ToString();
+ _menuFileGenerateScriptsProjectFiles.ShortKeys = inputOptions.GenerateScriptsProject.ToString();
+ _menuFileRecompileScripts.ShortKeys = inputOptions.RecompileScripts.ToString();
_menuEditUndo.ShortKeys = inputOptions.Undo.ToString();
_menuEditRedo.ShortKeys = inputOptions.Redo.ToString();
_menuEditCut.ShortKeys = inputOptions.Cut.ToString();
@@ -642,8 +655,21 @@ namespace FlaxEditor.Modules
_menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString();
_menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString();
_menuEditFind.ShortKeys = inputOptions.Search.ToString();
- _menuGamePlayCurrentScenes.ShortKeys = inputOptions.Play.ToString();
+ _menuGamePlayGame.ShortKeys = inputOptions.Play.ToString();
+ _menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString();
_menuGamePause.ShortKeys = inputOptions.Pause.ToString();
+ _menuGameStop.ShortKeys = inputOptions.Play.ToString();
+ _menuGameCookAndRun.ShortKeys = inputOptions.CookAndRun.ToString();
+ _menuGameRunCookedGame.ShortKeys = inputOptions.RunCookedGame.ToString();
+ _menuToolsBuildScenes.ShortKeys = inputOptions.BuildScenesData.ToString();
+ _menuToolsBakeLightmaps.ShortKeys = inputOptions.BakeLightmaps.ToString();
+ _menuToolsClearLightmaps.ShortKeys = inputOptions.ClearLightmaps.ToString();
+ _menuToolsBakeAllEnvProbes.ShortKeys = inputOptions.BakeEnvProbes.ToString();
+ _menuToolsBuildCSGMesh.ShortKeys = inputOptions.BuildCSG.ToString();
+ _menuToolsBuildNavMesh.ShortKeys = inputOptions.BuildNav.ToString();
+ _menuToolsBuildAllMeshesSDF.ShortKeys = inputOptions.BuildSDF.ToString();
+ _menuToolsProfilerWindow.ShortKeys = inputOptions.ProfilerWindow.ToString();
+ _menuToolsTakeScreenshot.ShortKeys = inputOptions.TakeScreenshot.ToString();
MainMenuShortcutKeysUpdated?.Invoke();
}
@@ -668,10 +694,10 @@ namespace FlaxEditor.Modules
ToolStrip.AddSeparator();
// Cook scenes
- _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)");
+ _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})");
// Cook and run
- _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Cook & Run - build game for the current platform and run it locally");
+ _toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.Play})");
_toolStripCook.ContextMenu = new ContextMenu();
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
_toolStripCook.ContextMenu.AddSeparator();
@@ -692,7 +718,7 @@ namespace FlaxEditor.Modules
playActionGroup.SelectedChanged = SetPlayAction;
Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; };
- _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game({inputOptions.Pause})");
+ _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})");
_toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
UpdateToolstrip();
@@ -872,7 +898,7 @@ namespace FlaxEditor.Modules
_menuToolsBakeLightmaps.Text = isBakingLightmaps ? "Cancel baking lightmaps" : "Bake lightmaps";
_menuToolsClearLightmaps.Enabled = canEdit;
_menuToolsBakeAllEnvProbes.Enabled = canEdit;
- _menuToolsBuildAllMesgesSDF.Enabled = canEdit && !isBakingLightmaps;
+ _menuToolsBuildAllMeshesSDF.Enabled = canEdit && !isBakingLightmaps;
_menuToolsBuildCSGMesh.Enabled = canEdit;
_menuToolsBuildNavMesh.Enabled = canEdit;
_menuToolsCancelBuilding.Enabled = GameCooker.IsRunning;
@@ -911,7 +937,7 @@ namespace FlaxEditor.Modules
Editor.Windows.LoadLayout((string)button.Tag);
}
- private void AlignViewportWithActor()
+ internal void AlignViewportWithActor()
{
var selection = Editor.SceneEditing;
if (selection.HasSthSelected && selection.Selection[0] is ActorNode node)
@@ -922,7 +948,7 @@ namespace FlaxEditor.Modules
}
}
- private void MoveActorToViewport()
+ internal void MoveActorToViewport()
{
var selection = Editor.SceneEditing;
if (selection.HasSthSelected && selection.Selection[0] is ActorNode node)
@@ -936,7 +962,7 @@ namespace FlaxEditor.Modules
}
}
- private void AlignActorWithViewport()
+ internal void AlignActorWithViewport()
{
var selection = Editor.SceneEditing;
if (selection.HasSthSelected && selection.Selection[0] is ActorNode node)
@@ -972,57 +998,6 @@ namespace FlaxEditor.Modules
new Tools.Terrain.CreateTerrainDialog().Show(Editor.Windows.MainWindow);
}
- private void BakeAllEnvProbes()
- {
- Editor.Scene.ExecuteOnGraph(node =>
- {
- if (node is EnvironmentProbeNode envProbeNode && envProbeNode.IsActive)
- {
- ((EnvironmentProbe)envProbeNode.Actor).Bake();
- node.ParentScene.IsEdited = true;
- }
- else if (node is SkyLightNode skyLightNode && skyLightNode.IsActive && skyLightNode.Actor is SkyLight skyLight && skyLight.Mode == SkyLight.Modes.CaptureScene)
- {
- skyLight.Bake();
- node.ParentScene.IsEdited = true;
- }
-
- return node.IsActive;
- });
- }
-
- private void BuildCSG()
- {
- var scenes = Level.Scenes;
- scenes.ToList().ForEach(x => x.BuildCSG(0));
- Editor.Scene.MarkSceneEdited(scenes);
- }
-
- private void BuildNavMesh()
- {
- var scenes = Level.Scenes;
- scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0));
- Editor.Scene.MarkSceneEdited(scenes);
- }
-
- private void BuildAllMeshesSDF()
- {
- // TODO: async maybe with progress reporting?
- Editor.Scene.ExecuteOnGraph(node =>
- {
- if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
- {
- if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null)
- {
- Editor.Log("Generating SDF for " + staticModel.Model);
- if (!staticModel.Model.GenerateSDF())
- staticModel.Model.Save();
- }
- }
- return true;
- });
- }
-
private void SetTheCurrentSceneViewAsDefault()
{
var projectInfo = Editor.GameProject;
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index 61220f7d5..086772ca2 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -721,7 +721,7 @@ namespace FlaxEditor.Modules
// Create main window
var settings = CreateWindowSettings.Default;
settings.Title = "Flax Editor";
- settings.Size = Platform.DesktopSize;
+ //settings.Size = Platform.DesktopSize;
settings.StartPosition = WindowStartPosition.CenterScreen;
settings.ShowAfterFirstPaint = true;
diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs
index 2e18dab9e..eb61c0f68 100644
--- a/Source/Editor/Options/InputBinding.cs
+++ b/Source/Editor/Options/InputBinding.cs
@@ -134,6 +134,66 @@ namespace FlaxEditor.Options
return false;
}
+ private bool ProcessModifiers(Control control)
+ {
+ return ProcessModifiers(control.Root.GetKey);
+ }
+
+ private bool ProcessModifiers(Window window)
+ {
+ return ProcessModifiers(window.GetKey);
+ }
+
+ private bool ProcessModifiers(Func getKeyFunc)
+ {
+ bool ctrlPressed = getKeyFunc(KeyboardKeys.Control);
+ bool shiftPressed = getKeyFunc(KeyboardKeys.Shift);
+ bool altPressed = getKeyFunc(KeyboardKeys.Alt);
+
+ bool mod1 = false;
+ if (Modifier1 == KeyboardKeys.None)
+ mod1 = true;
+ else if (Modifier1 == KeyboardKeys.Control)
+ {
+ mod1 = ctrlPressed;
+ ctrlPressed = false;
+ }
+ else if (Modifier1 == KeyboardKeys.Shift)
+ {
+ mod1 = shiftPressed;
+ shiftPressed = false;
+ }
+ else if (Modifier1 == KeyboardKeys.Alt)
+ {
+ mod1 = altPressed;
+ altPressed = false;
+ }
+
+ bool mod2 = false;
+ if (Modifier2 == KeyboardKeys.None)
+ mod2 = true;
+ else if (Modifier2 == KeyboardKeys.Control)
+ {
+ mod2 = ctrlPressed;
+ ctrlPressed = false;
+ }
+ else if (Modifier2 == KeyboardKeys.Shift)
+ {
+ mod2 = shiftPressed;
+ shiftPressed = false;
+ }
+ else if (Modifier2 == KeyboardKeys.Alt)
+ {
+ mod2 = altPressed;
+ altPressed = false;
+ }
+
+ // Check if any unhandled modifiers are not pressed
+ if (mod1 && mod2)
+ return !ctrlPressed && !shiftPressed && !altPressed;
+ return false;
+ }
+
///
/// Processes this input binding to check if state matches.
///
@@ -142,19 +202,7 @@ namespace FlaxEditor.Options
public bool Process(Control control)
{
var root = control.Root;
-
- if (root.GetKey(Key))
- {
- if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1))
- {
- if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2))
- {
- return true;
- }
- }
- }
-
- return false;
+ return root.GetKey(Key) && ProcessModifiers(control);
}
///
@@ -165,20 +213,20 @@ namespace FlaxEditor.Options
/// True if input has been processed, otherwise false.
public bool Process(Control control, KeyboardKeys key)
{
- var root = control.Root;
+ if (key != Key)
+ return false;
- if (key == Key)
- {
- if (Modifier1 == KeyboardKeys.None || root.GetKey(Modifier1))
- {
- if (Modifier2 == KeyboardKeys.None || root.GetKey(Modifier2))
- {
- return true;
- }
- }
- }
+ return ProcessModifiers(control);
+ }
- return false;
+ ///
+ /// Processes this input binding to check if state matches.
+ ///
+ /// The input providing window.
+ /// True if input has been processed, otherwise false.
+ public bool Process(Window window)
+ {
+ return window.GetKey(Key) && ProcessModifiers(window);
}
///
diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs
index e0a51622b..90d098bb6 100644
--- a/Source/Editor/Options/InputOptions.cs
+++ b/Source/Editor/Options/InputOptions.cs
@@ -78,6 +78,30 @@ namespace FlaxEditor.Options
#endregion
+ #region File
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("File"), EditorOrder(300)]
+ public InputBinding SaveScenes = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("File"), EditorOrder(310)]
+ public InputBinding CloseScenes = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("File"), EditorOrder(320)]
+ public InputBinding OpenScriptsProject = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("File"), EditorOrder(330)]
+ public InputBinding GenerateScriptsProject = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("File"), EditorOrder(340)]
+ public InputBinding RecompileScripts = new InputBinding(KeyboardKeys.None);
+
+ #endregion
+
#region Scene
[DefaultValue(typeof(InputBinding), "End")]
@@ -85,35 +109,115 @@ namespace FlaxEditor.Options
public InputBinding SnapToGround = new InputBinding(KeyboardKeys.End);
[DefaultValue(typeof(InputBinding), "F5")]
- [EditorDisplay("Scene"), EditorOrder(510)]
+ [EditorDisplay("Scene", "Play/Stop"), EditorOrder(510)]
public InputBinding Play = new InputBinding(KeyboardKeys.F5);
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Scene", "Play Current Scenes/Stop"), EditorOrder(520)]
+ public InputBinding PlayCurrentScenes = new InputBinding(KeyboardKeys.None);
+
[DefaultValue(typeof(InputBinding), "F6")]
- [EditorDisplay("Scene"), EditorOrder(520)]
+ [EditorDisplay("Scene"), EditorOrder(530)]
public InputBinding Pause = new InputBinding(KeyboardKeys.F6);
[DefaultValue(typeof(InputBinding), "F11")]
- [EditorDisplay("Scene"), EditorOrder(530)]
+ [EditorDisplay("Scene"), EditorOrder(540)]
public InputBinding StepFrame = new InputBinding(KeyboardKeys.F11);
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Scene", "Cook & Run"), EditorOrder(550)]
+ public InputBinding CookAndRun = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Scene", "Run cooked game"), EditorOrder(560)]
+ public InputBinding RunCookedGame = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Scene", "Move actor to viewport"), EditorOrder(570)]
+ public InputBinding MoveActorToViewport = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Scene", "Align actor with viewport"), EditorOrder(571)]
+ public InputBinding AlignActorWithViewport = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Scene", "Align viewport with actor"), EditorOrder(572)]
+ public InputBinding AlignViewportWithActor = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Scene"), EditorOrder(573)]
+ public InputBinding PilotActor = new InputBinding(KeyboardKeys.None);
+
+ #endregion
+
+ #region Tools
+
+ [DefaultValue(typeof(InputBinding), "Ctrl+F10")]
+ [EditorDisplay("Tools", "Build scenes data"), EditorOrder(600)]
+ public InputBinding BuildScenesData = new InputBinding(KeyboardKeys.F10, KeyboardKeys.Control);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Tools", "Bake lightmaps"), EditorOrder(601)]
+ public InputBinding BakeLightmaps = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Tools", "Clear lightmaps data"), EditorOrder(602)]
+ public InputBinding ClearLightmaps = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Tools", "Bake all env probes"), EditorOrder(603)]
+ public InputBinding BakeEnvProbes = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Tools", "Build CSG mesh"), EditorOrder(604)]
+ public InputBinding BuildCSG = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Tools", "Build Nav Mesh"), EditorOrder(605)]
+ public InputBinding BuildNav = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Tools", "Build all meshes SDF"), EditorOrder(606)]
+ public InputBinding BuildSDF = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "F12")]
+ [EditorDisplay("Tools", "Take screenshot"), EditorOrder(607)]
+ public InputBinding TakeScreenshot = new InputBinding(KeyboardKeys.F12);
+
+ #endregion
+
+ #region Profiler
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Profiler", "Open Profiler Window"), EditorOrder(630)]
+ public InputBinding ProfilerWindow = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Profiler", "Start/Stop Profiler"), EditorOrder(631)]
+ public InputBinding ProfilerStartStop = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Profiler", "Clear Profiler data"), EditorOrder(632)]
+ public InputBinding ProfilerClear = new InputBinding(KeyboardKeys.None);
+
#endregion
#region Debugger
[DefaultValue(typeof(InputBinding), "F5")]
- [EditorDisplay("Debugger", "Continue"), EditorOrder(610)]
+ [EditorDisplay("Debugger", "Continue"), EditorOrder(810)]
public InputBinding DebuggerContinue = new InputBinding(KeyboardKeys.F5);
[DefaultValue(typeof(InputBinding), "F10")]
- [EditorDisplay("Debugger", "Step Over"), EditorOrder(620)]
+ [EditorDisplay("Debugger", "Step Over"), EditorOrder(820)]
public InputBinding DebuggerStepOver = new InputBinding(KeyboardKeys.F10);
[DefaultValue(typeof(InputBinding), "F11")]
- [EditorDisplay("Debugger", "Step Into"), EditorOrder(630)]
+ [EditorDisplay("Debugger", "Step Into"), EditorOrder(830)]
public InputBinding DebuggerStepInto = new InputBinding(KeyboardKeys.F11);
[DefaultValue(typeof(InputBinding), "Shift+F11")]
- [EditorDisplay("Debugger", "Step Out"), EditorOrder(640)]
+ [EditorDisplay("Debugger", "Step Out"), EditorOrder(840)]
public InputBinding DebuggerStepOut = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift);
#endregion
@@ -132,6 +236,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Gizmo"), EditorOrder(1020)]
public InputBinding ScaleMode = new InputBinding(KeyboardKeys.Alpha3);
+ [DefaultValue(typeof(InputBinding), "Alpha4")]
+ [EditorDisplay("Gizmo"), EditorOrder(1030)]
+ public InputBinding ToggleTransformSpace = new InputBinding(KeyboardKeys.Alpha4);
+
#endregion
#region Viewport
@@ -160,28 +268,40 @@ namespace FlaxEditor.Options
[EditorDisplay("Viewport"), EditorOrder(1550)]
public InputBinding Down = new InputBinding(KeyboardKeys.Q);
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Viewport", "Toggle Camera Rotation"), EditorOrder(1560)]
+ public InputBinding CameraToggleRotation = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Viewport", "Increase Camera Move Speed"), EditorOrder(1570)]
+ public InputBinding CameraIncreaseMoveSpeed = new InputBinding(KeyboardKeys.None);
+
+ [DefaultValue(typeof(InputBinding), "None")]
+ [EditorDisplay("Viewport", "Decrease Camera Move Speed"), EditorOrder(1571)]
+ public InputBinding CameraDecreaseMoveSpeed = new InputBinding(KeyboardKeys.None);
+
[DefaultValue(typeof(InputBinding), "Numpad0")]
- [EditorDisplay("Viewport"), EditorOrder(1600)]
+ [EditorDisplay("Viewport"), EditorOrder(1700)]
public InputBinding ViewpointFront = new InputBinding(KeyboardKeys.Numpad0);
[DefaultValue(typeof(InputBinding), "Numpad5")]
- [EditorDisplay("Viewport"), EditorOrder(1610)]
+ [EditorDisplay("Viewport"), EditorOrder(1710)]
public InputBinding ViewpointBack = new InputBinding(KeyboardKeys.Numpad5);
[DefaultValue(typeof(InputBinding), "Numpad4")]
- [EditorDisplay("Viewport"), EditorOrder(1620)]
+ [EditorDisplay("Viewport"), EditorOrder(1720)]
public InputBinding ViewpointLeft = new InputBinding(KeyboardKeys.Numpad4);
[DefaultValue(typeof(InputBinding), "Numpad6")]
- [EditorDisplay("Viewport"), EditorOrder(1630)]
+ [EditorDisplay("Viewport"), EditorOrder(1730)]
public InputBinding ViewpointRight = new InputBinding(KeyboardKeys.Numpad6);
[DefaultValue(typeof(InputBinding), "Numpad8")]
- [EditorDisplay("Viewport"), EditorOrder(1640)]
+ [EditorDisplay("Viewport"), EditorOrder(1740)]
public InputBinding ViewpointTop = new InputBinding(KeyboardKeys.Numpad8);
[DefaultValue(typeof(InputBinding), "Numpad2")]
- [EditorDisplay("Viewport"), EditorOrder(1650)]
+ [EditorDisplay("Viewport"), EditorOrder(1750)]
public InputBinding ViewpointBottom = new InputBinding(KeyboardKeys.Numpad2);
#endregion
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index e2022d03c..7184391d5 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -19,10 +19,10 @@ using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph;
-using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
+using FlaxEditor.Windows;
namespace FlaxEngine
{
@@ -1263,5 +1263,60 @@ namespace FlaxEditor.Utilities
}
return s;
}
+
+ ///
+ /// Binds global input actions for the window.
+ ///
+ /// The editor window.
+ public static void SetupCommonInputActions(EditorWindow window)
+ {
+ var inputActions = window.InputActions;
+
+ // Setup input actions
+ inputActions.Add(options => options.Save, Editor.Instance.SaveAll);
+ inputActions.Add(options => options.Undo, () =>
+ {
+ Editor.Instance.PerformUndo();
+ window.Focus();
+ });
+ inputActions.Add(options => options.Redo, () =>
+ {
+ Editor.Instance.PerformRedo();
+ window.Focus();
+ });
+ inputActions.Add(options => options.Cut, Editor.Instance.SceneEditing.Cut);
+ inputActions.Add(options => options.Copy, Editor.Instance.SceneEditing.Copy);
+ inputActions.Add(options => options.Paste, Editor.Instance.SceneEditing.Paste);
+ inputActions.Add(options => options.Duplicate, Editor.Instance.SceneEditing.Duplicate);
+ inputActions.Add(options => options.SelectAll, Editor.Instance.SceneEditing.SelectAllScenes);
+ inputActions.Add(options => options.Delete, Editor.Instance.SceneEditing.Delete);
+ inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search());
+ inputActions.Add(options => options.MoveActorToViewport, Editor.Instance.UI.MoveActorToViewport);
+ inputActions.Add(options => options.AlignActorWithViewport, Editor.Instance.UI.AlignActorWithViewport);
+ inputActions.Add(options => options.AlignViewportWithActor, Editor.Instance.UI.AlignViewportWithActor);
+ inputActions.Add(options => options.PilotActor, Editor.Instance.UI.PilotActor);
+ inputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor);
+ inputActions.Add(options => options.PlayCurrentScenes, Editor.Instance.Simulation.RequestPlayScenesOrStopPlay);
+ inputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause);
+ inputActions.Add(options => options.StepFrame, Editor.Instance.Simulation.RequestPlayOneFrame);
+ inputActions.Add(options => options.CookAndRun, () => Editor.Instance.Windows.GameCookerWin.BuildAndRun());
+ inputActions.Add(options => options.RunCookedGame, () => Editor.Instance.Windows.GameCookerWin.RunCooked());
+ inputActions.Add(options => options.BuildScenesData, Editor.Instance.BuildScenesOrCancel);
+ inputActions.Add(options => options.BakeLightmaps, Editor.Instance.BakeLightmapsOrCancel);
+ inputActions.Add(options => options.ClearLightmaps, Editor.Instance.ClearLightmaps);
+ inputActions.Add(options => options.BakeEnvProbes, Editor.Instance.BakeAllEnvProbes);
+ inputActions.Add(options => options.BuildCSG, Editor.Instance.BuildCSG);
+ inputActions.Add(options => options.BuildNav, Editor.Instance.BuildNavMesh);
+ inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF);
+ inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot);
+ inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow());
+ inputActions.Add(options => options.ProfilerStartStop, () => { Editor.Instance.Windows.ProfilerWin.LiveRecording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; Editor.Instance.UI.AddStatusMessage($"Profiling {(Editor.Instance.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); });
+ inputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); });
+ inputActions.Add(options => options.SaveScenes, () => Editor.Instance.Scene.SaveScenes());
+ inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes());
+ inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution());
+ inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync());
+ inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile);
+ }
}
}
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index ad7e06ba5..34dbe561c 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -137,7 +137,7 @@ namespace FlaxEditor.Viewport
// Input
- private bool _isControllingMouse, _isViewportControllingMouse;
+ private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
private int _deltaFilteringStep;
private Float2 _startPos;
private Float2 _mouseDeltaLast;
@@ -685,6 +685,9 @@ namespace FlaxEditor.Viewport
InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation)));
InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation)));
InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation)));
+ InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown);
+ InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1));
+ InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1));
// Link for task event
task.Begin += OnRenderBegin;
@@ -722,6 +725,30 @@ namespace FlaxEditor.Viewport
}
}
+ ///
+ /// Increases or decreases the camera movement speed.
+ ///
+ /// The stepping direction for speed adjustment.
+ protected void AdjustCameraMoveSpeed(int step)
+ {
+ int camValueIndex = -1;
+ for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
+ {
+ if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed))
+ {
+ camValueIndex = i;
+ break;
+ }
+ }
+ if (camValueIndex == -1)
+ return;
+
+ if (step > 0)
+ MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)];
+ else if (step < 0)
+ MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)];
+ }
+
private void OnEditorOptionsChanged(EditorOptions options)
{
_mouseSensitivity = options.Viewport.MouseSensitivity;
@@ -1048,6 +1075,15 @@ namespace FlaxEditor.Viewport
// Track controlling mouse state change
bool wasControllingMouse = _prevInput.IsControllingMouse;
_isControllingMouse = _input.IsControllingMouse;
+
+ // Simulate holding mouse right down for trackpad users
+ if ((_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) || win.GetKeyDown(KeyboardKeys.Escape))
+ _isVirtualMouseRightDown = false; // Cancel when mouse right or escape is pressed
+ if (_wasVirtualMouseRightDown)
+ wasControllingMouse = true;
+ if (_isVirtualMouseRightDown)
+ _isControllingMouse = _isVirtualMouseRightDown;
+
if (wasControllingMouse != _isControllingMouse)
{
if (_isControllingMouse)
@@ -1061,16 +1097,18 @@ namespace FlaxEditor.Viewport
OnLeftMouseButtonDown();
else if (_prevInput.IsMouseLeftDown && !_input.IsMouseLeftDown)
OnLeftMouseButtonUp();
- //
- if (!_prevInput.IsMouseRightDown && _input.IsMouseRightDown)
+
+ if ((!_prevInput.IsMouseRightDown && _input.IsMouseRightDown) || (!_wasVirtualMouseRightDown && _isVirtualMouseRightDown))
OnRightMouseButtonDown();
- else if (_prevInput.IsMouseRightDown && !_input.IsMouseRightDown)
+ else if ((_prevInput.IsMouseRightDown && !_input.IsMouseRightDown) || (_wasVirtualMouseRightDown && !_isVirtualMouseRightDown))
OnRightMouseButtonUp();
- //
+
if (!_prevInput.IsMouseMiddleDown && _input.IsMouseMiddleDown)
OnMiddleMouseButtonDown();
else if (_prevInput.IsMouseMiddleDown && !_input.IsMouseMiddleDown)
OnMiddleMouseButtonUp();
+
+ _wasVirtualMouseRightDown = _isVirtualMouseRightDown;
}
// Get clamped delta time (more stable during lags)
@@ -1088,7 +1126,7 @@ namespace FlaxEditor.Viewport
bool isAltDown = _input.IsAltDown;
bool lbDown = _input.IsMouseLeftDown;
bool mbDown = _input.IsMouseMiddleDown;
- bool rbDown = _input.IsMouseRightDown;
+ bool rbDown = _input.IsMouseRightDown || _isVirtualMouseRightDown;
bool wheelInUse = Math.Abs(_input.MouseWheelDelta) > Mathf.Epsilon;
_input.IsPanning = !isAltDown && mbDown && !rbDown;
@@ -1098,32 +1136,20 @@ namespace FlaxEditor.Viewport
_input.IsOrbiting = isAltDown && lbDown && !mbDown && !rbDown;
// Control move speed with RMB+Wheel
- rmbWheel = useMovementSpeed && _input.IsMouseRightDown && wheelInUse;
+ rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse;
if (rmbWheel)
{
- float step = 4.0f;
+ const float step = 4.0f;
_wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
- int camValueIndex = -1;
- for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
+ if (_wheelMovementChangeDeltaSum >= step)
{
- if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed))
- {
- camValueIndex = i;
- break;
- }
+ _wheelMovementChangeDeltaSum -= step;
+ AdjustCameraMoveSpeed(1);
}
- if (camValueIndex != -1)
+ else if (_wheelMovementChangeDeltaSum <= -step)
{
- if (_wheelMovementChangeDeltaSum >= step)
- {
- _wheelMovementChangeDeltaSum -= step;
- MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)];
- }
- else if (_wheelMovementChangeDeltaSum <= -step)
- {
- _wheelMovementChangeDeltaSum += step;
- MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)];
- }
+ _wheelMovementChangeDeltaSum += step;
+ AdjustCameraMoveSpeed(-1);
}
}
}
@@ -1165,7 +1191,7 @@ namespace FlaxEditor.Viewport
// Calculate smooth mouse delta not dependant on viewport size
var offset = _viewMousePos - _startPos;
- if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel)
+ if (_input.IsZooming && !_input.IsMouseRightDown && !_input.IsMouseLeftDown && !_input.IsMouseMiddleDown && !_isOrtho && !rmbWheel && !_isVirtualMouseRightDown)
{
offset = Float2.Zero;
}
@@ -1213,7 +1239,7 @@ namespace FlaxEditor.Viewport
UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse);
// Move mouse back to the root position
- if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown))
+ if (centerMouse && (_input.IsMouseRightDown || _input.IsMouseLeftDown || _input.IsMouseMiddleDown || _isVirtualMouseRightDown))
{
var center = PointToWindow(_startPos);
win.MousePosition = center;
@@ -1229,7 +1255,7 @@ namespace FlaxEditor.Viewport
}
else
{
- if (_input.IsMouseLeftDown || _input.IsMouseRightDown)
+ if (_input.IsMouseLeftDown || _input.IsMouseRightDown || _isVirtualMouseRightDown)
{
// Calculate smooth mouse delta not dependant on viewport size
var offset = _viewMousePos - _startPos;
@@ -1359,6 +1385,7 @@ namespace FlaxEditor.Viewport
{
OnControlMouseEnd(RootWindow.Window);
_isControllingMouse = false;
+ _isVirtualMouseRightDown = false;
}
}
diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
index 979702514..e85b6c2b3 100644
--- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
@@ -220,6 +220,7 @@ namespace FlaxEditor.Viewport
{
_editor = editor;
_dragAssets = new DragAssets(ValidateDragItem);
+ var inputOptions = editor.Options.Options.Input;
// Prepare rendering task
Task.ActorsSource = ActorsSources.Scenes;
@@ -276,7 +277,7 @@ namespace FlaxEditor.Viewport
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true)
{
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
- TooltipText = "Gizmo transform space (world or local)",
+ TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
@@ -373,7 +374,7 @@ namespace FlaxEditor.Viewport
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true)
{
Tag = TransformGizmoBase.Mode.Translate,
- TooltipText = "Translate gizmo mode",
+ TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
Checked = true,
Parent = gizmoMode
};
@@ -381,14 +382,14 @@ namespace FlaxEditor.Viewport
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true)
{
Tag = TransformGizmoBase.Mode.Rotate,
- TooltipText = "Rotate gizmo mode",
+ TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
Parent = gizmoMode
};
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
_gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true)
{
Tag = TransformGizmoBase.Mode.Scale,
- TooltipText = "Scale gizmo mode",
+ TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
Parent = gizmoMode
};
_gizmoModeScale.Toggled += OnGizmoModeToggle;
@@ -429,6 +430,7 @@ namespace FlaxEditor.Viewport
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
+ InputActions.Add(options => options.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; });
InputActions.Add(options => options.LockFocusSelection, LockFocusSelection);
InputActions.Add(options => options.FocusSelection, FocusSelection);
InputActions.Add(options => options.RotateSelection, RotateSelection);
diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs
index 99d7cb8e6..1dc459135 100644
--- a/Source/Editor/Viewport/PrefabWindowViewport.cs
+++ b/Source/Editor/Viewport/PrefabWindowViewport.cs
@@ -85,6 +85,7 @@ namespace FlaxEditor.Viewport
ShowDebugDraw = true;
ShowEditorPrimitives = true;
Gizmos = new GizmosCollection(this);
+ var inputOptions = window.Editor.Options.Options.Input;
// Prepare rendering task
Task.ActorsSource = ActorsSources.CustomActors;
@@ -114,7 +115,7 @@ namespace FlaxEditor.Viewport
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true)
{
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World,
- TooltipText = "Gizmo transform space (world or local)",
+ TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})",
Parent = transformSpaceWidget
};
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
@@ -206,7 +207,7 @@ namespace FlaxEditor.Viewport
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true)
{
Tag = TransformGizmoBase.Mode.Translate,
- TooltipText = "Translate gizmo mode",
+ TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})",
Checked = true,
Parent = gizmoMode
};
@@ -214,14 +215,14 @@ namespace FlaxEditor.Viewport
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true)
{
Tag = TransformGizmoBase.Mode.Rotate,
- TooltipText = "Rotate gizmo mode",
+ TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})",
Parent = gizmoMode
};
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
_gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true)
{
Tag = TransformGizmoBase.Mode.Scale,
- TooltipText = "Scale gizmo mode",
+ TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})",
Parent = gizmoMode
};
_gizmoModeScale.Toggled += OnGizmoModeToggle;
@@ -234,6 +235,7 @@ namespace FlaxEditor.Viewport
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
+ InputActions.Add(options => options.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; });
InputActions.Add(options => options.FocusSelection, ShowSelectedActors);
SetUpdate(ref _update, OnUpdate);
diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs
index 4192f21b0..af2eee09f 100644
--- a/Source/Editor/Windows/ContentWindow.cs
+++ b/Source/Editor/Windows/ContentWindow.cs
@@ -142,6 +142,8 @@ namespace FlaxEditor.Windows
Title = "Content";
Icon = editor.Icons.Folder32;
+ FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
+
// Content database events
editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true;
editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved;
diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs
index 24e38d9af..ab0c07ec5 100644
--- a/Source/Editor/Windows/DebugLogWindow.cs
+++ b/Source/Editor/Windows/DebugLogWindow.cs
@@ -317,6 +317,7 @@ namespace FlaxEditor.Windows
Title = "Debug Log";
Icon = IconInfo;
OnEditorOptionsChanged(Editor.Options.Options);
+ FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
// Toolstrip
var toolstrip = new ToolStrip(22.0f)
diff --git a/Source/Editor/Windows/EditorOptionsWindow.cs b/Source/Editor/Windows/EditorOptionsWindow.cs
index 2942f98c0..d53d4f632 100644
--- a/Source/Editor/Windows/EditorOptionsWindow.cs
+++ b/Source/Editor/Windows/EditorOptionsWindow.cs
@@ -216,5 +216,36 @@ namespace FlaxEditor.Windows
base.OnDestroy();
}
+
+ ///
+ protected override bool OnClosing(ClosingReason reason)
+ {
+ // Block closing only on user events
+ if (reason == ClosingReason.User)
+ {
+ // Check if asset has been edited and not saved (and still has linked item)
+ if (_isDataDirty && _options != null)
+ {
+ // Ask user for further action
+ var result = MessageBox.Show(
+ "Editor options have been edited. Save before closing?",
+ "Save before closing?",
+ MessageBoxButtons.YesNoCancel
+ );
+ if (result == DialogResult.OK || result == DialogResult.Yes)
+ {
+ // Save and close
+ SaveData();
+ }
+ else if (result == DialogResult.Cancel || result == DialogResult.Abort)
+ {
+ // Cancel closing
+ return true;
+ }
+ }
+ }
+
+ return base.OnClosing(reason);
+ }
}
}
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index 513542fdc..bbdf763fb 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -271,6 +271,8 @@ namespace FlaxEditor.Windows
Title = "Game";
AutoFocus = true;
+ FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
+
var task = MainRenderTask.Instance;
// Setup viewport
@@ -302,10 +304,6 @@ namespace FlaxEditor.Windows
// Link editor options
Editor.Options.OptionsChanged += OnOptionsChanged;
OnOptionsChanged(Editor.Options.Options);
-
- InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor);
- InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause);
- InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame);
}
private void ChangeViewportRatio(ViewportScaleOptions v)
diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs
index 0b2249a33..4ea31b35e 100644
--- a/Source/Editor/Windows/OutputLogWindow.cs
+++ b/Source/Editor/Windows/OutputLogWindow.cs
@@ -150,6 +150,7 @@ namespace FlaxEditor.Windows
{
Title = "Output Log";
ClipChildren = false;
+ FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
// Setup UI
_viewDropdown = new Button(2, 2, 40.0f, TextBoxBase.DefaultHeight)
diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs
index f68021358..f97e943ad 100644
--- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs
+++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs
@@ -116,6 +116,10 @@ namespace FlaxEditor.Windows.Profiler
Parent = this
};
_tabs.SelectedTabChanged += OnSelectedTabChanged;
+
+ FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
+ InputActions.Bindings.RemoveAll(x => x.Callback == this.FocusOrShow);
+ InputActions.Add(options => options.ProfilerWindow, Hide);
}
private void OnLiveRecordingChanged()
diff --git a/Source/Editor/Windows/SceneEditorWindow.cs b/Source/Editor/Windows/SceneEditorWindow.cs
index 72ff3be33..04cce47e8 100644
--- a/Source/Editor/Windows/SceneEditorWindow.cs
+++ b/Source/Editor/Windows/SceneEditorWindow.cs
@@ -19,28 +19,7 @@ namespace FlaxEditor.Windows
protected SceneEditorWindow(Editor editor, bool hideOnClose, ScrollBars scrollBars)
: base(editor, hideOnClose, scrollBars)
{
- // Setup input actions
- InputActions.Add(options => options.Save, Editor.SaveAll);
- InputActions.Add(options => options.Undo, () =>
- {
- Editor.PerformUndo();
- Focus();
- });
- InputActions.Add(options => options.Redo, () =>
- {
- Editor.PerformRedo();
- Focus();
- });
- InputActions.Add(options => options.Cut, Editor.SceneEditing.Cut);
- InputActions.Add(options => options.Copy, Editor.SceneEditing.Copy);
- InputActions.Add(options => options.Paste, Editor.SceneEditing.Paste);
- InputActions.Add(options => options.Duplicate, Editor.SceneEditing.Duplicate);
- InputActions.Add(options => options.SelectAll, Editor.SceneEditing.SelectAllScenes);
- InputActions.Add(options => options.Delete, Editor.SceneEditing.Delete);
- InputActions.Add(options => options.Search, () => Editor.Windows.SceneWin.Search());
- InputActions.Add(options => options.Play, Editor.Simulation.DelegatePlayOrStopPlayInEditor);
- InputActions.Add(options => options.Pause, Editor.Simulation.RequestResumeOrPause);
- InputActions.Add(options => options.StepFrame, Editor.Simulation.RequestPlayOneFrame);
+ FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
}
}
}
diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
index e48e59f80..63d110368 100644
--- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
@@ -26,6 +26,7 @@ namespace FlaxEditor.Windows
bool hasSthSelected = Editor.SceneEditing.HasSthSelected;
bool isSingleActorSelected = Editor.SceneEditing.SelectionCount == 1 && Editor.SceneEditing.Selection[0] is ActorNode;
bool canEditScene = Editor.StateMachine.CurrentState.CanEditScene && Level.IsAnySceneLoaded;
+ var inputOptions = Editor.Options.Options.Input;
// Create popup
@@ -44,17 +45,17 @@ namespace FlaxEditor.Windows
if (hasSthSelected)
{
- contextMenu.AddButton(Editor.Windows.EditWin.IsPilotActorActive ? "Stop piloting actor" : "Pilot actor", Editor.UI.PilotActor);
+ contextMenu.AddButton(Editor.Windows.EditWin.IsPilotActorActive ? "Stop piloting actor" : "Pilot actor", inputOptions.PilotActor, Editor.UI.PilotActor);
}
contextMenu.AddSeparator();
// Basic editing options
- b = contextMenu.AddButton("Rename", Rename);
+ b = contextMenu.AddButton("Rename", inputOptions.Rename, Rename);
b.Enabled = isSingleActorSelected;
- b = contextMenu.AddButton("Duplicate", Editor.SceneEditing.Duplicate);
+ b = contextMenu.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
b.Enabled = hasSthSelected;
if (isSingleActorSelected)
@@ -116,17 +117,17 @@ namespace FlaxEditor.Windows
}
}
}
- b = contextMenu.AddButton("Delete", Editor.SceneEditing.Delete);
+ b = contextMenu.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete);
b.Enabled = hasSthSelected;
contextMenu.AddSeparator();
- b = contextMenu.AddButton("Copy", Editor.SceneEditing.Copy);
+ b = contextMenu.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy);
b.Enabled = hasSthSelected;
- contextMenu.AddButton("Paste", Editor.SceneEditing.Paste);
+ contextMenu.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste);
- b = contextMenu.AddButton("Cut", Editor.SceneEditing.Cut);
+ b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut);
b.Enabled = canEditScene;
// Prefab options
diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs
index 26aa7a2bf..e2b04669b 100644
--- a/Source/Editor/Windows/ToolboxWindow.cs
+++ b/Source/Editor/Windows/ToolboxWindow.cs
@@ -353,6 +353,8 @@ namespace FlaxEditor.Windows
: base(editor, true, ScrollBars.None)
{
Title = "Toolbox";
+
+ FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
}
///
diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs
index 381e6dac0..36d7f2e03 100644
--- a/Source/Engine/Engine/NativeInterop.Invoker.cs
+++ b/Source/Engine/Engine/NativeInterop.Invoker.cs
@@ -84,7 +84,7 @@ namespace FlaxEngine.Interop
internal static IntPtr MarshalReturnValueType(ref Type returnValue)
{
- return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)) : IntPtr.Zero;
+ return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(returnValue)) : IntPtr.Zero;
}
internal static IntPtr MarshalReturnValueArray(ref TRet returnValue)
@@ -162,8 +162,8 @@ namespace FlaxEngine.Interop
return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject);
if (returnType == typeof(bool))
return (bool)returnObject ? boolTruePtr : boolFalsePtr;
- if (returnType == typeof(Type))
- return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnObject)));
+ if (returnType == typeof(Type) || returnType == typeof(TypeHolder))
+ return ManagedHandle.ToIntPtr(GetTypeManagedHandle(Unsafe.As(returnObject)));
if (returnType.IsArray && ArrayFactory.GetMarshalledType(returnType.GetElementType()) == returnType.GetElementType())
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnObject)), GCHandleType.Weak);
if (returnType.IsArray)
@@ -186,8 +186,8 @@ namespace FlaxEngine.Interop
return (IntPtr)(object)returnObject;
if (returnType == typeof(ManagedHandle))
return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject);
- if (returnType == typeof(Type))
- return returnObject != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnObject))) : IntPtr.Zero;
+ if (returnType == typeof(Type) || returnType == typeof(TypeHolder))
+ return returnObject != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(Unsafe.As(returnObject))) : IntPtr.Zero;
if (returnType.IsArray)
{
var elementType = returnType.GetElementType();
diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs
index a74299670..58aa95a2f 100644
--- a/Source/Engine/Engine/NativeInterop.Marshallers.cs
+++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs
@@ -119,13 +119,13 @@ namespace FlaxEngine.Interop
[CustomMarshaller(typeof(Type), MarshalMode.Default, typeof(SystemTypeMarshaller))]
public static class SystemTypeMarshaller
{
- public static Type ConvertToManaged(IntPtr unmanaged) => Unsafe.As(ManagedHandleMarshaller.ConvertToManaged(unmanaged));
+ public static Type ConvertToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As(ManagedHandleMarshaller.ConvertToManaged(unmanaged)).type : null;
public static IntPtr ConvertToUnmanaged(Type managed)
{
if (managed == null)
return IntPtr.Zero;
- ManagedHandle handle = NativeInterop.GetTypeGCHandle(managed);
+ ManagedHandle handle = NativeInterop.GetTypeManagedHandle(managed);
return ManagedHandle.ToIntPtr(handle);
}
diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
index cfa71f97f..a2647dd10 100644
--- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs
+++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
@@ -19,6 +19,7 @@ namespace FlaxEngine.Interop
internal struct NativeClassDefinitions
{
internal ManagedHandle typeHandle;
+ internal IntPtr nativePointer;
internal IntPtr name;
internal IntPtr fullname;
internal IntPtr @namespace;
@@ -40,6 +41,7 @@ namespace FlaxEngine.Interop
internal IntPtr name;
internal ManagedHandle fieldHandle;
internal ManagedHandle fieldTypeHandle;
+ internal int fieldOffset;
internal uint fieldAttributes;
}
@@ -139,6 +141,9 @@ namespace FlaxEngine.Interop
unsafe partial class NativeInterop
{
+ [LibraryImport("FlaxEngine", EntryPoint = "NativeInterop_CreateClass", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
+ internal static partial void NativeInterop_CreateClass(ref NativeClassDefinitions managedClass, ManagedHandle assemblyHandle);
+
internal enum MTypes : uint
{
End = 0x00,
@@ -205,46 +210,8 @@ namespace FlaxEngine.Interop
NativeMemory.AlignedFree(ptr);
}
- [UnmanagedCallersOnly]
- internal static void GetManagedClasses(ManagedHandle assemblyHandle, NativeClassDefinitions** managedClasses, int* managedClassCount)
+ private static Assembly GetOwningAssembly(Type type)
{
- Assembly assembly = Unsafe.As(assemblyHandle.Target);
- var assemblyTypes = GetAssemblyTypes(assembly);
-
- NativeClassDefinitions* arr = (NativeClassDefinitions*)NativeAlloc(assemblyTypes.Length, Unsafe.SizeOf());
-
- for (int i = 0; i < assemblyTypes.Length; i++)
- {
- var type = assemblyTypes[i];
- IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i);
- var managedClass = new NativeClassDefinitions
- {
- typeHandle = GetTypeGCHandle(type),
- name = NativeAllocStringAnsi(type.Name),
- fullname = NativeAllocStringAnsi(type.GetTypeName()),
- @namespace = NativeAllocStringAnsi(type.Namespace ?? ""),
- typeAttributes = (uint)type.Attributes,
- };
- Unsafe.Write(ptr.ToPointer(), managedClass);
- }
-
- *managedClasses = arr;
- *managedClassCount = assemblyTypes.Length;
- }
-
- [UnmanagedCallersOnly]
- internal static void GetManagedClassFromType(ManagedHandle typeHandle, NativeClassDefinitions* managedClass, ManagedHandle* assemblyHandle)
- {
- Type type = Unsafe.As(typeHandle.Target);
- *managedClass = new NativeClassDefinitions
- {
- typeHandle = GetTypeGCHandle(type),
- name = NativeAllocStringAnsi(type.Name),
- fullname = NativeAllocStringAnsi(type.GetTypeName()),
- @namespace = NativeAllocStringAnsi(type.Namespace ?? ""),
- typeAttributes = (uint)type.Attributes,
- };
-
Assembly assembly = null;
if (type.IsGenericType && !type.Assembly.IsCollectible)
{
@@ -261,14 +228,87 @@ namespace FlaxEngine.Interop
}
if (assembly == null)
assembly = type.Assembly;
+ return assembly;
+ }
- *assemblyHandle = GetAssemblyHandle(assembly);
+ private static NativeClassDefinitions CreateNativeClassDefinitions(Type type, out ManagedHandle assemblyHandle)
+ {
+ assemblyHandle = GetAssemblyHandle(GetOwningAssembly(type));
+ return CreateNativeClassDefinitions(type);
+ }
+
+ private static NativeClassDefinitions CreateNativeClassDefinitions(Type type)
+ {
+ return new NativeClassDefinitions()
+ {
+ typeHandle = RegisterType(type).handle,
+ name = NativeAllocStringAnsi(type.Name),
+ fullname = NativeAllocStringAnsi(type.GetTypeName()),
+ @namespace = NativeAllocStringAnsi(type.Namespace ?? ""),
+ typeAttributes = (uint)type.Attributes,
+ };
+ }
+
+ private static NativeClassDefinitions CreateNativeClassDefinitions(Type type, ManagedHandle typeHandle, out ManagedHandle assemblyHandle)
+ {
+ assemblyHandle = GetAssemblyHandle(GetOwningAssembly(type));
+ return new NativeClassDefinitions()
+ {
+ typeHandle = typeHandle,
+ name = NativeAllocStringAnsi(type.Name),
+ fullname = NativeAllocStringAnsi(type.GetTypeName()),
+ @namespace = NativeAllocStringAnsi(type.Namespace ?? ""),
+ typeAttributes = (uint)type.Attributes,
+ };
+ }
+
+ [UnmanagedCallersOnly]
+ internal static void GetManagedClasses(ManagedHandle assemblyHandle, NativeClassDefinitions** managedClasses, int* managedClassCount)
+ {
+ Assembly assembly = Unsafe.As(assemblyHandle.Target);
+ Type[] assemblyTypes = GetAssemblyTypes(assembly);
+
+ *managedClasses = (NativeClassDefinitions*)NativeAlloc(assemblyTypes.Length, Unsafe.SizeOf());
+ *managedClassCount = assemblyTypes.Length;
+ Span span = new Span(*managedClasses, assemblyTypes.Length);
+ for (int i = 0; i < assemblyTypes.Length; i++)
+ {
+ Type type = assemblyTypes[i];
+ ref var managedClass = ref span[i];
+ managedClass = CreateNativeClassDefinitions(type);
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ internal static void RegisterManagedClassNativePointers(NativeClassDefinitions** managedClasses, int managedClassCount)
+ {
+ Span span = new Span(Unsafe.Read(managedClasses).ToPointer(), managedClassCount);
+ foreach (ref NativeClassDefinitions managedClass in span)
+ {
+ TypeHolder typeHolder = Unsafe.As(managedClass.typeHandle.Target);
+ typeHolder.managedClassPointer = managedClass.nativePointer;
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ internal static void GetManagedClassFromType(ManagedHandle typeHandle, NativeClassDefinitions* managedClass, ManagedHandle* assemblyHandle)
+ {
+ Type type = Unsafe.As(typeHandle.Target);
+ *managedClass = CreateNativeClassDefinitions(type, out ManagedHandle handle);
+ *assemblyHandle = handle;
+ }
+
+ private static void RegisterNativeClassFromType(TypeHolder typeHolder, ManagedHandle typeHandle)
+ {
+ NativeClassDefinitions managedClass = CreateNativeClassDefinitions(typeHolder.type, typeHandle, out ManagedHandle assemblyHandle);
+ NativeInterop_CreateClass(ref managedClass, assemblyHandle);
+ typeHolder.managedClassPointer = managedClass.nativePointer;
}
[UnmanagedCallersOnly]
internal static void GetClassMethods(ManagedHandle typeHandle, NativeMethodDefinitions** classMethods, int* classMethodsCount)
{
- Type type = Unsafe.As(typeHandle.Target);
+ Type type = Unsafe.As(typeHandle.Target);
var methods = new List();
var staticMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
@@ -296,7 +336,7 @@ namespace FlaxEngine.Interop
[UnmanagedCallersOnly]
internal static void GetClassFields(ManagedHandle typeHandle, NativeFieldDefinitions** classFields, int* classFieldsCount)
{
- Type type = Unsafe.As(typeHandle.Target);
+ Type type = Unsafe.As(typeHandle.Target);
var fields = type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
NativeFieldDefinitions* arr = (NativeFieldDefinitions*)NativeAlloc(fields.Length, Unsafe.SizeOf());
@@ -318,7 +358,8 @@ namespace FlaxEngine.Interop
{
name = NativeAllocStringAnsi(fieldHolder.field.Name),
fieldHandle = fieldHandle,
- fieldTypeHandle = GetTypeGCHandle(fieldHolder.field.FieldType),
+ fieldTypeHandle = GetTypeManagedHandle(fieldHolder.field.FieldType),
+ fieldOffset = fieldHolder.fieldOffset,
fieldAttributes = (uint)fieldHolder.field.Attributes,
};
Unsafe.Write(IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i).ToPointer(), classField);
@@ -330,7 +371,7 @@ namespace FlaxEngine.Interop
[UnmanagedCallersOnly]
internal static void GetClassProperties(ManagedHandle typeHandle, NativePropertyDefinitions** classProperties, int* classPropertiesCount)
{
- Type type = Unsafe.As(typeHandle.Target);
+ Type type = Unsafe.As(typeHandle.Target);
var properties = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var arr = (NativePropertyDefinitions*)NativeAlloc(properties.Length, Unsafe.SizeOf());
@@ -364,7 +405,7 @@ namespace FlaxEngine.Interop
[UnmanagedCallersOnly]
internal static void GetClassAttributes(ManagedHandle typeHandle, ManagedHandle** classAttributes, int* classAttributesCount)
{
- Type type = Unsafe.As(typeHandle.Target);
+ Type type = Unsafe.As(typeHandle.Target);
object[] attributeValues = type.GetCustomAttributes(false);
ManagedHandle* arr = (ManagedHandle*)NativeAlloc(attributeValues.Length, Unsafe.SizeOf());
@@ -384,13 +425,13 @@ namespace FlaxEngine.Interop
[UnmanagedCallersOnly]
internal static ManagedHandle GetCustomAttribute(ManagedHandle typeHandle, ManagedHandle attributeHandle)
{
- Type type = Unsafe.As(typeHandle.Target);
+ Type type = Unsafe.As(typeHandle.Target);
var attributes = type.GetCustomAttributes(false);
object attrib;
if (attributeHandle.IsAllocated)
{
// Check for certain attribute type
- Type attributeType = Unsafe.As(attributeHandle.Target);
+ Type attributeType = Unsafe.As(attributeHandle.Target);
attrib = attributes.FirstOrDefault(x => x.GetType() == attributeType);
}
else
@@ -413,7 +454,7 @@ namespace FlaxEngine.Interop
[UnmanagedCallersOnly]
internal static void GetClassInterfaces(ManagedHandle typeHandle, IntPtr* classInterfaces, int* classInterfacesCount)
{
- Type type = Unsafe.As(typeHandle.Target);
+ Type type = Unsafe.As(typeHandle.Target);
Type[] interfaces = type.GetInterfaces();
// Match mono_class_get_interfaces which doesn't return interfaces from base class
@@ -465,7 +506,7 @@ namespace FlaxEngine.Interop
IntPtr arr = (IntPtr)NativeAlloc(interfaces.Length, IntPtr.Size);
for (int i = 0; i < interfaces.Length; i++)
{
- ManagedHandle handle = GetTypeGCHandle(interfaces[i]);
+ ManagedHandle handle = GetTypeManagedHandle(interfaces[i]);
Unsafe.Write(IntPtr.Add(arr, IntPtr.Size * i).ToPointer(), handle);
}
*classInterfaces = arr;
@@ -477,7 +518,7 @@ namespace FlaxEngine.Interop
{
MethodHolder methodHolder = Unsafe.As(methodHandle.Target);
Type returnType = methodHolder.returnType;
- return GetTypeGCHandle(returnType);
+ return GetTypeManagedHandle(returnType);
}
[UnmanagedCallersOnly]
@@ -488,7 +529,7 @@ namespace FlaxEngine.Interop
IntPtr arr = (IntPtr)NativeAlloc(methodHolder.parameterTypes.Length, IntPtr.Size);
for (int i = 0; i < methodHolder.parameterTypes.Length; i++)
{
- ManagedHandle typeHandle = GetTypeGCHandle(methodHolder.parameterTypes[i]);
+ ManagedHandle typeHandle = GetTypeManagedHandle(methodHolder.parameterTypes[i]);
Unsafe.Write(IntPtr.Add(new IntPtr(arr), IntPtr.Size * i).ToPointer(), typeHandle);
}
*typeHandles = arr;
@@ -509,22 +550,15 @@ namespace FlaxEngine.Interop
[UnmanagedCallersOnly]
internal static ManagedHandle NewObject(ManagedHandle typeHandle)
{
- Type type = Unsafe.As(typeHandle.Target);
- if (type.IsAbstract)
- {
- // Dotnet doesn't allow to instantiate abstract type thus allow to use generated mock class usage (eg. for Script or GPUResource) for generated abstract types
- var abstractWrapper = type.GetNestedType("AbstractWrapper", BindingFlags.NonPublic);
- if (abstractWrapper != null)
- type = abstractWrapper;
- }
- object value = RuntimeHelpers.GetUninitializedObject(type);
+ TypeHolder typeHolder = Unsafe.As(typeHandle.Target);
+ object value = typeHolder.CreateObject();
return ManagedHandle.Alloc(value);
}
[UnmanagedCallersOnly]
internal static ManagedHandle NewArray(ManagedHandle typeHandle, long size)
{
- Type elementType = Unsafe.As(typeHandle.Target);
+ Type elementType = Unsafe.As(typeHandle.Target);
Type marshalledType = ArrayFactory.GetMarshalledType(elementType);
Type arrayType = ArrayFactory.GetArrayType(elementType);
if (marshalledType.IsValueType)
@@ -543,9 +577,9 @@ namespace FlaxEngine.Interop
[UnmanagedCallersOnly]
internal static ManagedHandle GetArrayTypeFromElementType(ManagedHandle elementTypeHandle)
{
- Type elementType = Unsafe.As(elementTypeHandle.Target);
+ Type elementType = Unsafe.As(elementTypeHandle.Target);
Type classType = ArrayFactory.GetArrayType(elementType);
- return GetTypeGCHandle(classType);
+ return GetTypeManagedHandle(classType);
}
[UnmanagedCallersOnly]
@@ -606,7 +640,7 @@ namespace FlaxEngine.Interop
Type classType = obj.GetType();
if (classType == typeof(ManagedArray))
classType = ((ManagedArray)obj).ArrayType;
- return GetTypeGCHandle(classType);
+ return GetTypeManagedHandle(classType);
}
[UnmanagedCallersOnly]
@@ -647,7 +681,7 @@ namespace FlaxEngine.Interop
[UnmanagedCallersOnly]
internal static ManagedHandle BoxValue(ManagedHandle typeHandle, IntPtr valuePtr)
{
- Type type = Unsafe.As(typeHandle.Target);
+ Type type = Unsafe.As(typeHandle.Target);
object value = MarshalToManaged(valuePtr, type);
return ManagedHandle.Alloc(value, GCHandleType.Weak);
}
@@ -690,6 +724,14 @@ namespace FlaxEngine.Interop
}
}
+ [UnmanagedCallersOnly]
+ internal static IntPtr GetObjectClass(ManagedHandle objectHandle)
+ {
+ object obj = objectHandle.Target;
+ TypeHolder typeHolder = GetTypeHolder(obj.GetType());
+ return typeHolder.managedClassPointer;
+ }
+
[UnmanagedCallersOnly]
internal static IntPtr InvokeMethod(ManagedHandle instanceHandle, ManagedHandle methodHandle, IntPtr paramPtr, IntPtr exceptionPtr)
{
@@ -706,7 +748,7 @@ namespace FlaxEngine.Interop
catch (Exception exception)
{
if (exceptionPtr != IntPtr.Zero)
- Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(exception, GCHandleType.Weak));
+ Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak));
return IntPtr.Zero;
}
return returnValue;
@@ -721,7 +763,7 @@ namespace FlaxEngine.Interop
for (int i = 0; i < numParams; i++)
{
- IntPtr nativePtr = Marshal.ReadIntPtr(IntPtr.Add(paramPtr, sizeof(IntPtr) * i));
+ IntPtr nativePtr = Unsafe.Read((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer());
methodParameters[i] = MarshalToManaged(nativePtr, methodHolder.parameterTypes[i]);
}
@@ -737,7 +779,7 @@ namespace FlaxEngine.Interop
realException = exception.InnerException;
if (exceptionPtr != IntPtr.Zero)
- Marshal.WriteIntPtr(exceptionPtr, ManagedHandle.ToIntPtr(realException, GCHandleType.Weak));
+ Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(realException, GCHandleType.Weak));
else
throw realException;
return IntPtr.Zero;
@@ -749,7 +791,7 @@ namespace FlaxEngine.Interop
Type parameterType = methodHolder.parameterTypes[i];
if (parameterType.IsByRef)
{
- IntPtr nativePtr = Marshal.ReadIntPtr(IntPtr.Add(paramPtr, sizeof(IntPtr) * i));
+ IntPtr nativePtr = Unsafe.Read((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer());
MarshalToNative(methodParameters[i], nativePtr, parameterType.GetElementType());
}
}
@@ -803,7 +845,7 @@ namespace FlaxEngine.Interop
internal static int FieldGetOffset(ManagedHandle fieldHandle)
{
FieldHolder field = Unsafe.As(fieldHandle.Target);
- return (int)Marshal.OffsetOf(field.field.DeclaringType, field.field.Name);
+ return field.fieldOffset;
}
[UnmanagedCallersOnly]
@@ -811,7 +853,40 @@ namespace FlaxEngine.Interop
{
object fieldOwner = fieldOwnerHandle.Target;
FieldHolder field = Unsafe.As(fieldHandle.Target);
- field.toNativeMarshaller(field.field, fieldOwner, valuePtr, out int fieldOffset);
+ field.toNativeMarshaller(field.fieldOffset, fieldOwner, valuePtr, out int fieldSize);
+ }
+
+ [UnmanagedCallersOnly]
+ internal static void FieldGetValueReference(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr)
+ {
+ object fieldOwner = fieldOwnerHandle.Target;
+ FieldHolder field = Unsafe.As(fieldHandle.Target);
+ if (fieldOwner.GetType().IsValueType)
+ {
+ ref IntPtr fieldRef = ref FieldHelper.GetValueTypeFieldReference