Merge branch 'FlaxEngine:master' into add_spline_edit_options

This commit is contained in:
Mr. Capybara
2023-08-05 11:11:11 -04:00
committed by GitHub
83 changed files with 1289 additions and 393 deletions

View File

@@ -45,8 +45,14 @@ namespace FlaxEditor.Content.GUI
private void ImportActors(DragActors actors, ContentFolder location)
{
// Use only the first actor
Editor.Instance.Prefabs.CreatePrefab(actors.Objects[0].Actor);
foreach (var actorNode in actors.Objects)
{
var actor = actorNode.Actor;
if (actors.Objects.Contains(actorNode.ParentNode as ActorNode))
continue;
Editor.Instance.Prefabs.CreatePrefab(actor, false);
}
}
/// <inheritdoc />

View File

@@ -33,7 +33,12 @@ namespace FlaxEditor.CustomEditors.Editors
[CustomEditor(typeof(Asset)), DefaultEditor]
public class AssetRefEditor : CustomEditor
{
private AssetPicker _picker;
/// <summary>
/// The asset picker used to get a reference to an asset.
/// </summary>
public AssetPicker Picker;
private bool _isRefreshing;
private ScriptType _valueType;
/// <inheritdoc />
@@ -44,7 +49,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (HasDifferentTypes)
return;
_picker = layout.Custom<AssetPicker>().CustomControl;
Picker = layout.Custom<AssetPicker>().CustomControl;
_valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
var assetType = _valueType;
@@ -66,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
// Generic file picker
assetType = ScriptType.Null;
_picker.FileExtension = assetReference.TypeName;
Picker.FileExtension = assetReference.TypeName;
}
else
{
@@ -75,26 +80,30 @@ namespace FlaxEditor.CustomEditors.Editors
assetType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
else
assetType = ScriptType.Void;
}
}
_picker.AssetType = assetType;
_picker.Height = height;
_picker.SelectedItemChanged += OnSelectedItemChanged;
Picker.AssetType = assetType;
Picker.Height = height;
Picker.SelectedItemChanged += OnSelectedItemChanged;
}
private void OnSelectedItemChanged()
{
if (_isRefreshing)
return;
if (typeof(AssetItem).IsAssignableFrom(_valueType.Type))
SetValue(_picker.SelectedItem);
SetValue(Picker.SelectedItem);
else if (_valueType.Type == typeof(Guid))
SetValue(_picker.SelectedID);
SetValue(Picker.SelectedID);
else if (_valueType.Type == typeof(SceneReference))
SetValue(new SceneReference(_picker.SelectedID));
SetValue(new SceneReference(Picker.SelectedID));
else if (_valueType.Type == typeof(string))
SetValue(_picker.SelectedPath);
SetValue(Picker.SelectedPath);
else
SetValue(_picker.SelectedAsset);
SetValue(Picker.SelectedAsset);
}
/// <inheritdoc />
@@ -104,16 +113,18 @@ namespace FlaxEditor.CustomEditors.Editors
if (!HasDifferentValues)
{
_isRefreshing = true;
if (Values[0] is AssetItem assetItem)
_picker.SelectedItem = assetItem;
Picker.SelectedItem = assetItem;
else if (Values[0] is Guid guid)
_picker.SelectedID = guid;
Picker.SelectedID = guid;
else if (Values[0] is SceneReference sceneAsset)
_picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
else if (Values[0] is string path)
_picker.SelectedPath = path;
Picker.SelectedPath = path;
else
_picker.SelectedAsset = Values[0] as Asset;
Picker.SelectedAsset = Values[0] as Asset;
_isRefreshing = false;
}
}
}

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
namespace FlaxEditor.CustomEditors.Editors
@@ -13,6 +15,11 @@ namespace FlaxEditor.CustomEditors.Editors
{
private GroupElement _group;
private bool _updateName;
private int _entryIndex;
private bool _isRefreshing;
private MaterialBase _material;
private ModelInstanceActor _modelInstance;
private AssetRefEditor _materialEditor;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
@@ -21,47 +28,122 @@ namespace FlaxEditor.CustomEditors.Editors
var group = layout.Group("Entry");
_group = group;
if (ParentEditor == null)
return;
var entry = (ModelInstanceEntry)Values[0];
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
var materialLabel = new PropertyNameLabel("Material");
materialLabel.TooltipText = "The mesh surface material used for the rendering.";
if (ParentEditor.ParentEditor?.Values[0] is ModelInstanceActor modelInstance)
{
_entryIndex = entryIndex;
_modelInstance = modelInstance;
var slots = modelInstance.MaterialSlots;
if (entry.Material == slots[entryIndex].Material)
{
// Ensure that entry with default material set is set back to null
modelInstance.SetMaterial(entryIndex, null);
}
_material = modelInstance.GetMaterial(entryIndex);
var defaultValue = GPUDevice.Instance.DefaultMaterial;
if (slots[entryIndex].Material)
{
// Use default value set on asset (eg. Model Asset)
defaultValue = slots[entryIndex].Material;
}
// Create material picker
var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase);
var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue);
materialEditor.Values.SetDefaultValue(defaultValue);
materialEditor.RefreshDefaultValue();
materialEditor.Picker.SelectedItemChanged += OnSelectedMaterialChanged;
_materialEditor = materialEditor;
}
base.Initialize(group);
}
private void OnSelectedMaterialChanged()
{
if (_isRefreshing)
return;
_isRefreshing = true;
var slots = _modelInstance.MaterialSlots;
var material = _materialEditor.Picker.SelectedAsset as MaterialBase;
var defaultMaterial = GPUDevice.Instance.DefaultMaterial;
var value = (ModelInstanceEntry)Values[0];
var prevMaterial = value.Material;
if (!material)
{
// Fallback to default material
_materialEditor.Picker.SelectedAsset = defaultMaterial;
value.Material = defaultMaterial;
}
else if (material == slots[_entryIndex].Material)
{
// Asset default material
value.Material = null;
}
else if (material == defaultMaterial && !slots[_entryIndex].Material)
{
// Default material while asset has no set as well
value.Material = null;
}
else
{
// Custom material
value.Material = material;
}
if (prevMaterial != value.Material)
SetValue(value);
_isRefreshing = false;
}
/// <inheritdoc />
protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
{
// Skip material member as it is overridden
if (item.Info.Name == "Material" && _materialEditor != null)
return;
base.SpawnProperty(itemLayout, itemValues, item);
}
/// <inheritdoc />
public override void Refresh()
{
// Update panel title to match material slot name
if (_updateName &&
_group != null &&
ParentEditor?.ParentEditor != null &&
ParentEditor.ParentEditor.Values.Count > 0)
{
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
if (ParentEditor.ParentEditor.Values[0] is StaticModel staticModel)
if (ParentEditor.ParentEditor.Values[0] is ModelInstanceActor modelInstance)
{
var model = staticModel.Model;
if (model && model.IsLoaded)
var slots = modelInstance.MaterialSlots;
if (slots != null && slots.Length > entryIndex)
{
var slots = model.MaterialSlots;
if (slots != null && slots.Length > entryIndex)
{
_group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
_updateName = false;
}
}
}
else if (ParentEditor.ParentEditor.Values[0] is AnimatedModel animatedModel)
{
var model = animatedModel.SkinnedModel;
if (model && model.IsLoaded)
{
var slots = model.MaterialSlots;
if (slots != null && slots.Length > entryIndex)
{
_group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
_updateName = false;
}
_updateName = false;
_group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
}
}
}
// Refresh currently selected material
_material = _modelInstance.GetMaterial(_entryIndex);
base.Refresh();
}
/// <inheritdoc />
protected override void Deinitialize()
{
_material = null;
_modelInstance = null;
_materialEditor = null;
base.Deinitialize();
}
}
}

