// 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
{
///
/// Dockable window UI control.
///
///
public class DockWindow : Panel
{
private string _title;
private Float2 _titleSize;
///
/// The master panel.
///
protected MasterDockPanel _masterPanel;
///
/// The parent panel.
///
protected DockPanel _dockedTo;
///
/// Gets or sets a value indicating whether hide window on close.
///
public bool HideOnClose { get; protected set; }
///
/// Gets the master panel.
///
public MasterDockPanel MasterPanel => _masterPanel;
///
/// Gets the parent dock panel.
///
public DockPanel ParentDockPanel
{
get => _dockedTo;
internal set { _dockedTo = value; }
}
///
/// Gets a value indicating whether this window is docked.
///
public bool IsDocked => _dockedTo != null;
///
/// Gets a value indicating whether this window is selected.
///
public bool IsSelected => _dockedTo?.SelectedTab == this;
///
/// Gets a value indicating whether this window is hidden from the user (eg. not shown or hidden on closed).
///
public bool IsHidden => !Visible || _dockedTo == null;
///
/// Gets the default window size (in UI units, unscaled by DPI which is handled by windowing system).
///
public virtual Float2 DefaultSize => new Float2(900, 580);
///
/// Gets the serialization typename.
///
public virtual string SerializationTypename => "::" + GetType().FullName;
///
/// Gets or sets the window title.
///
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;
}
}
}
///
/// Gets or sets the window icon
///
public SpriteHandle Icon { get; set; }
///
/// Gets the size of the title.
///
public Float2 TitleSize => _titleSize;
///
/// The input actions collection to processed during user input.
///
public InputActionsContainer InputActions = new InputActionsContainer();
///
/// Initializes a new instance of the class.
///
/// The master docking panel.
/// True if hide window on closing, otherwise it will be destroyed.
/// The scroll bars.
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);
}
///
/// Shows the window in a floating state.
///
public void ShowFloating()
{
ShowFloating(Float2.Zero);
}
///
/// Shows the window in a floating state.
///
/// Window location.
public void ShowFloating(WindowStartPosition position)
{
ShowFloating(Float2.Zero, position);
}
///
/// Shows the window in a floating state.
///
/// Window size, set to use default.
/// Window location.
public void ShowFloating(Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent)
{
ShowFloating(new Float2(200, 200), size, position);
}
///
/// Shows the window in a floating state.
///
/// Window location.
/// Window size, set to use default.
/// Window location.
public void ShowFloating(Float2 location, Float2 size, WindowStartPosition position = WindowStartPosition.CenterParent)
{
CreateFloating(location, size, position, true);
}
///
/// Creates the window in a floating state.
///
public void CreateFloating()
{
CreateFloating(Float2.Zero, Float2.Zero);
}
///
/// Creates the window in a floating state.
///
/// Window location.
/// Window size, set to use default.
/// Window location.
/// Window visibility.
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();
}
}
///
/// Shows the window.
///
/// Initial window state.
/// Panel to dock to it.
/// Only used if is set. If true the window will be selected after docking it.
/// Only used if is set. The splitter value to use. If not specified, a default value will be used.
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();
}
}
///
/// Shows the window.
///
/// Initial window state.
/// Window to dock to it.
public void Show(DockState state, DockWindow toDock)
{
Show(state, toDock?.ParentDockPanel);
}
///
/// Focuses or shows the window.
///
public void FocusOrShow()
{
FocusOrShow((DockState)Editor.Instance.Options.Options.Interface.NewWindowLocation);
}
///
/// Focuses or shows the window.
///
/// The state.
public void FocusOrShow(DockState state)
{
if (Visible)
{
SelectTab();
Focus();
}
else
{
Show(state);
}
}
///
/// Hides the window.
///
public void Hide()
{
// Undock
Undock();
Visible = false;
// Ensure that dock panel has no parent
Assert.IsFalse(HasParent);
}
///
/// Closes the window.
///
/// Window closing reason.
/// True if action has been cancelled (due to window internal logic).
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;
}
///
/// Selects this tab page.
///
/// True if focus tab after selection change.
public void SelectTab(bool autoFocus = true)
{
_dockedTo?.SelectTab(this, autoFocus);
}
///
/// Brings the window to the front of the Z order.
///
public void BringToFront()
{
_dockedTo?.RootWindow?.BringToFront();
}
internal void OnUnlinkInternal()
{
OnUnlink();
}
///
/// Called when window is unlinked from the master panel.
///
protected virtual void OnUnlink()
{
_masterPanel = null;
}
///
/// Undocks this window.
///
protected virtual void Undock()
{
// Defocus itself
if (ContainsFocus)
Focus();
Defocus();
// Call undock
if (_dockedTo != null)
{
_dockedTo.UndockWindowInternal(this);
Assert.IsNull(_dockedTo);
}
}
///
/// Called when window is closing. Operation can be cancelled.
///
/// The reason.
/// True if cancel, otherwise false to allow.
protected virtual bool OnClosing(ClosingReason reason)
{
// Allow
return false;
}
///
/// Called when window is closed.
///
protected virtual void OnClose()
{
// Nothing to do
}
///
/// Called when window shows.
///
protected virtual void OnShow()
{
// Nothing to do
}
///
/// Gets a value indicating whether window uses custom layout data.
///
public virtual bool UseLayoutData => false;
///
/// Called when during windows layout serialization. Each window can use it to store custom interface data (eg. splitter position).
///
/// The Xml writer.
public virtual void OnLayoutSerialize(XmlWriter writer)
{
}
///
/// Called when during windows layout deserialization. Each window can use it to load custom interface data (eg. splitter position).
///
/// The Xml document node.
public virtual void OnLayoutDeserialize(XmlElement node)
{
}
///
/// Called when during windows layout deserialization if window has no layout data to load. Can be used to restore default UI layout.
///
public virtual void OnLayoutDeserialize()
{
}
///
/// Serializes splitter panel value into the saved layout.
///
/// The Xml writer.
/// The Xml attribute name to use for value.
/// The splitter panel.
protected void LayoutSerializeSplitter(XmlWriter writer, string name, SplitPanel splitter)
{
writer.WriteAttributeString(name, splitter.SplitterValue.ToString(CultureInfo.InvariantCulture));
}
///
/// Deserializes splitter panel value from the saved layout.
///
/// The Xml document node.
/// The Xml attribute name to use for value.
/// The splitter panel.
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;
}
///
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();
}
///
public override void Focus()
{
base.Focus();
SelectTab();
BringToFront();
}
///
public override bool OnKeyDown(KeyboardKeys key)
{
// Base
if (base.OnKeyDown(key))
return true;
// Custom input events
return InputActions.Process(Editor.Instance, this, key);
}
///
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();
}
///
/// 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).
///
/// The menu.
public virtual void OnShowContextMenu(ContextMenu.ContextMenu menu)
{
}
}
}