// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.GUI.Tabs
{
///
/// Represents control which contains collection of .
///
///
[HideInEditor]
public class Tabs : ContainerControl
{
///
/// Tab header control. Draw the tab title and handles selecting it.
///
///
public class TabHeader : Control
{
///
/// The tabs control.
///
protected Tabs Tabs;
///
/// The tab.
///
protected Tab Tab;
///
/// Initializes a new instance of the class.
///
/// The tabs.
/// The tab.
public TabHeader(Tabs tabs, Tab tab)
: base(Float2.Zero, tabs._tabsSize)
{
Tabs = tabs;
Tab = tab;
}
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (EnabledInHierarchy && Tab.Enabled)
{
Tabs.SelectedTab = Tab;
Tabs.Focus();
}
return true;
}
///
public override void Draw()
{
base.Draw();
var style = Style.Current;
var enabled = EnabledInHierarchy && Tab.EnabledInHierarchy;
var tabRect = new Rectangle(Float2.Zero, Size);
var textOffset = Tabs._orientation == Orientation.Horizontal ? 0 : 8;
// Draw bar
if (Tabs.SelectedTab == Tab)
{
var color = style.BackgroundSelected;
if (!enabled)
color *= 0.6f;
if (Tabs._orientation == Orientation.Horizontal)
{
Render2D.FillRectangle(tabRect, color);
}
else
{
const float lefEdgeWidth = 4;
var leftEdgeRect = tabRect;
leftEdgeRect.Size.X = lefEdgeWidth;
var fillRect = tabRect;
fillRect.Size.X -= lefEdgeWidth;
fillRect.Location.X += lefEdgeWidth;
Render2D.FillRectangle(fillRect, style.Background);
Render2D.FillRectangle(leftEdgeRect, color);
}
}
else if (IsMouseOver && enabled)
{
Render2D.FillRectangle(tabRect, style.BackgroundHighlighted);
}
// Draw icon
if (Tab.Icon.IsValid)
{
Render2D.DrawSprite(Tab.Icon, tabRect.MakeExpanded(-8), style.Foreground);
}
// Draw text
if (!string.IsNullOrEmpty(Tab.Text))
{
Render2D.DrawText(style.FontMedium, Tab.Text, new Rectangle(tabRect.X + textOffset, tabRect.Y, tabRect.Width - textOffset, tabRect.Height), style.Foreground, Tabs.TabsTextHorizontalAlignment, Tabs.TabsTextVerticalAlignment);
}
}
}
///
/// The tabs headers container control. Arranges the tabs headers and support scrolling them.
///
///
public class TabsHeader : Panel
{
///
/// The tabs control.
///
protected Tabs Tabs;
///
/// Initializes a new instance of the class.
///
/// The tabs.
public TabsHeader(Tabs tabs)
{
Tabs = tabs;
}
///
protected override void PerformLayoutBeforeChildren()
{
base.PerformLayoutBeforeChildren();
// Arrange tab header controls
var pos = Float2.Zero;
var sizeMask = Tabs._orientation == Orientation.Horizontal ? Float2.UnitX : Float2.UnitY;
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is TabHeader tabHeader)
{
tabHeader.Location = pos;
pos += tabHeader.Size * sizeMask;
}
}
}
}
///
/// The selected tab index.
///
protected int _selectedIndex;
///
/// The tabs size.
///
protected Float2 _tabsSize;
///
/// Automatic tab size based on the fill axis.
///
protected bool _autoTabsSizeAuto;
///
/// The orientation.
///
protected Orientation _orientation;
///
/// Gets the size of the tabs.
///
public Float2 TabsSize
{
get => _tabsSize;
set
{
_tabsSize = value;
if (!_autoTabsSizeAuto)
{
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
{
if (TabsPanel.Children[i] is TabHeader tabHeader)
tabHeader.Size = value;
}
}
PerformLayout();
}
}
///
/// Enables automatic tabs size to fill the space.
///
public bool AutoTabsSize
{
get => _autoTabsSizeAuto;
set
{
_autoTabsSizeAuto = value;
PerformLayout();
}
}
///
/// Gets or sets the orientation.
///
public Orientation Orientation
{
get => _orientation;
set
{
_orientation = value;
if (UseScroll)
TabsPanel.ScrollBars = _orientation == Orientation.Horizontal ? ScrollBars.Horizontal : ScrollBars.Vertical;
PerformLayout();
}
}
///
/// Gets or sets the color of the tab strip background.
///
public Color TabStripColor
{
get => TabsPanel.BackgroundColor;
set => TabsPanel.BackgroundColor = value;
}
///
/// Occurs when selected tab gets changed.
///
public event Action SelectedTabChanged;
///
/// The tabs panel control.
///
public readonly TabsHeader TabsPanel;
///
/// Gets or sets the selected tab.
///
public Tab SelectedTab
{
get => _selectedIndex == -1 && Children.Count > _selectedIndex + 1 ? null : Children[_selectedIndex + 1] as Tab;
set => SelectedTabIndex = value != null ? Children.IndexOf(value) - 1 : -1;
}
///
/// Gets or sets the selected tab index.
///
public int SelectedTabIndex
{
get => _selectedIndex;
set
{
var index = value;
var tabsCount = Children.Count - 1;
// Clamp index
if (index < -1)
index = -1;
else if (index >= tabsCount)
index = tabsCount - 1;
// Check if index will change
if (_selectedIndex != index)
{
SelectedTab?.OnDeselected();
_selectedIndex = index;
PerformLayout();
OnSelectedTabChanged();
}
}
}
///
/// Gets or sets a value indicating whether use scroll bars for the tabs.
///
public bool UseScroll
{
get => TabsPanel.ScrollBars != ScrollBars.None;
set
{
if (value)
TabsPanel.ScrollBars = _orientation == Orientation.Horizontal ? ScrollBars.Horizontal : ScrollBars.Vertical;
else
TabsPanel.ScrollBars = ScrollBars.None;
}
}
///
/// Gets or sets the horizontal tabs text alignment within the tab titles bounds.
///
public TextAlignment TabsTextHorizontalAlignment { get; set; } = TextAlignment.Near;
///
/// Gets or sets the vertical tabs text alignment within the tab titles bounds.
///
public TextAlignment TabsTextVerticalAlignment { get; set; } = TextAlignment.Center;
///
/// Initializes a new instance of the class.
///
public Tabs()
{
AutoFocus = false;
BackgroundColor = Style.Current.Background;
_selectedIndex = -1;
_tabsSize = new Float2(70, 16);
_orientation = Orientation.Horizontal;
TabsPanel = new TabsHeader(this);
TabStripColor = Style.Current.LightBackground;
TabsPanel.Parent = this;
}
///
/// Adds the tab.
///
/// Tab control type.
/// The tab.
/// The tab.
public T AddTab(T tab) where T : Tab
{
if (tab == null)
throw new ArgumentNullException();
FlaxEngine.Assertions.Assert.IsFalse(Children.Contains(tab));
// Add
tab.Parent = this;
// Check if has no selected tab
if (_selectedIndex == -1)
SelectedTab = tab;
return tab;
}
///
/// Called when selected tab gets changed.
///
protected virtual void OnSelectedTabChanged()
{
SelectedTabChanged?.Invoke(this);
SelectedTab?.OnSelected();
}
///
public override void OnChildrenChanged()
{
bool wasLocked = TabsPanel.IsLayoutLocked;
TabsPanel.IsLayoutLocked = true;
// Update tabs headers
TabsPanel.DisposeChildren();
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is Tab tab)
TabsPanel.AddChild(tab.CreateHeader());
}
TabsPanel.IsLayoutLocked = wasLocked;
base.OnChildrenChanged();
}
///
public override void Update(float deltaTime)
{
// Update all controls except not selected tabs
var selectedTab = SelectedTab;
for (int i = 0; i < _children.Count; i++)
{
if (_children[i].Enabled && (!(_children[i] is Tab) || _children[i] == selectedTab))
{
_children[i].Update(deltaTime);
}
}
}
///
protected override void PerformLayoutBeforeChildren()
{
var tabsSize = _tabsSize;
if (_autoTabsSizeAuto)
{
// Horizontal is default for Toolbox so tabs go to the right
int tabsCount = 0;
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
{
if (TabsPanel.Children[i] is TabHeader)
tabsCount++;
}
if (tabsCount == 0)
tabsCount = 1;
if (_orientation == Orientation.Horizontal)
tabsSize.X = Width / tabsCount;
else
tabsSize.Y = Height / tabsCount;
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
{
if (TabsPanel.Children[i] is TabHeader tabHeader)
tabHeader.Size = tabsSize;
}
}
// Fit the tabs panel
TabsPanel.Size = _orientation == Orientation.Horizontal
? new Float2(Width, tabsSize.Y)
: new Float2(tabsSize.X, Height);
// Hide all pages except selected one
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is Tab tab)
{
if (i - 1 == _selectedIndex)
{
// Show and fit size
tab.Visible = true;
tab.Bounds = _orientation == Orientation.Horizontal
? new Rectangle(0, tabsSize.Y, Width, Height - tabsSize.Y)
: new Rectangle(tabsSize.X, 0, Width - tabsSize.X, Height);
}
else
{
// Hide
tab.Visible = false;
}
}
}
}
}
}