View File

@@ -96,6 +96,7 @@ namespace FlaxEditor
public SpriteHandle Link64;
public SpriteHandle Build64;
public SpriteHandle Add64;
public SpriteHandle ShipIt64;
// 96px
public SpriteHandle Toolbox96;

View File

@@ -475,22 +475,31 @@ namespace FlaxEditor.GUI
if (_type != ScriptType.Null)
{
// Show asset picker popup
AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
{
SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (_selected != null)
{
var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}
else
{
// Show content item picker popup
ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
{
SelectedItem = item;
RootWindow.Focus();
Focus();
});
if (_selectedItem != null)
{
popup.ScrollToAndHighlightItemByName(_selectedItem.ShortName);
}
}
}
else if (_selected != null || _selectedItem != null)

View File

@@ -0,0 +1,93 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEngine;
namespace FlaxEditor.GUI.ContextMenu
{
/// <summary>
/// Context menu for a single selectable option from range of values (eg. enum).
/// </summary>
[HideInEditor]
class ContextMenuSingleSelectGroup<T>
{
private struct SingleSelectGroupItem
{
public string Text;
public string Tooltip;
public T Value;
public Action Selected;
public List<ContextMenuButton> Buttons;
}
private List<ContextMenu> _menus = new List<ContextMenu>();
private List<SingleSelectGroupItem> _items = new List<SingleSelectGroupItem>();
private SingleSelectGroupItem _selectedItem;
public T Selected
{
get => _selectedItem.Value;
set
{
var index = _items.FindIndex(x => x.Value.Equals(value));
if (index != -1 && !_selectedItem.Value.Equals(value))
{
SetSelected(_items[index]);
}
}
}
public Action<T> SelectedChanged;
public ContextMenuSingleSelectGroup<T> AddItem(string text, T value, Action selected = null, string tooltip = null)
{
var item = new SingleSelectGroupItem
{
Text = text,
Tooltip = tooltip,
Value = value,
Selected = selected,
Buttons = new List<ContextMenuButton>()
};
_items.Add(item);
foreach (var contextMenu in _menus)
AddItemToContextMenu(contextMenu, item);
return this;
}
public ContextMenuSingleSelectGroup<T> AddItemsToContextMenu(ContextMenu contextMenu)
{
_menus.Add(contextMenu);
for (int i = 0; i < _items.Count; i++)
AddItemToContextMenu(contextMenu, _items[i]);
return this;
}
private void AddItemToContextMenu(ContextMenu contextMenu, SingleSelectGroupItem item)
{
var btn = contextMenu.AddButton(item.Text, () => { SetSelected(item); });
if (item.Tooltip != null)
btn.TooltipText = item.Tooltip;
item.Buttons.Add(btn);
if (item.Equals(_selectedItem))
btn.Checked = true;
}
private void SetSelected(SingleSelectGroupItem item)
{
foreach (var e in _items)
{
foreach (var btn in e.Buttons)
btn.Checked = false;
}
_selectedItem = item;
SelectedChanged?.Invoke(item.Value);
item.Selected?.Invoke();
foreach (var btn in item.Buttons)
btn.Checked = true;
}
}
}

