// 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; } } } } } }