// 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) { } } }