View File

@@ -265,6 +265,35 @@ namespace FlaxEditor.GUI
_searchBox.Focus();
}
/// <summary>
/// Scrolls the scroll panel to a specific Item
/// </summary>
/// <param name="item">The item to scroll to.</param>
public void ScrollViewTo(Item item)
{
_scrollPanel.ScrollViewTo(item, true);
}
/// <summary>
/// Scrolls to the item and focuses it by name.
/// </summary>
/// <param name="itemName">The item name.</param>
public void ScrollToAndHighlightItemByName(string itemName)
{
foreach (var child in ItemsPanel.Children)
{
if (child is not ItemsListContextMenu.Item item)
continue;
if (string.Equals(item.Name, itemName, StringComparison.Ordinal))
{
// Highlight and scroll to item
item.Focus();
ScrollViewTo(item);
break;
}
}
}
/// <summary>
/// Sorts the items list (by item name by default).
/// </summary>

View File

@@ -23,10 +23,15 @@ namespace FlaxEditor.GUI
public const int DefaultMarginH = 2;
/// <summary>
/// Event fired when button gets clicked.
/// Event fired when button gets clicked with the primary mouse button.
/// </summary>
public Action<ToolStripButton> ButtonClicked;
/// <summary>
/// Event fired when button gets clicked with the secondary mouse button.
/// </summary>
public Action<ToolStripButton> SecondaryButtonClicked;
/// <summary>
/// Tries to get the last button.
/// </summary>
@@ -146,6 +151,11 @@ namespace FlaxEditor.GUI
ButtonClicked?.Invoke(button);
}
internal void OnSecondaryButtonClicked(ToolStripButton button)
{
SecondaryButtonClicked?.Invoke(button);
}
/// <inheritdoc />
protected override void PerformLayoutBeforeChildren()
{

View File

@@ -20,13 +20,19 @@ namespace FlaxEditor.GUI
private SpriteHandle _icon;
private string _text;
private bool _mouseDown;
private bool _primaryMouseDown;
private bool _secondaryMouseDown;
/// <summary>
/// Event fired when user clicks the button.
/// </summary>
public Action Clicked;
/// <summary>
/// Event fired when user clicks the button.
/// </summary>
public Action SecondaryClicked;
/// <summary>
/// The checked state.
/// </summary>
@@ -63,6 +69,11 @@ namespace FlaxEditor.GUI
}
}
/// <summary>
/// A reference to a context menu to raise when the secondary mouse button is pressed.
/// </summary>
public ContextMenu.ContextMenu ContextMenu;
/// <summary>
/// Initializes a new instance of the <see cref="ToolStripButton"/> class.
/// </summary>
@@ -108,10 +119,11 @@ namespace FlaxEditor.GUI
var iconRect = new Rectangle(DefaultMargin, DefaultMargin, iconSize, iconSize);
var textRect = new Rectangle(DefaultMargin, 0, 0, Height);
bool enabled = EnabledInHierarchy;
bool mouseButtonDown = _primaryMouseDown || _secondaryMouseDown;
// Draw background
if (enabled && (IsMouseOver || IsNavFocused || Checked))
Render2D.FillRectangle(clientRect, Checked ? style.BackgroundSelected : _mouseDown ? style.BackgroundHighlighted : (style.LightBackground * 1.3f));
Render2D.FillRectangle(clientRect, Checked ? style.BackgroundSelected : mouseButtonDown ? style.BackgroundHighlighted : (style.LightBackground * 1.3f));
// Draw icon
if (_icon.IsValid)
@@ -124,13 +136,7 @@ namespace FlaxEditor.GUI
if (!string.IsNullOrEmpty(_text))
{
textRect.Size.X = Width - DefaultMargin - textRect.Left;
Render2D.DrawText(
style.FontMedium,
_text,
textRect,
enabled ? style.Foreground : style.ForegroundDisabled,
TextAlignment.Near,
TextAlignment.Center);
Render2D.DrawText(style.FontMedium, _text, textRect, enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center);
}
}
@@ -155,9 +161,13 @@ namespace FlaxEditor.GUI
{
if (button == MouseButton.Left)
{
// Set flag
_mouseDown = true;
_primaryMouseDown = true;
Focus();
return true;
}
if (button == MouseButton.Right)
{
_secondaryMouseDown = true;
Focus();
return true;
}
@@ -168,12 +178,9 @@ namespace FlaxEditor.GUI
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _mouseDown)
if (button == MouseButton.Left && _primaryMouseDown)
{
// Clear flag
_mouseDown = false;
// Fire events
_primaryMouseDown = false;
if (AutoCheck)
Checked = !Checked;
Clicked?.Invoke();
@@ -181,6 +188,14 @@ namespace FlaxEditor.GUI
return true;
}
if (button == MouseButton.Right && _secondaryMouseDown)
{
_secondaryMouseDown = false;
SecondaryClicked?.Invoke();
(Parent as ToolStrip)?.OnSecondaryButtonClicked(this);
ContextMenu?.Show(this, new Float2(0, Height));
return true;
}
return base.OnMouseUp(location, button);
}
@@ -188,8 +203,8 @@ namespace FlaxEditor.GUI
/// <inheritdoc />
public override void OnMouseLeave()
{
// Clear flag
_mouseDown = false;
_primaryMouseDown = false;
_secondaryMouseDown = false;
base.OnMouseLeave();
}
@@ -197,8 +212,8 @@ namespace FlaxEditor.GUI
/// <inheritdoc />
public override void OnLostFocus()
{
// Clear flag
_mouseDown = false;
_primaryMouseDown = false;
_secondaryMouseDown = false;
base.OnLostFocus();
}

