Files
FlaxEngine/Source/Editor/GUI/Docking/DockWindow.cs

527 lines
17 KiB
C#

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Xml;
using System.Globalization;
using FlaxEngine;
using FlaxEngine.Assertions;
using FlaxEngine.GUI;
using FlaxEditor.Options;
namespace FlaxEditor.GUI.Docking
{
/// <summary>
/// Dockable window UI control.
/// </summary>
/// <seealso cref="FlaxEngine.GUI.Panel" />
public class DockWindow : Panel
{
private string _title;
private Float2 _titleSize;
/// <summary>
/// The master panel.
/// </summary>
protected MasterDockPanel _masterPanel;
/// <summary>
/// The parent panel.
/// </summary>
protected DockPanel _dockedTo;
/// <summary>
/// Gets or sets a value indicating whether hide window on close.
/// </summary>
public bool HideOnClose { get; protected set; }
/// <summary>
/// Gets the master panel.
/// </summary>
public MasterDockPanel MasterPanel => _masterPanel;
/// <summary>
/// Gets the parent dock panel.
/// </summary>
public DockPanel ParentDockPanel
{
get => _dockedTo;
internal set { _dockedTo = value; }
}
/// <summary>
/// Gets a value indicating whether this window is docked.
/// </summary>
public bool IsDocked => _dockedTo != null;
/// <summary>
/// Gets a value indicating whether this window is selected.
/// </summary>
public bool IsSelected => _dockedTo?.SelectedTab == this;
/// <summary>
/// Gets a value indicating whether this window is hidden from the user (eg. not shown or hidden on closed).
/// </summary>
public bool IsHidden => !Visible || _dockedTo == null;
/// <summary>
/// Gets the default window size (in UI units, unscaled by DPI which is handled by windowing system).
/// </summary>
public virtual Float2 DefaultSize => new Float2(900, 580);
/// <summary>
/// Gets the serialization typename.
/// </summary>
public virtual string SerializationTypename => "::" + GetType().FullName;
/// <summary>
/// Gets or sets the window title.
/// </summary>
public string Title
{
get => _title;
set
{
_title = value;
// Invalidate cached title size
_titleSize = new Float2(-1);
PerformLayoutBeforeChildren();
// Check if is docked to the floating window and is selected so update window title
if (IsSelected && _dockedTo is FloatWindowDockPanel floatPanel)
{
floatPanel.Window.Title = Title;
}
}
}
/// <summary>
/// Gets or sets the window icon
/// </summary>
public SpriteHandle Icon { get; set; }
/// <summary>
/// Gets the size of the title.
/// </summary>
public Float2 TitleSize => _titleSize;
/// <summary>
/// The input actions collection to processed during user input.
/// </summary>
public InputActionsContainer InputActions = new InputActionsContainer();
/// <summary>
/// Initializes a new instance of the <see cref="DockWindow"/> class.
/// </summary>
/// <param name="masterPanel">The master docking panel.</param>
/// <param name="hideOnClose">True if hide window on closing, otherwise it will be destroyed.</param>
/// <param name="scrollBars">The scroll bars.</param>
public DockWindow(MasterDockPanel masterPanel, bool hideOnClose, ScrollBars scrollBars)
: base(scrollBars)
{
_masterPanel = masterPanel;
HideOnClose = hideOnClose;
AnchorPreset = AnchorPresets.StretchAll;
Offsets = Margin.Zero;
// Bind navigation shortcuts
InputActions.Add(options => options.CloseTab, () => Close(ClosingReason.User));
InputActions.Add(options => options.PreviousTab, () =>
{
if (_dockedTo != null)
{
var index = _dockedTo.SelectedTabIndex;
index = index == 0 ? _dockedTo.TabsCount - 1 : index - 1;
_dockedTo.SelectedTabIndex = index;
}
});
InputActions.Add(options => options.NextTab, () =>
{
if (_dockedTo != null)
{
var index = _dockedTo.SelectedTabIndex;
index = (index + 1) % _dockedTo.TabsCount;
_dockedTo.SelectedTabIndex = index;
}
});
// Link to the master panel
_masterPanel?.LinkWindow(this);
}
/// <summary>
/// Shows the window in a floating state.
/// </summary>
public void ShowFloating()
{
ShowFloating(Float2.Zero);
}
/// <summary>
/// Shows the window in a floating state.
/// </summary>
/// <param name="position">Window location.</param>
public void ShowFloating(WindowStartPosition position)
{
ShowFloating(Float2.Zero, position);
}
/// <summary>
/// Shows the window in a floating state.
/// </summary>
/// <param name="size">Window size, set <see cref="Float2.Zero"/> to use default.</param>
/// <param name="position">Window location.</param>
public void ShowFloating(Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent)
{
ShowFloating(new Float2(200, 200), size, position);
}
/// <summary>
/// Shows the window in a floating state.
/// </summary>
/// <param name="location">Window location.</param>
/// <param name="size">Window size, set <see cref="Float2.Zero"/> to use default.</param>
/// <param name="position">Window location.</param>
public void ShowFloating(Float2 location, Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent)
{
CreateFloating(location, size, position, true);
}
/// <summary>
/// Creates the window in a floating state.
/// </summary>
public void CreateFloating()
{
CreateFloating(Float2.Zero, Float2.Zero);
}
/// <summary>
/// Creates the window in a floating state.
/// </summary>
/// <param name="location">Window location.</param>
/// <param name="size">Window size, set <see cref="Float2.Zero"/> to use default.</param>
/// <param name="position">Window location.</param>
/// <param name="showWindow">Window visibility.</param>
public void CreateFloating(Float2 location, Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent, bool showWindow = false)
{
Undock();
// Create window
var winSize = size.LengthSquared > 4 ? size : DefaultSize;
var window = FloatWindowDockPanel.CreateFloatWindow(_masterPanel.Root, location, winSize, position, _title);
var windowGUI = window.GUI;
// Create dock panel for the window
var dockPanel = new FloatWindowDockPanel(_masterPanel, windowGUI);
dockPanel.DockWindowInternal(DockState.DockFill, this);
// Perform layout
Visible = true;
windowGUI.UnlockChildrenRecursive();
windowGUI.PerformLayout();
if (showWindow)
{
// Show
window.Show();
window.BringToFront();
window.Focus();
OnShow();
// Perform layout again
windowGUI.PerformLayout();
}
}
/// <summary>
/// Shows the window.
/// </summary>
/// <param name="state">Initial window state.</param>
/// <param name="toDock">Panel to dock to it.</param>
/// <param name="autoSelect">Only used if <paramref name="toDock"/> is set. If true the window will be selected after docking it.</param>
/// <param name="splitterValue">Only used if <paramref name="toDock"/> is set. The splitter value to use. If not specified, a default value will be used.</param>
public void Show(DockState state = DockState.Float, DockPanel toDock = null, bool autoSelect = true, float? splitterValue = null)
{
if (state == DockState.Hidden)
{
Hide();
}
else if (state == DockState.Float)
{
ShowFloating();
}
else
{
Visible = true;
// Undock first
Undock();
// Then dock
(toDock ?? _masterPanel).DockWindowInternal(state, this, autoSelect, splitterValue);
OnShow();
PerformLayout();
}
}
/// <summary>
/// Shows the window.
/// </summary>
/// <param name="state">Initial window state.</param>
/// <param name="toDock">Window to dock to it.</param>
public void Show(DockState state, DockWindow toDock)
{
Show(state, toDock?.ParentDockPanel);
}
/// <summary>
/// Focuses or shows the window.
/// </summary>
public void FocusOrShow()
{
FocusOrShow((DockState)Editor.Instance.Options.Options.Interface.NewWindowLocation);
}
/// <summary>
/// Focuses or shows the window.
/// </summary>
/// <param name="state">The state.</param>
public void FocusOrShow(DockState state)
{
if (Visible)
{
SelectTab();
Focus();
}
else
{
Show(state);
}
}
/// <summary>
/// Hides the window.
/// </summary>
public void Hide()
{
// Undock
Undock();
Visible = false;
// Ensure that dock panel has no parent
Assert.IsFalse(HasParent);
}
/// <summary>
/// Closes the window.
/// </summary>
/// <param name="reason">Window closing reason.</param>
/// <returns>True if action has been cancelled (due to window internal logic).</returns>
public bool Close(ClosingReason reason = ClosingReason.CloseEvent)
{
// Fire events
if (OnClosing(reason))
return true;
OnClose();
// Check if window should be hidden on close event
if (HideOnClose)
{
// Hide
Hide();
}
else
{
// Undock
Undock();
// Delete itself
Dispose();
}
// Done
return false;
}
/// <summary>
/// Selects this tab page.
/// </summary>
/// <param name="autoFocus">True if focus tab after selection change.</param>
public void SelectTab(bool autoFocus = true)
{
_dockedTo?.SelectTab(this, autoFocus);
}
/// <summary>
/// Brings the window to the front of the Z order.
/// </summary>
public void BringToFront()
{
_dockedTo?.RootWindow?.BringToFront();
}
internal void OnUnlinkInternal()
{
OnUnlink();
}
/// <summary>
/// Called when window is unlinked from the master panel.
/// </summary>
protected virtual void OnUnlink()
{
_masterPanel = null;
}
/// <summary>
/// Undocks this window.
/// </summary>
protected virtual void Undock()
{
// Defocus itself
if (ContainsFocus)
Focus();
Defocus();
// Call undock
if (_dockedTo != null)
{
_dockedTo.UndockWindowInternal(this);
Assert.IsNull(_dockedTo);
}
}
/// <summary>
/// Called when window is closing. Operation can be cancelled.
/// </summary>
/// <param name="reason">The reason.</param>
/// <returns>True if cancel, otherwise false to allow.</returns>
protected virtual bool OnClosing(ClosingReason reason)
{
// Allow
return false;
}
/// <summary>
/// Called when window is closed.
/// </summary>
protected virtual void OnClose()
{
// Nothing to do
}
/// <summary>
/// Called when window shows.
/// </summary>
protected virtual void OnShow()
{
// Nothing to do
}
/// <summary>
/// Gets a value indicating whether window uses custom layout data.
/// </summary>
public virtual bool UseLayoutData => false;
/// <summary>
/// Called when during windows layout serialization. Each window can use it to store custom interface data (eg. splitter position).
/// </summary>
/// <param name="writer">The Xml writer.</param>
public virtual void OnLayoutSerialize(XmlWriter writer)
{
}
/// <summary>
/// Called when during windows layout deserialization. Each window can use it to load custom interface data (eg. splitter position).
/// </summary>
/// <param name="node">The Xml document node.</param>
public virtual void OnLayoutDeserialize(XmlElement node)
{
}
/// <summary>
/// Called when during windows layout deserialization if window has no layout data to load. Can be used to restore default UI layout.
/// </summary>
public virtual void OnLayoutDeserialize()
{
}
/// <summary>
/// Serializes splitter panel value into the saved layout.
/// </summary>
/// <param name="writer">The Xml writer.</param>
/// <param name="name">The Xml attribute name to use for value.</param>
/// <param name="splitter">The splitter panel.</param>
protected void LayoutSerializeSplitter(XmlWriter writer, string name, SplitPanel splitter)
{
writer.WriteAttributeString(name, splitter.SplitterValue.ToString(CultureInfo.InvariantCulture));
}
/// <summary>
/// Deserializes splitter panel value from the saved layout.
/// </summary>
/// <param name="node">The Xml document node.</param>
/// <param name="name">The Xml attribute name to use for value.</param>
/// <param name="splitter">The splitter panel.</param>
protected void LayoutDeserializeSplitter(XmlElement node, string name, SplitPanel splitter)
{
if (float.TryParse(node.GetAttribute(name), CultureInfo.InvariantCulture, out float value) && value > 0.01f && value < 0.99f)
splitter.SplitterValue = value;
}
/// <inheritdoc />
public override void OnDestroy()
{
// Auto undock from non-disposing parent (user wants to remove only the dock window)
if (HasParent && !Parent.IsDisposing)
Undock();
// Unlink from the master panel
_masterPanel?.UnlinkWindow(this);
base.OnDestroy();
}
/// <inheritdoc />
public override void Focus()
{
base.Focus();
SelectTab(false);
BringToFront();
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
// Base
if (base.OnKeyDown(key))
return true;
// Custom input events
return InputActions.Process(Editor.Instance, this, key);
}
/// <inheritdoc />
protected override void PerformLayoutBeforeChildren()
{
// Cache window title dimensions
if (_titleSize.X <= 0)
{
var style = Style.Current;
if (style?.FontMedium != null)
_titleSize = style.FontMedium.MeasureText(_title);
}
base.PerformLayoutBeforeChildren();
}
/// <summary>
/// Called when dock panel wants to show the context menu for this window. Can be used to inject custom buttons and items to the context menu (on top).
/// </summary>
/// <param name="menu">The menu.</param>
public virtual void OnShowContextMenu(ContextMenu.ContextMenu menu)
{
}
}
}