diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs index cca696d9a..30ffd6287 100644 --- a/Source/Editor/GUI/Docking/DockHintWindow.cs +++ b/Source/Editor/GUI/Docking/DockHintWindow.cs @@ -215,8 +215,8 @@ namespace FlaxEditor.GUI.Docking switch (state) { case DockState.DockFill: - result.Location.Y += DockPanel.DefaultHeaderHeight; - result.Size.Y -= DockPanel.DefaultHeaderHeight; + result.Location.Y += Editor.Instance.Options.Options.Interface.TabHeight; + result.Size.Y -= Editor.Instance.Options.Options.Interface.TabHeight; break; case DockState.DockTop: result.Size.Y *= DockPanel.DefaultSplitterValue; diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs index dbb4e082e..20c36e808 100644 --- a/Source/Editor/GUI/Docking/DockPanelProxy.cs +++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs @@ -1,6 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Options; using FlaxEngine; using FlaxEngine.GUI; @@ -13,12 +14,16 @@ namespace FlaxEditor.GUI.Docking public class DockPanelProxy : ContainerControl { private DockPanel _panel; + private InterfaceOptions.TabCloseButtonVisibility closeButtonVisibility; private double _dragEnterTime = -1; - #if PLATFORM_WINDOWS - private const bool HideTabForSingleTab = true; - #else - private const bool HideTabForSingleTab = false; - #endif + private float _tabHeight = Editor.Instance.Options.Options.Interface.TabHeight; + private bool _useMinimumTabWidth = Editor.Instance.Options.Options.Interface.UseMinimumTabWidth; + private float _minimumTabWidth = Editor.Instance.Options.Options.Interface.MinimumTabWidth; +#if PLATFORM_WINDOWS + private readonly bool _hideTabForSingleTab = Editor.Instance.Options.Options.Interface.HideSingleTabWindowTabBars; +#else + private readonly bool _hideTabForSingleTab = false; +#endif /// /// The is mouse down flag (left button). @@ -55,8 +60,8 @@ namespace FlaxEditor.GUI.Docking /// public DockWindow StartDragAsyncWindow; - private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, DockPanel.DefaultHeaderHeight); - private bool IsSingleFloatingWindow => HideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0; + private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, _tabHeight); + private bool IsSingleFloatingWindow => _hideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0; /// /// Initializes a new instance of the class. @@ -70,6 +75,14 @@ namespace FlaxEditor.GUI.Docking _panel = panel; AnchorPreset = AnchorPresets.StretchAll; Offsets = Margin.Zero; + + Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged; + OnEditorOptionsChanged(Editor.Instance.Options.Options); + } + + private void OnEditorOptionsChanged(EditorOptions options) + { + closeButtonVisibility = options.Interface.ShowTabCloseButton; } private DockWindow GetTabAtPos(Float2 position, out bool closeButton) @@ -80,11 +93,11 @@ namespace FlaxEditor.GUI.Docking var tabsCount = _panel.TabsCount; if (tabsCount == 1) { - var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); + var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); if (HeaderRectangle.Contains(position)) { - closeButton = crossRect.Contains(position); result = _panel.GetTab(0); + closeButton = crossRect.Contains(position) && IsCloseButtonVisible(result, closeButtonVisibility); } } else @@ -93,15 +106,17 @@ namespace FlaxEditor.GUI.Docking for (int i = 0; i < tabsCount; i++) { var tab = _panel.GetTab(i); - var titleSize = tab.TitleSize; - var iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0; - var width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth; - var tabRect = new Rectangle(x, 0, width, DockPanel.DefaultHeaderHeight); + float width = CalculateTabWidth(tab, closeButtonVisibility); + + if (_useMinimumTabWidth && width < _minimumTabWidth) + width = _minimumTabWidth; + + var tabRect = new Rectangle(x, 0, width, HeaderRectangle.Height); var isMouseOver = tabRect.Contains(position); if (isMouseOver) { - var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); - closeButton = crossRect.Contains(position); + var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); + closeButton = crossRect.Contains(position) && IsCloseButtonVisible(tab, closeButtonVisibility); result = tab; break; } @@ -112,6 +127,24 @@ namespace FlaxEditor.GUI.Docking return result; } + private bool IsCloseButtonVisible(DockWindow win, InterfaceOptions.TabCloseButtonVisibility visibilityMode) + { + return visibilityMode != InterfaceOptions.TabCloseButtonVisibility.Never && + (visibilityMode == InterfaceOptions.TabCloseButtonVisibility.Always || + (visibilityMode == InterfaceOptions.TabCloseButtonVisibility.SelectedTab && _panel.SelectedTab == win)); + } + + private float CalculateTabWidth(DockWindow win, InterfaceOptions.TabCloseButtonVisibility visibilityMode) + { + var iconWidth = win.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0; + var width = win.TitleSize.X + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth; + + if (IsCloseButtonVisible(win, visibilityMode)) + width += 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultButtonsSize; + + return width; + } + private void GetTabRect(DockWindow win, out Rectangle bounds) { FlaxEngine.Assertions.Assert.IsTrue(_panel.ContainsTab(win)); @@ -129,10 +162,10 @@ namespace FlaxEditor.GUI.Docking { var tab = _panel.GetTab(i); var titleSize = tab.TitleSize; - float width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin; + float width = CalculateTabWidth(tab, closeButtonVisibility); if (tab == win) { - bounds = new Rectangle(x, 0, width, DockPanel.DefaultHeaderHeight); + bounds = new Rectangle(x, 0, width, HeaderRectangle.Height); return; } x += width; @@ -212,7 +245,7 @@ namespace FlaxEditor.GUI.Docking { Render2D.DrawSprite( tab.Icon, - new Rectangle(DockPanel.DefaultLeftTextMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize), + new Rectangle(DockPanel.DefaultLeftTextMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize), style.Foreground); } @@ -221,17 +254,20 @@ namespace FlaxEditor.GUI.Docking Render2D.DrawText( style.FontMedium, tab.Title, - new Rectangle(DockPanel.DefaultLeftTextMargin + iconWidth, 0, Width - DockPanel.DefaultLeftTextMargin - DockPanel.DefaultButtonsSize - 2 * DockPanel.DefaultButtonsMargin, DockPanel.DefaultHeaderHeight), + new Rectangle(DockPanel.DefaultLeftTextMargin + iconWidth, 0, Width - DockPanel.DefaultLeftTextMargin - DockPanel.DefaultButtonsSize - 2 * DockPanel.DefaultButtonsMargin, HeaderRectangle.Height), style.Foreground, TextAlignment.Near, TextAlignment.Center); - // Draw cross - var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); - bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition); - if (isMouseOverCross) - Render2D.FillRectangle(crossRect, (containsFocus ? style.BackgroundSelected : style.LightBackground) * 1.3f); - Render2D.DrawSprite(style.Cross, crossRect, isMouseOverCross ? style.Foreground : style.ForegroundGrey); + if (IsCloseButtonVisible(tab, closeButtonVisibility)) + { + // Draw cross + var crossRect = new Rectangle(Width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); + bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition); + if (isMouseOverCross) + Render2D.FillRectangle(crossRect, (containsFocus ? style.BackgroundSelected : style.LightBackground) * 1.3f); + Render2D.DrawSprite(style.Cross, crossRect, isMouseOverCross ? style.Foreground : style.ForegroundGrey); + } } else { @@ -245,10 +281,14 @@ namespace FlaxEditor.GUI.Docking // Cache data var tab = _panel.GetTab(i); var tabColor = Color.Black; - var titleSize = tab.TitleSize; var iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0; - var width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth; - var tabRect = new Rectangle(x, 0, width, DockPanel.DefaultHeaderHeight); + + float width = CalculateTabWidth(tab, closeButtonVisibility); + + if (_useMinimumTabWidth && width < _minimumTabWidth) + width = _minimumTabWidth; + + var tabRect = new Rectangle(x, 0, width, headerRect.Height); var isMouseOver = tabRect.Contains(MousePosition); var isSelected = _panel.SelectedTab == tab; @@ -275,7 +315,7 @@ namespace FlaxEditor.GUI.Docking { Render2D.DrawSprite( tab.Icon, - new Rectangle(x + DockPanel.DefaultLeftTextMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize), + new Rectangle(x + DockPanel.DefaultLeftTextMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize), style.Foreground); } @@ -284,27 +324,27 @@ namespace FlaxEditor.GUI.Docking Render2D.DrawText( style.FontMedium, tab.Title, - new Rectangle(x + DockPanel.DefaultLeftTextMargin + iconWidth, 0, 10000, DockPanel.DefaultHeaderHeight), + new Rectangle(x + DockPanel.DefaultLeftTextMargin + iconWidth, 0, 10000, HeaderRectangle.Height), style.Foreground, TextAlignment.Near, TextAlignment.Center); // Draw cross - if (isSelected || isMouseOver) + if (IsCloseButtonVisible(tab, closeButtonVisibility)) { - var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); + var crossRect = new Rectangle(x + width - DockPanel.DefaultButtonsSize - DockPanel.DefaultButtonsMargin, (HeaderRectangle.Height - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize); bool isMouseOverCross = isMouseOver && crossRect.Contains(MousePosition); if (isMouseOverCross) Render2D.FillRectangle(crossRect, tabColor * 1.3f); Render2D.DrawSprite(style.Cross, crossRect, isMouseOverCross ? style.Foreground : style.ForegroundGrey); } - // Move + // Set the start position for the next tab x += width; } // Draw selected tab strip - Render2D.FillRectangle(new Rectangle(0, DockPanel.DefaultHeaderHeight - 2, Width, 2), containsFocus ? style.BackgroundSelected : style.BackgroundNormal); + Render2D.FillRectangle(new Rectangle(0, HeaderRectangle.Height - 2, Width, 2), containsFocus ? style.BackgroundSelected : style.BackgroundNormal); } } @@ -522,7 +562,7 @@ namespace FlaxEditor.GUI.Docking if (IsSingleFloatingWindow) rect = new Rectangle(0, 0, Width, Height); else - rect = new Rectangle(0, DockPanel.DefaultHeaderHeight, Width, Height - DockPanel.DefaultHeaderHeight); + rect = new Rectangle(0, HeaderRectangle.Height, Width, Height - HeaderRectangle.Height); } private DragDropEffect TrySelectTabUnderLocation(ref Float2 location) diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index e86f5cf42..4c1061d4a 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -76,6 +76,25 @@ namespace FlaxEditor.Options DockBottom = DockState.DockBottom } + /// + /// Options for the visibility status of the tab close button. + /// + public enum TabCloseButtonVisibility + { + /// + /// Never show the close button. + /// + Never, + /// + /// Show the close button on tabs that are currently selected. + /// + SelectedTab, + /// + /// Show the close button on all tabs that can be closed. + /// + Always + } + /// /// Options for the action taken by the play button. /// @@ -167,15 +186,6 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(10), Tooltip("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.")] public float InterfaceScale { get; set; } = 1.0f; -#if PLATFORM_WINDOWS - /// - /// Gets or sets a value indicating whether use native window title bar. Editor restart required. - /// - [DefaultValue(false)] - [EditorDisplay("Interface"), EditorOrder(70), Tooltip("Determines whether use native window title bar. Editor restart required.")] - public bool UseNativeWindowSystem { get; set; } = false; -#endif - /// /// Gets or sets a value indicating whether show selected camera preview in the editor window. /// @@ -183,20 +193,6 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(80), Tooltip("Determines whether show selected camera preview in the edit window.")] public bool ShowSelectedCameraPreview { get; set; } = true; - /// - /// 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. - /// - [DefaultValue(false)] - [EditorDisplay("Interface", "Center Mouse On Game Window Focus"), EditorOrder(100), Tooltip("Determines whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.")] - public bool CenterMouseOnGameWinFocus { get; set; } = false; - - /// - /// Gets or sets the method window opening. - /// - [DefaultValue(DockStateProxy.Float)] - [EditorDisplay("Interface", "New Window Location"), EditorOrder(150), Tooltip("Define the opening method for new windows, open in a new tab by default.")] - public DockStateProxy NewWindowLocation { get; set; } = DockStateProxy.Float; - /// /// Gets or sets the editor icons scale. Editor restart required. /// @@ -265,6 +261,66 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(322)] public bool ScrollToScriptOnAdd { get; set; } = true; +#if PLATFORM_WINDOWS + /// + /// Gets or sets a value indicating whether use native window title bar. Editor restart required. + /// + [DefaultValue(false)] + [EditorDisplay("Tabs & Windows"), EditorOrder(70), Tooltip("Determines whether use native window title bar. Editor restart required.")] + public bool UseNativeWindowSystem { get; set; } = false; +#endif + +#if PLATFORM_WINDOWS + /// + /// Gets or sets a value indicating whether a window containing a single tabs hides the tab bar. Editor restart recommended. + /// + [DefaultValue(true)] + [EditorDisplay("Tabs & Windows", "Hide Single-Tab Window Tab Bars"), EditorOrder(71)] + public bool HideSingleTabWindowTabBars { get; set; } = true; +#endif + + /// + /// Gets or sets a value indicating wether the minum tab width should be used. Editor restart required. + /// + [DefaultValue(false)] + [EditorDisplay("Tabs & Windows"), EditorOrder(99)] + public bool UseMinimumTabWidth { get; set; } = false; + + /// + /// Gets or sets a value indicating the minimum tab width. If a tab is smaller than this width, its width will be set to this. Editor restart required. + /// + [DefaultValue(80.0f), Limit(50.0f, 150.0f)] + [EditorDisplay("Tabs & Windows"), EditorOrder(99), VisibleIf(nameof(UseMinimumTabWidth))] + public float MinimumTabWidth { get; set; } = 80.0f; + + /// + /// Gets or sets a value indicating the height of window tabs. Editor restart required. + /// + [DefaultValue(20.0f), Limit(15.0f, 40.0f)] + [EditorDisplay("Tabs & Windows"), EditorOrder(100)] + public float TabHeight { get; set; } = 20.0f; + + /// + /// 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. + /// + [DefaultValue(false)] + [EditorDisplay("Tabs & Windows", "Center Mouse On Game Window Focus"), EditorOrder(101), Tooltip("Determines whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.")] + public bool CenterMouseOnGameWinFocus { get; set; } = false; + + /// + /// Gets or sets the method window opening. + /// + [DefaultValue(DockStateProxy.Float)] + [EditorDisplay("Tabs & Windows", "New Window Location"), EditorOrder(150), Tooltip("Define the opening method for new windows, open in a new tab by default.")] + public DockStateProxy NewWindowLocation { get; set; } = DockStateProxy.Float; + + /// + /// Gets or sets a value indicating when the tab close button should be visible. + /// + [DefaultValue(TabCloseButtonVisibility.SelectedTab)] + [EditorDisplay("Tabs & Windows"), EditorOrder(151)] + public TabCloseButtonVisibility ShowTabCloseButton { get; set; } = TabCloseButtonVisibility.SelectedTab; + /// /// Gets or sets the timestamps prefix mode for output log messages. ///