View File

@@ -46,20 +46,19 @@ namespace FlaxEditor.Gizmo
var plane = new Plane(Vector3.Zero, Vector3.UnitY);
var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos);
float space, size;
float space = Editor.Instance.Options.Options.Viewport.ViewportGridScale, size;
if (dst <= 500.0f)
{
space = 50;
size = 8000;
}
else if (dst <= 2000.0f)
{
space = 100;
space *= 2;
size = 8000;
}
else
{
space = 1000;
space *= 20;
size = 100000;
}

View File

@@ -178,6 +178,8 @@ namespace FlaxEditor.Gizmo
if (selection[i] is ActorNode actorNode && actorNode.Actor != null)
CollectActors(actorNode.Actor);
}
if (_actors.Count == 0)
return;
// Render selected objects depth
Renderer.DrawSceneDepth(context, task, customDepth, _actors);

View File

@@ -469,7 +469,7 @@ namespace FlaxEditor.Gizmo
}
// Apply transformation (but to the parents, not whole selection pool)
if (anyValid || (!_isTransforming && Owner.UseDuplicate))
if (anyValid || (_isTransforming && Owner.UseDuplicate))
{
StartTransforming();
LastDelta = new Transform(translationDelta, rotationDelta, scaleDelta);

View File

@@ -37,6 +37,11 @@ namespace FlaxEditor.Modules
/// </summary>
public event Action<Prefab, Actor> PrefabApplied;
/// <summary>
/// Locally cached actor for prefab creation.
/// </summary>
private Actor _prefabCreationActor;
internal PrefabsModule(Editor editor)
: base(editor)
{
@@ -78,6 +83,16 @@ namespace FlaxEditor.Modules
/// </remarks>
/// <param name="actor">The root prefab actor.</param>
public void CreatePrefab(Actor actor)
{
CreatePrefab(actor, true);
}
/// <summary>
/// Starts the creating prefab for the given actor by showing the new item creation dialog in <see cref="ContentWindow"/>.
/// </summary>
/// <param name="actor">The root prefab actor.</param>
/// <param name="rename">Allow renaming or not</param>
public void CreatePrefab(Actor actor, bool rename)
{
// Skip in invalid states
if (!Editor.StateMachine.CurrentState.CanEditContent)
@@ -90,7 +105,8 @@ namespace FlaxEditor.Modules
PrefabCreating?.Invoke(actor);
var proxy = Editor.ContentDatabase.GetProxy<Prefab>();
Editor.Windows.ContentWin.NewItem(proxy, actor, OnPrefabCreated, actor.Name);
_prefabCreationActor = actor;
Editor.Windows.ContentWin.NewItem(proxy, actor, OnPrefabCreated, actor.Name, rename);
}
private void OnPrefabCreated(ContentItem contentItem)
@@ -107,25 +123,21 @@ namespace FlaxEditor.Modules
// Record undo for prefab creating (backend links the target instance with the prefab)
if (Editor.Undo.Enabled)
{
var selection = Editor.SceneEditing.Selection.Where(x => x is ActorNode).ToList().BuildNodesParents();
if (selection.Count == 0)
if (!_prefabCreationActor)
return;
if (selection.Count == 1)
var actorsList = new List<Actor>();
Utilities.Utils.GetActorsTree(actorsList, _prefabCreationActor);
var actions = new IUndoAction[actorsList.Count];
for (int i = 0; i < actorsList.Count; i++)
{
var action = BreakPrefabLinkAction.Linked(((ActorNode)selection[0]).Actor);
Undo.AddAction(action);
}
else
{
var actions = new IUndoAction[selection.Count];
for (int i = 0; i < selection.Count; i++)
{
var action = BreakPrefabLinkAction.Linked(((ActorNode)selection[i]).Actor);
actions[i] = action;
}
Undo.AddAction(new MultiUndoAction(actions));
var action = BreakPrefabLinkAction.Linked(actorsList[i]);
actions[i] = action;
}
Undo.AddAction(new MultiUndoAction(actions));
_prefabCreationActor = null;
}
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();

View File

@@ -20,6 +20,8 @@ using FlaxEngine.GUI;
using FlaxEngine.Json;
using DockHintWindow = FlaxEditor.GUI.Docking.DockHintWindow;
using MasterDockPanel = FlaxEditor.GUI.Docking.MasterDockPanel;
using FlaxEditor.Content.Settings;
using FlaxEditor.Options;
namespace FlaxEditor.Modules
{
@@ -36,6 +38,9 @@ namespace FlaxEditor.Modules
private ContentStats _contentStats;
private bool _progressFailed;
ContextMenuSingleSelectGroup<int> _numberOfClientsGroup = new ContextMenuSingleSelectGroup<int>();
private Scene[] _scenesToReload;
private ContextMenuButton _menuFileSaveScenes;
private ContextMenuButton _menuFileCloseScenes;
private ContextMenuButton _menuFileGenerateScriptsProjectFiles;
@@ -54,7 +59,9 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuSceneAlignViewportWithActor;
private ContextMenuButton _menuScenePilotActor;
private ContextMenuButton _menuSceneCreateTerrain;
private ContextMenuButton _menuGamePlay;
private ContextMenuButton _menuGamePlayGame;
private ContextMenuButton _menuGamePlayCurrentScenes;
private ContextMenuButton _menuGameStop;
private ContextMenuButton _menuGamePause;
private ContextMenuButton _menuToolsBuildScenes;
private ContextMenuButton _menuToolsBakeLightmaps;
@@ -74,6 +81,7 @@ namespace FlaxEditor.Modules
private ToolStripButton _toolStripRotate;
private ToolStripButton _toolStripScale;
private ToolStripButton _toolStripBuildScenes;
private ToolStripButton _toolStripCook;
private ToolStripButton _toolStripPlay;
private ToolStripButton _toolStripPause;
private ToolStripButton _toolStripStep;
@@ -195,6 +203,7 @@ namespace FlaxEditor.Modules
_toolStripScale.Checked = gizmoMode == TransformGizmoBase.Mode.Scale;
//
_toolStripBuildScenes.Enabled = (canEditScene && !isPlayMode) || Editor.StateMachine.BuildingScenesState.IsActive;
_toolStripCook.Enabled = Editor.Windows.GameCookerWin.CanBuild(Platform.PlatformType) && !GameCooker.IsRunning;
//
var play = _toolStripPlay;
var pause = _toolStripPause;
@@ -351,6 +360,7 @@ namespace FlaxEditor.Modules
// Update window background
mainWindow.BackgroundColor = Style.Current.Background;
InitSharedMenus();
InitMainMenu(mainWindow);
InitToolstrip(mainWindow);
InitStatusBar(mainWindow);
@@ -409,6 +419,7 @@ namespace FlaxEditor.Modules
Editor.Undo.UndoDone += OnUndoEvent;
Editor.Undo.RedoDone += OnUndoEvent;
Editor.Undo.ActionDone += OnUndoEvent;
GameCooker.Event += OnGameCookerEvent;
UpdateToolstrip();
}
@@ -424,6 +435,11 @@ namespace FlaxEditor.Modules
UpdateStatusBar();
}
private void OnGameCookerEvent(GameCooker.EventType type)
{
UpdateToolstrip();
}
/// <inheritdoc />
public override void OnExit()
{
@@ -466,6 +482,22 @@ namespace FlaxEditor.Modules
return dialog;
}
private void InitSharedMenus()
{
for (int i = 1; i <= 4; i++)
_numberOfClientsGroup.AddItem(i.ToString(), i);
_numberOfClientsGroup.Selected = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch;
_numberOfClientsGroup.SelectedChanged = value =>
{
var options = Editor.Options.Options;
options.Interface.NumberOfGameClientsToLaunch = value;
Editor.Options.Apply(options);
};
Editor.Options.OptionsChanged += options => { _numberOfClientsGroup.Selected = options.Interface.NumberOfGameClientsToLaunch; };
}
private void InitMainMenu(RootControl mainWindow)
{
MainMenu = new MainMenu(mainWindow)
@@ -523,11 +555,19 @@ namespace FlaxEditor.Modules
MenuGame = MainMenu.AddButton("Game");
cm = MenuGame.ContextMenu;
cm.VisibleChanged += OnMenuGameShowHide;
_menuGamePlay = cm.AddButton("Play", inputOptions.Play.ToString(), Editor.Simulation.RequestStartPlay);
_menuGamePlayGame = cm.AddButton("Play Game", PlayGame);
_menuGamePlayCurrentScenes = cm.AddButton("Play Current Scenes", inputOptions.Play.ToString(), PlayScenes);
_menuGameStop = cm.AddButton("Stop Game", Editor.Simulation.RequestStopPlay);
_menuGamePause = cm.AddButton("Pause", inputOptions.Pause.ToString(), Editor.Simulation.RequestPausePlay);
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.");
var numberOfClientsMenu = cm.AddChildMenu("Number of game clients");
_numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
cm.AddSeparator();
cm.AddButton("Cook & Run", CookAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after.");
cm.AddButton("Run cooked game", RunCookedGame).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first.");
// Tools
MenuTools = MainMenu.AddButton("Tools");
@@ -603,7 +643,7 @@ namespace FlaxEditor.Modules
_menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString();
_menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString();
_menuEditFind.ShortKeys = inputOptions.Search.ToString();
_menuGamePlay.ShortKeys = inputOptions.Play.ToString();
_menuGamePlayCurrentScenes.ShortKeys = inputOptions.Play.ToString();
_menuGamePause.ShortKeys = inputOptions.Pause.ToString();
MainMenuShortcutKeysUpdated?.Invoke();
@@ -611,6 +651,8 @@ namespace FlaxEditor.Modules
private void InitToolstrip(RootControl mainWindow)
{
var inputOptions = Editor.Options.Options.Input;
ToolStrip = new ToolStrip(34.0f, MainMenu.Bottom)
{
Parent = mainWindow,
@@ -625,10 +667,33 @@ namespace FlaxEditor.Modules
_toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)");
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)");
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)");
// Cook and run
_toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, CookAndRun).LinkTooltip("Cook & Run - build game for the current platform and run it locally");
_toolStripCook.ContextMenu = new ContextMenu();
_toolStripCook.ContextMenu.AddButton("Run cooked game", RunCookedGame);
_toolStripCook.ContextMenu.AddSeparator();
var numberOfClientsMenu = _toolStripCook.ContextMenu.AddChildMenu("Number of game clients");
_numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
ToolStrip.AddSeparator();
_toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.RequestPlayOrStopPlay).LinkTooltip("Start/Stop game (F5)");
_toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip("Pause/Resume game(F6)");
// Play
_toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, OnPlayPressed).LinkTooltip("Play Game");
_toolStripPlay.ContextMenu = new ContextMenu();
var playSubMenu = _toolStripPlay.ContextMenu.AddChildMenu("Play button action");
var playActionGroup = new ContextMenuSingleSelectGroup<InterfaceOptions.PlayAction>();
playActionGroup.AddItem("Play Game", InterfaceOptions.PlayAction.PlayGame, null, "Launches the game from the First Scene defined in the project settings.");
playActionGroup.AddItem("Play Scenes", InterfaceOptions.PlayAction.PlayScenes, null, "Launches the game using the scenes currently loaded in the editor.");
playActionGroup.AddItemsToContextMenu(playSubMenu.ContextMenu);
playActionGroup.Selected = Editor.Options.Options.Interface.PlayButtonAction;
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})");
_toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
UpdateToolstrip();
@@ -663,10 +728,7 @@ namespace FlaxEditor.Modules
var defaultTextColor = StatusBar.TextColor;
_outputLogButton.HoverBegin += () => StatusBar.TextColor = Style.Current.BackgroundSelected;
_outputLogButton.HoverEnd += () => StatusBar.TextColor = defaultTextColor;
_outputLogButton.Clicked += () =>
{
Editor.Windows.OutputLogWin.FocusOrShow();
};
_outputLogButton.Clicked += () => { Editor.Windows.OutputLogWin.FocusOrShow(); };
// Progress bar with label
const float progressBarWidth = 120.0f;
@@ -786,7 +848,9 @@ namespace FlaxEditor.Modules
var isPlayMode = Editor.StateMachine.IsPlayMode;
var canPlay = Level.IsAnySceneLoaded;
_menuGamePlay.Enabled = !isPlayMode && canPlay;
_menuGamePlayGame.Enabled = !isPlayMode && canPlay;
_menuGamePlayCurrentScenes.Enabled = !isPlayMode && canPlay;
_menuGameStop.Enabled = isPlayMode && canPlay;
_menuGamePause.Enabled = isPlayMode && canPlay;
c.PerformLayout();
@@ -968,6 +1032,72 @@ namespace FlaxEditor.Modules
projectInfo.Save();
}
private void SetPlayAction(InterfaceOptions.PlayAction newPlayAction)
{
var options = Editor.Options.Options;
options.Interface.PlayButtonAction = newPlayAction;
Editor.Options.Apply(options);
}
private void OnPlayPressed()
{
switch (Editor.Options.Options.Interface.PlayButtonAction)
{
case InterfaceOptions.PlayAction.PlayGame:
if (Editor.IsPlayMode)
Editor.Simulation.RequestStopPlay();
else
PlayGame();
return;
case InterfaceOptions.PlayAction.PlayScenes:
PlayScenes();
return;
}
}
private void PlayGame()
{
var firstScene = GameSettings.Load().FirstScene;
if (firstScene == Guid.Empty)
{
if (Level.IsAnySceneLoaded)
Editor.Simulation.RequestStartPlay();
return;
}
_scenesToReload = Level.Scenes;
Level.UnloadAllScenes();
Level.LoadScene(firstScene);
Editor.PlayModeEnd += OnPlayGameSceneEnding;
Editor.Simulation.RequestPlayOrStopPlay();
}
private void OnPlayGameSceneEnding()
{
Editor.PlayModeEnd -= OnPlayGameSceneEnding;
Level.UnloadAllScenes();
foreach (var scene in _scenesToReload)
Level.LoadScene(scene.ID);
}
private void PlayScenes()
{
Editor.Simulation.RequestPlayOrStopPlay();
}
private void CookAndRun()
{
Editor.Windows.GameCookerWin.BuildAndRun();
}
private void RunCookedGame()
{
Editor.Windows.GameCookerWin.RunCooked();
}
private void OnMainWindowClosing()
{
// Clear UI references (GUI cannot be used after window closing)

View File

@@ -74,6 +74,22 @@ namespace FlaxEditor.Options
DockBottom = DockState.DockBottom
}
/// <summary>
/// Options for the action taken by the play button.
/// </summary>
public enum PlayAction
{
/// <summary>
/// Launches the game from the First Scene defined in the project settings.
/// </summary>
PlayGame,
/// <summary>
/// Launches the game using the scenes currently loaded in the editor.
/// </summary>
PlayScenes,
}
/// <summary>
/// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.
/// </summary>
@@ -82,14 +98,12 @@ namespace FlaxEditor.Options
public float InterfaceScale { get; set; } = 1.0f;
#if PLATFORM_WINDOWS
/// <summary>
/// Gets or sets a value indicating whether use native window title bar. Editor restart required.
/// </summary>
[DefaultValue(false)]
[EditorDisplay("Interface"), EditorOrder(70), Tooltip("Determines whether use native window title bar. Editor restart required.")]
public bool UseNativeWindowSystem { get; set; } = false;
#endif
/// <summary>
@@ -196,30 +210,44 @@ namespace FlaxEditor.Options
[EditorDisplay("Play In-Editor", "Focus Game Window On Play"), EditorOrder(400), Tooltip("Determines whether auto-focus game window on play mode start.")]
public bool FocusGameWinOnPlay { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating what action should be taken upon pressing the play button.
/// </summary>
[DefaultValue(PlayAction.PlayScenes)]
[EditorDisplay("Play In-Editor", "Play Button Action"), EditorOrder(410)]
public PlayAction PlayButtonAction { get; set; } = PlayAction.PlayScenes;
/// <summary>
/// Gets or sets a value indicating the number of game clients to launch when building and/or running cooked game.
/// </summary>
[DefaultValue(1), Range(1, 4)]
[EditorDisplay("Cook & Run"), EditorOrder(500)]
public int NumberOfGameClientsToLaunch = 1;
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
/// <summary>
/// Gets or sets the title font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(500), Tooltip("The title font for editor UI.")]
[EditorDisplay("Fonts"), EditorOrder(600), Tooltip("The title font for editor UI.")]
public FontReference TitleFont { get; set; } = new FontReference(DefaultFont, 18);
/// <summary>
/// Gets or sets the large font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(510), Tooltip("The large font for editor UI.")]
[EditorDisplay("Fonts"), EditorOrder(610), Tooltip("The large font for editor UI.")]
public FontReference LargeFont { get; set; } = new FontReference(DefaultFont, 14);
/// <summary>
/// Gets or sets the medium font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(520), Tooltip("The medium font for editor UI.")]
[EditorDisplay("Fonts"), EditorOrder(620), Tooltip("The medium font for editor UI.")]
public FontReference MediumFont { get; set; } = new FontReference(DefaultFont, 9);
/// <summary>
/// Gets or sets the small font for editor UI.
/// </summary>
[EditorDisplay("Fonts"), EditorOrder(530), Tooltip("The small font for editor UI.")]
[EditorDisplay("Fonts"), EditorOrder(630), Tooltip("The small font for editor UI.")]
public FontReference SmallFont { get; set; } = new FontReference(DefaultFont, 9);
}
}

