- move play logic to simulation module - update play hotkey to call play game/scene delegate function - link hotkey tooltip text to inputOptions
1081 lines
38 KiB
C#
1081 lines
38 KiB
C#
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Xml;
|
|
using FlaxEditor.GUI.ContextMenu;
|
|
using FlaxEditor.GUI.Input;
|
|
using FlaxEditor.Options;
|
|
using FlaxEngine;
|
|
using FlaxEngine.GUI;
|
|
using FlaxEngine.Json;
|
|
|
|
namespace FlaxEditor.Windows
|
|
{
|
|
/// <summary>
|
|
/// Provides in-editor play mode simulation.
|
|
/// </summary>
|
|
/// <seealso cref="FlaxEditor.Windows.EditorWindow" />
|
|
public class GameWindow : EditorWindow
|
|
{
|
|
private readonly RenderOutputControl _viewport;
|
|
private readonly GameRoot _guiRoot;
|
|
private bool _showGUI = true;
|
|
private bool _showDebugDraw = false;
|
|
private bool _isMaximized = false, _isUnlockingMouse = false;
|
|
private bool _cursorVisible = true;
|
|
private float _gameStartTime;
|
|
private GUI.Docking.DockState _maximizeRestoreDockState;
|
|
private GUI.Docking.DockPanel _maximizeRestoreDockTo;
|
|
private CursorLockMode _cursorLockMode = CursorLockMode.None;
|
|
|
|
// Viewport scaling variables
|
|
private List<ViewportScaleOptions> _defaultViewportScaling = new List<ViewportScaleOptions>();
|
|
private List<ViewportScaleOptions> _customViewportScaling = new List<ViewportScaleOptions>();
|
|
private float _viewportAspectRatio = 1;
|
|
private float _windowAspectRatio = 1;
|
|
private bool _useAspect = false;
|
|
private bool _freeAspect = true;
|
|
|
|
/// <summary>
|
|
/// Gets the viewport.
|
|
/// </summary>
|
|
public RenderOutputControl Viewport => _viewport;
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether show game GUI in the view or keep it hidden.
|
|
/// </summary>
|
|
public bool ShowGUI
|
|
{
|
|
get => _showGUI;
|
|
set
|
|
{
|
|
if (value != _showGUI)
|
|
{
|
|
_showGUI = value;
|
|
_guiRoot.Visible = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether show Debug Draw shapes in the view or keep it hidden.
|
|
/// </summary>
|
|
public bool ShowDebugDraw
|
|
{
|
|
get => _showDebugDraw;
|
|
set => _showDebugDraw = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether game window is maximized (only in play mode).
|
|
/// </summary>
|
|
private bool IsMaximized
|
|
{
|
|
get => _isMaximized;
|
|
set
|
|
{
|
|
if (_isMaximized == value)
|
|
return;
|
|
_isMaximized = value;
|
|
var rootWindow = RootWindow;
|
|
if (value)
|
|
{
|
|
// Maximize
|
|
_maximizeRestoreDockTo = _dockedTo;
|
|
_maximizeRestoreDockState = _dockedTo.TryGetDockState(out _);
|
|
if (_maximizeRestoreDockState != GUI.Docking.DockState.Float)
|
|
{
|
|
var monitorBounds = Platform.GetMonitorBounds(PointToScreen(Size * 0.5f));
|
|
ShowFloating(monitorBounds.Location + new Float2(200, 200), Float2.Zero, WindowStartPosition.Manual);
|
|
rootWindow = RootWindow;
|
|
}
|
|
if (rootWindow != null && !rootWindow.IsMaximized)
|
|
rootWindow.Maximize();
|
|
}
|
|
else
|
|
{
|
|
// Restore
|
|
if (rootWindow != null)
|
|
rootWindow.Restore();
|
|
if (_maximizeRestoreDockTo != null && _maximizeRestoreDockTo.IsDisposing)
|
|
_maximizeRestoreDockTo = null;
|
|
Show(_maximizeRestoreDockState, _maximizeRestoreDockTo);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.
|
|
/// </summary>
|
|
public bool CenterMouseOnFocus { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether auto-focus game window on play mode start.
|
|
/// </summary>
|
|
public bool FocusOnPlay { get; set; }
|
|
|
|
private enum ViewportScaleType
|
|
{
|
|
Resolution = 0,
|
|
Aspect = 1,
|
|
}
|
|
|
|
private class ViewportScaleOptions
|
|
{
|
|
/// <summary>
|
|
/// The name.
|
|
/// </summary>
|
|
public string Label;
|
|
|
|
/// <summary>
|
|
/// The Type of scaling to do.
|
|
/// </summary>
|
|
public ViewportScaleType ScaleType;
|
|
|
|
/// <summary>
|
|
/// The width and height to scale by.
|
|
/// </summary>
|
|
public Int2 Size;
|
|
|
|
/// <summary>
|
|
/// If the scaling is active.
|
|
/// </summary>
|
|
public bool Active;
|
|
}
|
|
|
|
private class GameRoot : ContainerControl
|
|
{
|
|
public bool EnableEvents => !Time.GamePaused;
|
|
|
|
public override bool OnCharInput(char c)
|
|
{
|
|
if (!EnableEvents)
|
|
return false;
|
|
|
|
return base.OnCharInput(c);
|
|
}
|
|
|
|
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
|
{
|
|
if (!EnableEvents)
|
|
return DragDropEffect.None;
|
|
|
|
return base.OnDragDrop(ref location, data);
|
|
}
|
|
|
|
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
|
{
|
|
if (!EnableEvents)
|
|
return DragDropEffect.None;
|
|
|
|
return base.OnDragEnter(ref location, data);
|
|
}
|
|
|
|
public override void OnDragLeave()
|
|
{
|
|
if (!EnableEvents)
|
|
return;
|
|
|
|
base.OnDragLeave();
|
|
}
|
|
|
|
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
|
{
|
|
if (!EnableEvents)
|
|
return DragDropEffect.None;
|
|
|
|
return base.OnDragMove(ref location, data);
|
|
}
|
|
|
|
public override bool OnKeyDown(KeyboardKeys key)
|
|
{
|
|
if (!EnableEvents)
|
|
return false;
|
|
|
|
return base.OnKeyDown(key);
|
|
}
|
|
|
|
public override void OnKeyUp(KeyboardKeys key)
|
|
{
|
|
if (!EnableEvents)
|
|
return;
|
|
|
|
base.OnKeyUp(key);
|
|
}
|
|
|
|
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
|
{
|
|
if (!EnableEvents)
|
|
return false;
|
|
|
|
return base.OnMouseDoubleClick(location, button);
|
|
}
|
|
|
|
public override bool OnMouseDown(Float2 location, MouseButton button)
|
|
{
|
|
if (!EnableEvents)
|
|
return false;
|
|
|
|
return base.OnMouseDown(location, button);
|
|
}
|
|
|
|
public override void OnMouseEnter(Float2 location)
|
|
{
|
|
if (!EnableEvents)
|
|
return;
|
|
|
|
base.OnMouseEnter(location);
|
|
}
|
|
|
|
public override void OnMouseLeave()
|
|
{
|
|
if (!EnableEvents)
|
|
return;
|
|
|
|
base.OnMouseLeave();
|
|
}
|
|
|
|
public override void OnMouseMove(Float2 location)
|
|
{
|
|
if (!EnableEvents)
|
|
return;
|
|
|
|
base.OnMouseMove(location);
|
|
}
|
|
|
|
public override bool OnMouseUp(Float2 location, MouseButton button)
|
|
{
|
|
if (!EnableEvents)
|
|
return false;
|
|
|
|
return base.OnMouseUp(location, button);
|
|
}
|
|
|
|
public override bool OnMouseWheel(Float2 location, float delta)
|
|
{
|
|
if (!EnableEvents)
|
|
return false;
|
|
|
|
return base.OnMouseWheel(location, delta);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="GameWindow"/> class.
|
|
/// </summary>
|
|
/// <param name="editor">The editor.</param>
|
|
public GameWindow(Editor editor)
|
|
: base(editor, true, ScrollBars.None)
|
|
{
|
|
Title = "Game";
|
|
AutoFocus = true;
|
|
|
|
var task = MainRenderTask.Instance;
|
|
|
|
// Setup viewport
|
|
_viewport = new RenderOutputControl(task)
|
|
{
|
|
AnchorPreset = AnchorPresets.StretchAll,
|
|
Offsets = Margin.Zero,
|
|
AutoFocus = false,
|
|
Parent = this
|
|
};
|
|
task.PostRender += OnPostRender;
|
|
|
|
// Override the game GUI root
|
|
_guiRoot = new GameRoot
|
|
{
|
|
AnchorPreset = AnchorPresets.StretchAll,
|
|
Offsets = Margin.Zero,
|
|
//Visible = false,
|
|
AutoFocus = false,
|
|
Parent = _viewport
|
|
};
|
|
RootControl.GameRoot = _guiRoot;
|
|
|
|
SizeChanged += control => { ResizeViewport(); };
|
|
|
|
Editor.StateMachine.PlayingState.SceneDuplicating += PlayingStateOnSceneDuplicating;
|
|
Editor.StateMachine.PlayingState.SceneRestored += PlayingStateOnSceneRestored;
|
|
|
|
// 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)
|
|
{
|
|
if (v == null)
|
|
return;
|
|
|
|
if (v.Size.Y <= 0 || v.Size.X <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.Equals(v.Label, "Free Aspect") && v.Size == new Int2(1, 1))
|
|
{
|
|
_freeAspect = true;
|
|
_useAspect = true;
|
|
}
|
|
else
|
|
{
|
|
switch (v.ScaleType)
|
|
{
|
|
case ViewportScaleType.Aspect:
|
|
_useAspect = true;
|
|
_freeAspect = false;
|
|
break;
|
|
case ViewportScaleType.Resolution:
|
|
_useAspect = false;
|
|
_freeAspect = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_viewportAspectRatio = (float)v.Size.X / v.Size.Y;
|
|
|
|
if (!_freeAspect)
|
|
{
|
|
if (!_useAspect)
|
|
{
|
|
_viewport.KeepAspectRatio = true;
|
|
_viewport.CustomResolution = new Int2(v.Size.X, v.Size.Y);
|
|
}
|
|
else
|
|
{
|
|
_viewport.CustomResolution = new Int2?();
|
|
_viewport.KeepAspectRatio = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_viewport.CustomResolution = new Int2?();
|
|
_viewport.KeepAspectRatio = false;
|
|
}
|
|
|
|
ResizeViewport();
|
|
}
|
|
|
|
private void ResizeViewport()
|
|
{
|
|
if (!_freeAspect)
|
|
{
|
|
_windowAspectRatio = Width / Height;
|
|
}
|
|
else
|
|
{
|
|
_windowAspectRatio = 1;
|
|
}
|
|
|
|
var scaleWidth = _viewportAspectRatio / _windowAspectRatio;
|
|
var scaleHeight = _windowAspectRatio / _viewportAspectRatio;
|
|
|
|
if (scaleHeight < 1)
|
|
{
|
|
_viewport.Bounds = new Rectangle(0, Height * (1 - scaleHeight) / 2, Width, Height * scaleHeight);
|
|
}
|
|
else
|
|
{
|
|
_viewport.Bounds = new Rectangle(Width * (1 - scaleWidth) / 2, 0, Width * scaleWidth, Height);
|
|
}
|
|
PerformLayout();
|
|
}
|
|
|
|
private void OnPostRender(GPUContext context, ref RenderContext renderContext)
|
|
{
|
|
// Debug Draw shapes
|
|
if (_showDebugDraw)
|
|
{
|
|
var task = _viewport.Task;
|
|
|
|
// Draw actors debug shapes manually if editor viewport is hidden (game viewport task is always rendered before editor viewports)
|
|
var editWindowViewport = Editor.Windows.EditWin.Viewport;
|
|
if (editWindowViewport.Task.LastUsedFrame != Engine.FrameCount)
|
|
{
|
|
var drawDebugData = editWindowViewport.DebugDrawData;
|
|
drawDebugData.Clear();
|
|
var selectedParents = editWindowViewport.TransformGizmo.SelectedParents;
|
|
if (selectedParents.Count > 0)
|
|
{
|
|
for (int i = 0; i < selectedParents.Count; i++)
|
|
{
|
|
if (selectedParents[i].IsActiveInHierarchy)
|
|
selectedParents[i].OnDebugDraw(drawDebugData);
|
|
}
|
|
}
|
|
unsafe
|
|
{
|
|
fixed (IntPtr* actors = drawDebugData.ActorsPtrs)
|
|
{
|
|
DebugDraw.DrawActors(new IntPtr(actors), drawDebugData.ActorsCount, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
DebugDraw.Draw(ref renderContext, task.OutputView);
|
|
}
|
|
}
|
|
|
|
private void OnOptionsChanged(EditorOptions options)
|
|
{
|
|
CenterMouseOnFocus = options.Interface.CenterMouseOnGameWinFocus;
|
|
FocusOnPlay = options.Interface.FocusGameWinOnPlay;
|
|
}
|
|
|
|
private void PlayingStateOnSceneDuplicating()
|
|
{
|
|
// Remove remaining GUI controls so loaded scene can add own GUI
|
|
//_guiRoot.DisposeChildren();
|
|
|
|
// Show GUI
|
|
//_guiRoot.Visible = _showGUI;
|
|
}
|
|
|
|
private void PlayingStateOnSceneRestored()
|
|
{
|
|
// Remove remaining GUI controls so loaded scene can add own GUI
|
|
//_guiRoot.DisposeChildren();
|
|
|
|
// Hide GUI
|
|
//_guiRoot.Visible = false;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override bool CanOpenContentFinder => !Editor.IsPlayMode;
|
|
|
|
/// <inheritdoc />
|
|
protected override bool CanUseNavigation => false;
|
|
|
|
/// <inheritdoc />
|
|
public override void OnPlayBegin()
|
|
{
|
|
_gameStartTime = Time.UnscaledGameTime;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnPlayEnd()
|
|
{
|
|
IsMaximized = false;
|
|
Cursor = CursorType.Default;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnShowContextMenu(ContextMenu menu)
|
|
{
|
|
base.OnShowContextMenu(menu);
|
|
|
|
// Viewport Brightness
|
|
{
|
|
var brightness = menu.AddButton("Viewport Brightness");
|
|
brightness.CloseMenuOnClick = false;
|
|
var brightnessValue = new FloatValueBox(_viewport.Brightness, 140, 2, 50.0f, 0.001f, 10.0f, 0.001f)
|
|
{
|
|
Parent = brightness
|
|
};
|
|
brightnessValue.ValueChanged += () => _viewport.Brightness = brightnessValue.Value;
|
|
}
|
|
|
|
// Viewport Resolution
|
|
{
|
|
var resolution = menu.AddButton("Viewport Resolution");
|
|
resolution.CloseMenuOnClick = false;
|
|
var resolutionValue = new FloatValueBox(_viewport.ResolutionScale, 140, 2, 50.0f, 0.1f, 4.0f, 0.001f)
|
|
{
|
|
Parent = resolution
|
|
};
|
|
resolutionValue.ValueChanged += () => _viewport.ResolutionScale = resolutionValue.Value;
|
|
}
|
|
|
|
// Viewport aspect ratio
|
|
{
|
|
// Create default scaling options if they dont exist from deserialization.
|
|
if (_defaultViewportScaling.Count == 0)
|
|
{
|
|
_defaultViewportScaling.Add(new ViewportScaleOptions
|
|
{
|
|
Label = "Free Aspect",
|
|
ScaleType = ViewportScaleType.Aspect,
|
|
Size = new Int2(1, 1),
|
|
Active = true,
|
|
});
|
|
_defaultViewportScaling.Add(new ViewportScaleOptions
|
|
{
|
|
Label = "16:9 Aspect",
|
|
ScaleType = ViewportScaleType.Aspect,
|
|
Size = new Int2(16, 9),
|
|
Active = false,
|
|
});
|
|
_defaultViewportScaling.Add(new ViewportScaleOptions
|
|
{
|
|
Label = "16:10 Aspect",
|
|
ScaleType = ViewportScaleType.Aspect,
|
|
Size = new Int2(16, 10),
|
|
Active = false,
|
|
});
|
|
_defaultViewportScaling.Add(new ViewportScaleOptions
|
|
{
|
|
Label = "1920x1080 Resolution",
|
|
ScaleType = ViewportScaleType.Resolution,
|
|
Size = new Int2(1920, 1080),
|
|
Active = false,
|
|
});
|
|
_defaultViewportScaling.Add(new ViewportScaleOptions
|
|
{
|
|
Label = "2560x1440 Resolution",
|
|
ScaleType = ViewportScaleType.Resolution,
|
|
Size = new Int2(2560, 1440),
|
|
Active = false,
|
|
});
|
|
}
|
|
|
|
var vsMenu = menu.AddChildMenu("Viewport Size").ContextMenu;
|
|
|
|
CreateViewportSizingContextMenu(vsMenu);
|
|
}
|
|
|
|
// Take Screenshot
|
|
{
|
|
var takeScreenshot = menu.AddButton("Take Screenshot");
|
|
takeScreenshot.Clicked += TakeScreenshot;
|
|
}
|
|
|
|
menu.AddSeparator();
|
|
|
|
// Show GUI
|
|
{
|
|
var button = menu.AddButton("Show GUI");
|
|
button.CloseMenuOnClick = false;
|
|
var checkbox = new CheckBox(140, 2, ShowGUI) { Parent = button };
|
|
checkbox.StateChanged += x => ShowGUI = x.Checked;
|
|
}
|
|
|
|
// Show Debug Draw
|
|
{
|
|
var button = menu.AddButton("Show Debug Draw");
|
|
button.CloseMenuOnClick = false;
|
|
var checkbox = new CheckBox(140, 2, ShowDebugDraw) { Parent = button };
|
|
checkbox.StateChanged += x => ShowDebugDraw = x.Checked;
|
|
}
|
|
|
|
menu.MinimumWidth = 200;
|
|
menu.AddSeparator();
|
|
}
|
|
|
|
private void CreateViewportSizingContextMenu(ContextMenu vsMenu)
|
|
{
|
|
// Add default viewport sizing options
|
|
for (int i = 0; i < _defaultViewportScaling.Count; i++)
|
|
{
|
|
var viewportScale = _defaultViewportScaling[i];
|
|
var button = vsMenu.AddButton(viewportScale.Label);
|
|
button.CloseMenuOnClick = false;
|
|
button.Icon = viewportScale.Active ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
|
button.Tag = viewportScale;
|
|
if (viewportScale.Active)
|
|
ChangeViewportRatio(viewportScale);
|
|
|
|
button.Clicked += () =>
|
|
{
|
|
if (button.Tag == null)
|
|
return;
|
|
|
|
// Reset selected icon on all buttons
|
|
foreach (var child in vsMenu.Items)
|
|
{
|
|
if (child is ContextMenuButton cmb && cmb.Tag is ViewportScaleOptions v)
|
|
{
|
|
if (cmb == button)
|
|
{
|
|
v.Active = true;
|
|
button.Icon = Style.Current.CheckBoxTick;
|
|
ChangeViewportRatio(v);
|
|
}
|
|
else if (v.Active)
|
|
{
|
|
cmb.Icon = SpriteHandle.Invalid;
|
|
v.Active = false;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
if (_defaultViewportScaling.Count != 0)
|
|
vsMenu.AddSeparator();
|
|
|
|
// Add custom viewport options
|
|
for (int i = 0; i < _customViewportScaling.Count; i++)
|
|
{
|
|
var viewportScale = _customViewportScaling[i];
|
|
var childCM = vsMenu.AddChildMenu(viewportScale.Label);
|
|
childCM.CloseMenuOnClick = false;
|
|
childCM.Icon = viewportScale.Active ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
|
|
childCM.Tag = viewportScale;
|
|
if (viewportScale.Active)
|
|
ChangeViewportRatio(viewportScale);
|
|
|
|
var applyButton = childCM.ContextMenu.AddButton("Apply");
|
|
applyButton.Tag = childCM.Tag = viewportScale;
|
|
applyButton.CloseMenuOnClick = false;
|
|
applyButton.Clicked += () =>
|
|
{
|
|
if (childCM.Tag == null)
|
|
return;
|
|
|
|
// Reset selected icon on all buttons
|
|
foreach (var child in vsMenu.Items)
|
|
{
|
|
if (child is ContextMenuButton cmb && cmb.Tag is ViewportScaleOptions v)
|
|
{
|
|
if (child == childCM)
|
|
{
|
|
v.Active = true;
|
|
childCM.Icon = Style.Current.CheckBoxTick;
|
|
ChangeViewportRatio(v);
|
|
}
|
|
else if (v.Active)
|
|
{
|
|
cmb.Icon = SpriteHandle.Invalid;
|
|
v.Active = false;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var deleteButton = childCM.ContextMenu.AddButton("Delete");
|
|
deleteButton.CloseMenuOnClick = false;
|
|
deleteButton.Clicked += () =>
|
|
{
|
|
if (childCM.Tag == null)
|
|
return;
|
|
|
|
var v = (ViewportScaleOptions)childCM.Tag;
|
|
if (v.Active)
|
|
{
|
|
v.Active = false;
|
|
_defaultViewportScaling[0].Active = true;
|
|
ChangeViewportRatio(_defaultViewportScaling[0]);
|
|
}
|
|
_customViewportScaling.Remove(v);
|
|
vsMenu.DisposeAllItems();
|
|
CreateViewportSizingContextMenu(vsMenu);
|
|
vsMenu.PerformLayout();
|
|
};
|
|
}
|
|
if (_customViewportScaling.Count != 0)
|
|
vsMenu.AddSeparator();
|
|
|
|
// Add button
|
|
var add = vsMenu.AddButton("Add...");
|
|
add.CloseMenuOnClick = false;
|
|
add.Clicked += () =>
|
|
{
|
|
var popup = new ContextMenuBase
|
|
{
|
|
Size = new Float2(230, 125),
|
|
ClipChildren = false,
|
|
CullChildren = false,
|
|
};
|
|
popup.Show(add, new Float2(add.Width, 0));
|
|
|
|
var nameLabel = new Label
|
|
{
|
|
Parent = popup,
|
|
AnchorPreset = AnchorPresets.TopLeft,
|
|
Text = "Name",
|
|
HorizontalAlignment = TextAlignment.Near,
|
|
};
|
|
nameLabel.LocalX += 10;
|
|
nameLabel.LocalY += 10;
|
|
|
|
var nameTextBox = new TextBox
|
|
{
|
|
Parent = popup,
|
|
AnchorPreset = AnchorPresets.TopLeft,
|
|
IsMultiline = false,
|
|
};
|
|
nameTextBox.LocalX += 100;
|
|
nameTextBox.LocalY += 10;
|
|
|
|
var typeLabel = new Label
|
|
{
|
|
Parent = popup,
|
|
AnchorPreset = AnchorPresets.TopLeft,
|
|
Text = "Type",
|
|
HorizontalAlignment = TextAlignment.Near,
|
|
};
|
|
typeLabel.LocalX += 10;
|
|
typeLabel.LocalY += 35;
|
|
|
|
var typeDropdown = new Dropdown
|
|
{
|
|
Parent = popup,
|
|
AnchorPreset = AnchorPresets.TopLeft,
|
|
Items = { "Aspect", "Resolution" },
|
|
SelectedItem = "Aspect",
|
|
Width = nameTextBox.Width
|
|
};
|
|
typeDropdown.LocalY += 35;
|
|
typeDropdown.LocalX += 100;
|
|
|
|
var whLabel = new Label
|
|
{
|
|
Parent = popup,
|
|
AnchorPreset = AnchorPresets.TopLeft,
|
|
Text = "Width & Height",
|
|
HorizontalAlignment = TextAlignment.Near,
|
|
};
|
|
whLabel.LocalX += 10;
|
|
whLabel.LocalY += 60;
|
|
|
|
var wValue = new IntValueBox(16)
|
|
{
|
|
Parent = popup,
|
|
AnchorPreset = AnchorPresets.TopLeft,
|
|
MinValue = 1,
|
|
Width = 55,
|
|
};
|
|
wValue.LocalY += 60;
|
|
wValue.LocalX += 100;
|
|
|
|
var hValue = new IntValueBox(9)
|
|
{
|
|
Parent = popup,
|
|
AnchorPreset = AnchorPresets.TopLeft,
|
|
MinValue = 1,
|
|
Width = 55,
|
|
};
|
|
hValue.LocalY += 60;
|
|
hValue.LocalX += 165;
|
|
|
|
var submitButton = new Button
|
|
{
|
|
Parent = popup,
|
|
AnchorPreset = AnchorPresets.TopLeft,
|
|
Text = "Submit",
|
|
Width = 70,
|
|
};
|
|
submitButton.LocalX += 40;
|
|
submitButton.LocalY += 90;
|
|
|
|
submitButton.Clicked += () =>
|
|
{
|
|
Enum.TryParse(typeDropdown.SelectedItem, out ViewportScaleType type);
|
|
|
|
var combineString = type == ViewportScaleType.Aspect ? ":" : "x";
|
|
var name = nameTextBox.Text + " (" + wValue.Value + combineString + hValue.Value + ") " + typeDropdown.SelectedItem;
|
|
|
|
var newViewportOption = new ViewportScaleOptions
|
|
{
|
|
ScaleType = type,
|
|
Label = name,
|
|
Size = new Int2(wValue.Value, hValue.Value),
|
|
};
|
|
|
|
_customViewportScaling.Add(newViewportOption);
|
|
vsMenu.DisposeAllItems();
|
|
CreateViewportSizingContextMenu(vsMenu);
|
|
vsMenu.PerformLayout();
|
|
};
|
|
|
|
var cancelButton = new Button
|
|
{
|
|
Parent = popup,
|
|
AnchorPreset = AnchorPresets.TopLeft,
|
|
Text = "Cancel",
|
|
Width = 70,
|
|
};
|
|
cancelButton.LocalX += 120;
|
|
cancelButton.LocalY += 90;
|
|
|
|
cancelButton.Clicked += () =>
|
|
{
|
|
nameTextBox.Clear();
|
|
typeDropdown.SelectedItem = "Aspect";
|
|
hValue.Value = 9;
|
|
wValue.Value = 16;
|
|
popup.Hide();
|
|
};
|
|
};
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Draw()
|
|
{
|
|
base.Draw();
|
|
|
|
if (Camera.MainCamera == null)
|
|
{
|
|
var style = Style.Current;
|
|
Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
|
|
}
|
|
|
|
// Selected UI controls outline
|
|
bool drawAnySelectedControl = false;
|
|
// TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed)
|
|
var selection = Editor.SceneEditing.Selection;
|
|
for (var i = 0; i < selection.Count; i++)
|
|
{
|
|
if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null)
|
|
{
|
|
if (!drawAnySelectedControl)
|
|
{
|
|
drawAnySelectedControl = true;
|
|
Render2D.PushTransform(ref _viewport._cachedTransform);
|
|
}
|
|
var options = Editor.Options.Options.Visual;
|
|
var control = controlActor.Control;
|
|
var bounds = control.EditorBounds;
|
|
bounds = Rectangle.FromPoints(control.PointToParent(_viewport, bounds.Location), control.PointToParent(_viewport, bounds.Size));
|
|
Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize);
|
|
}
|
|
}
|
|
if (drawAnySelectedControl)
|
|
Render2D.PopTransform();
|
|
|
|
// Play mode hints and overlay
|
|
if (Editor.StateMachine.IsPlayMode)
|
|
{
|
|
var style = Style.Current;
|
|
float time = Time.UnscaledGameTime - _gameStartTime;
|
|
float timeout = 3.0f;
|
|
float fadeOutTime = 1.0f;
|
|
float animTime = time - timeout;
|
|
if (animTime < 0)
|
|
{
|
|
var alpha = Mathf.Saturate(-animTime / fadeOutTime);
|
|
var rect = new Rectangle(new Float2(6), Size - 12);
|
|
var text = "Press Shift+F11 to unlock the mouse";
|
|
Render2D.DrawText(style.FontSmall, text, rect + new Float2(1.0f), style.Background * alpha, TextAlignment.Near, TextAlignment.Far);
|
|
Render2D.DrawText(style.FontSmall, text, rect, style.Foreground * alpha, TextAlignment.Near, TextAlignment.Far);
|
|
}
|
|
|
|
timeout = 1.0f;
|
|
fadeOutTime = 0.6f;
|
|
animTime = time - timeout;
|
|
if (animTime < 0)
|
|
{
|
|
float alpha = Mathf.Saturate(-animTime / fadeOutTime);
|
|
Render2D.DrawRectangle(new Rectangle(new Float2(4), Size - 8), Color.Orange * alpha);
|
|
}
|
|
|
|
// Add overlay during debugger breakpoint hang
|
|
if (Editor.Simulation.IsDuringBreakpointHang)
|
|
{
|
|
var bounds = new Rectangle(Float2.Zero, Size);
|
|
Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f));
|
|
Render2D.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlocks the mouse if game window is focused during play mode.
|
|
/// </summary>
|
|
public void UnlockMouseInPlay()
|
|
{
|
|
if (Editor.StateMachine.IsPlayMode)
|
|
{
|
|
// Cache cursor
|
|
_cursorVisible = Screen.CursorVisible;
|
|
_cursorLockMode = Screen.CursorLock;
|
|
Screen.CursorVisible = true;
|
|
if (Screen.CursorLock == CursorLockMode.Clipped)
|
|
Screen.CursorLock = CursorLockMode.None;
|
|
|
|
// Defocus
|
|
_isUnlockingMouse = true;
|
|
Focus(null);
|
|
_isUnlockingMouse = false;
|
|
Editor.Windows.MainWindow.Focus();
|
|
if (Editor.Windows.PropertiesWin.IsDocked)
|
|
Editor.Windows.PropertiesWin.Focus();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Focuses the game viewport. Shows the window if hidden or unselected.
|
|
/// </summary>
|
|
public void FocusGameViewport()
|
|
{
|
|
if (!IsDocked)
|
|
{
|
|
ShowFloating();
|
|
}
|
|
else if (!IsSelected)
|
|
{
|
|
SelectTab(false);
|
|
RootWindow?.Window?.Focus();
|
|
}
|
|
Focus();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the screenshot of the current viewport.
|
|
/// </summary>
|
|
public void TakeScreenshot()
|
|
{
|
|
Screenshot.Capture(_viewport.Task);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the screenshot of the current viewport.
|
|
/// </summary>
|
|
/// <param name="path">The output file path.</param>
|
|
public void TakeScreenshot(string path)
|
|
{
|
|
Screenshot.Capture(_viewport.Task, path);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool OnKeyDown(KeyboardKeys key)
|
|
{
|
|
switch (key)
|
|
{
|
|
case KeyboardKeys.F12:
|
|
Screenshot.Capture(string.Empty);
|
|
return true;
|
|
case KeyboardKeys.F11:
|
|
if (Root.GetKey(KeyboardKeys.Shift))
|
|
{
|
|
// Unlock mouse in game mode
|
|
UnlockMouseInPlay();
|
|
return true;
|
|
}
|
|
else if (Editor.IsPlayMode)
|
|
{
|
|
// Maximized game window toggle
|
|
IsMaximized = !IsMaximized;
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Prevent closing the game window tab during a play session
|
|
if (Editor.StateMachine.IsPlayMode && Editor.Options.Options.Input.CloseTab.Process(this, key))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return base.OnKeyDown(key);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnStartContainsFocus()
|
|
{
|
|
base.OnStartContainsFocus();
|
|
|
|
if (Editor.StateMachine.IsPlayMode && !Editor.StateMachine.PlayingState.IsPaused)
|
|
{
|
|
// Center mouse in play mode
|
|
if (CenterMouseOnFocus)
|
|
{
|
|
var center = PointToWindow(Size * 0.5f);
|
|
Root.MousePosition = center;
|
|
}
|
|
|
|
// Restore cursor
|
|
if (_cursorLockMode != CursorLockMode.None)
|
|
Screen.CursorLock = _cursorLockMode;
|
|
if (!_cursorVisible)
|
|
Screen.CursorVisible = false;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnEndContainsFocus()
|
|
{
|
|
base.OnEndContainsFocus();
|
|
|
|
if (!_isUnlockingMouse)
|
|
{
|
|
// Cache cursor
|
|
_cursorVisible = Screen.CursorVisible;
|
|
_cursorLockMode = Screen.CursorLock;
|
|
|
|
// Restore cursor visibility (could be hidden by the game)
|
|
if (!_cursorVisible)
|
|
Screen.CursorVisible = true;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override Control OnNavigate(NavDirection direction, Float2 location, Control caller, List<Control> visited)
|
|
{
|
|
// Block leaking UI navigation focus outside the game window
|
|
if (IsFocused && caller != this)
|
|
{
|
|
// Pick the first UI control if game UI is not focused yet
|
|
foreach (var child in _guiRoot.Children)
|
|
{
|
|
if (child.Visible)
|
|
return child.OnNavigate(direction, Float2.Zero, this, visited);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool OnMouseDown(Float2 location, MouseButton button)
|
|
{
|
|
var result = base.OnMouseDown(location, button);
|
|
|
|
// Catch user focus
|
|
if (!ContainsFocus)
|
|
Focus();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool UseLayoutData => true;
|
|
|
|
/// <inheritdoc />
|
|
public override void OnLayoutSerialize(XmlWriter writer)
|
|
{
|
|
writer.WriteAttributeString("ShowGUI", ShowGUI.ToString());
|
|
writer.WriteAttributeString("ShowDebugDraw", ShowDebugDraw.ToString());
|
|
writer.WriteAttributeString("DefaultViewportScaling", JsonSerializer.Serialize(_defaultViewportScaling));
|
|
writer.WriteAttributeString("CustomViewportScaling", JsonSerializer.Serialize(_customViewportScaling));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnLayoutDeserialize(XmlElement node)
|
|
{
|
|
if (bool.TryParse(node.GetAttribute("ShowGUI"), out bool value1))
|
|
ShowGUI = value1;
|
|
if (bool.TryParse(node.GetAttribute("ShowDebugDraw"), out value1))
|
|
ShowDebugDraw = value1;
|
|
if (node.HasAttribute("CustomViewportScaling"))
|
|
_customViewportScaling = JsonSerializer.Deserialize<List<ViewportScaleOptions>>(node.GetAttribute("CustomViewportScaling"));
|
|
|
|
for (int i = 0; i < _customViewportScaling.Count; i++)
|
|
{
|
|
if (_customViewportScaling[i].Active)
|
|
ChangeViewportRatio(_customViewportScaling[i]);
|
|
}
|
|
|
|
if (node.HasAttribute("DefaultViewportScaling"))
|
|
_defaultViewportScaling = JsonSerializer.Deserialize<List<ViewportScaleOptions>>(node.GetAttribute("DefaultViewportScaling"));
|
|
|
|
for (int i = 0; i < _defaultViewportScaling.Count; i++)
|
|
{
|
|
if (_defaultViewportScaling[i].Active)
|
|
ChangeViewportRatio(_defaultViewportScaling[i]);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnLayoutDeserialize()
|
|
{
|
|
ShowGUI = true;
|
|
ShowDebugDraw = false;
|
|
}
|
|
}
|
|
}
|