View File

@@ -59,5 +59,12 @@ namespace FlaxEditor.Options
[DefaultValue(false)]
[EditorDisplay("Defaults"), EditorOrder(150), Tooltip("Invert the panning direction for the viewport camera.")]
public bool DefaultInvertPanning { get; set; } = false;
/// <summary>
/// Scales editor viewport grid.
/// </summary>
[DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)]
[EditorDisplay("Defaults"), EditorOrder(160), Tooltip("Scales editor viewport grid.")]
public float ViewportGridScale { get; set; } = 50.0f;
}
}

View File

@@ -292,7 +292,6 @@ void FoliageTools::Paint(Foliage* foliage, Span<int32> foliageTypesIndices, cons
{
PROFILE_CPU_NAMED("Place Instances");
Matrix matrix;
FoliageInstance instance;
Quaternion tmp;
Matrix world;

View File

@@ -80,6 +80,7 @@ namespace FlaxEditor.Windows
public LogGroup Group;
public LogEntryDescription Desc;
public SpriteHandle Icon;
public int LogCount = 1;
public LogEntry(DebugLogWindow window, ref LogEntryDescription desc)
: base(0, 0, 120, DefaultHeight)
@@ -137,7 +138,14 @@ namespace FlaxEditor.Windows
// Title
var textRect = new Rectangle(38, 2, clientRect.Width - 40, clientRect.Height - 10);
Render2D.PushClip(ref clientRect);
Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground);
if (LogCount == 1)
{
Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground);
}
else if (LogCount > 1)
{
Render2D.DrawText(style.FontMedium, $"{Desc.Title} ({LogCount})", textRect, style.Foreground);
}
Render2D.PopClip();
}
@@ -289,6 +297,7 @@ namespace FlaxEditor.Windows
private readonly List<LogEntry> _pendingEntries = new List<LogEntry>(32);
private readonly ToolStripButton _clearOnPlayButton;
private readonly ToolStripButton _collapseLogsButton;
private readonly ToolStripButton _pauseOnErrorButton;
private readonly ToolStripButton[] _groupButtons = new ToolStripButton[3];
@@ -316,6 +325,7 @@ namespace FlaxEditor.Windows
};
toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries");
_clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play").SetAutoCheck(true).SetChecked(true).LinkTooltip("Clears all log entries on enter playmode");
_collapseLogsButton = (ToolStripButton)toolstrip.AddButton("Collapse").SetAutoCheck(true).SetChecked(true).LinkTooltip("Collapses similar logs.");
_pauseOnErrorButton = (ToolStripButton)toolstrip.AddButton("Pause on Error").SetAutoCheck(true).LinkTooltip("Performs auto pause on error");
toolstrip.AddSeparator();
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides error messages");
@@ -612,6 +622,30 @@ namespace FlaxEditor.Windows
var top = _entriesPanel.Children.Count != 0 ? _entriesPanel.Children[_entriesPanel.Children.Count - 1].Bottom + spacing : margin.Top;
for (int i = 0; i < _pendingEntries.Count; i++)
{
if (_collapseLogsButton.Checked)
{
bool logExists = false;
foreach (var child in _entriesPanel.Children)
{
if (child is LogEntry entry)
{
var pendingEntry = _pendingEntries[i];
if (string.Equals(entry.Desc.Title, pendingEntry.Desc.Title, StringComparison.Ordinal) &&
string.Equals(entry.Desc.LocationFile, pendingEntry.Desc.LocationFile, StringComparison.Ordinal) &&
entry.Desc.Level == pendingEntry.Desc.Level &&
string.Equals(entry.Desc.Description, pendingEntry.Desc.Description, StringComparison.Ordinal) &&
entry.Desc.LocationLine == pendingEntry.Desc.LocationLine)
{
entry.LogCount += 1;
newEntry = entry;
logExists = true;
break;
}
}
}
if (logExists)
continue;
}
newEntry = _pendingEntries[i];
newEntry.Visible = _groupButtons[(int)newEntry.Group].Checked;
anyVisible |= newEntry.Visible;

View File

@@ -238,6 +238,7 @@ namespace FlaxEditor.Windows
{
[EditorDisplay(null, "arm64")]
ARM64,
[EditorDisplay(null, "x64")]
x64,
}
@@ -614,6 +615,18 @@ namespace FlaxEditor.Windows
_exitOnBuildEnd = true;
}
/// <summary>
/// Returns true if can build for the given platform (both supported and available).
/// </summary>
/// <param name="platformType">The platform.</param>
/// <returns>True if can build, otherwise false.</returns>
public bool CanBuild(PlatformType platformType)
{
if (_buildTabProxy.PerPlatformOptions.TryGetValue(platformType, out var platform))
return platform.IsAvailable && platform.IsSupported;
return false;
}
/// <summary>
/// Builds all the targets from the given preset.
/// </summary>
@@ -659,16 +672,24 @@ namespace FlaxEditor.Windows
{
Editor.Log("Building and running");
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration);
_buildingQueue.Enqueue(new QueueItem
var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch;
for (int i = 0; i < numberOfClients; i++)
{
Target = new BuildTarget
var buildOptions = BuildOptions.AutoRun;
if (i > 0)
buildOptions |= BuildOptions.NoCook;
_buildingQueue.Enqueue(new QueueItem
{
Output = _buildTabProxy.PerPlatformOptions[platform].Output,
Platform = buildPlatform,
Mode = buildConfiguration,
},
Options = BuildOptions.AutoRun,
});
Target = new BuildTarget
{
Output = _buildTabProxy.PerPlatformOptions[platform].Output,
Platform = buildPlatform,
Mode = buildConfiguration,
},
Options = buildOptions,
});
}
}
/// <summary>
@@ -678,16 +699,20 @@ namespace FlaxEditor.Windows
{
Editor.Log("Running cooked build");
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration);
_buildingQueue.Enqueue(new QueueItem
var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch;
for (int i = 0; i < numberOfClients; i++)
{
Target = new BuildTarget
_buildingQueue.Enqueue(new QueueItem
{
Output = _buildTabProxy.PerPlatformOptions[platform].Output,
Platform = buildPlatform,
Mode = buildConfiguration,
},
Options = BuildOptions.AutoRun | BuildOptions.NoCook,
});
Target = new BuildTarget
{
Output = _buildTabProxy.PerPlatformOptions[platform].Output,
Platform = buildPlatform,
Mode = buildConfiguration,
},
Options = BuildOptions.AutoRun | BuildOptions.NoCook,
});
}
}
private void BuildTarget()