// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph.Actors; using FlaxEditor.Viewport.Modes; using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.Tools.Foliage { /// /// Foliage editing tab. Supports different modes for foliage editing including spawning, removing, and managing tools. /// /// [HideInEditor] public class FoliageTab : Tab { private readonly Tabs _modes; private readonly ContainerControl _noFoliagePanel; private int _selectedFoliageTypeIndex = -1; private Button _createNewFoliage; /// /// The editor instance. /// public readonly Editor Editor; /// /// The cached selected foliage. It's synchronized with . /// public FlaxEngine.Foliage SelectedFoliage; /// /// Occurs when selected foliage gets changed (to a different value). /// public event Action SelectedFoliageChanged; /// /// Delegate signature for selected foliage index change. /// /// The index of the previous foliage type. /// The index of the current foliage type. public delegate void SelectedFoliageTypeIndexChangedDelegate(int previousIndex, int currentIndex); /// /// Occurs when selected foliage type index gets changed. /// public event SelectedFoliageTypeIndexChangedDelegate SelectedFoliageTypeIndexChanged; /// /// Occurs when selected foliage actors gets modification for foliage types collection (item added or removed). UI uses it to update the layout without manually tracking the collection. /// public event Action SelectedFoliageTypesChanged; /// /// Gets or sets the index of the selected foliage type. /// public int SelectedFoliageTypeIndex { get => _selectedFoliageTypeIndex; set { var prev = _selectedFoliageTypeIndex; if (value == prev) return; _selectedFoliageTypeIndex = value; SelectedFoliageTypeIndexChanged?.Invoke(prev, value); } } /// /// The foliage types tab; /// public FoliageTypesTab FoliageTypes; /// /// The paint tab; /// public PaintTab Paint; /// /// The edit tab; /// public EditTab Edit; /// /// The foliage type model asset IDs checked to paint with them by default. /// public readonly Dictionary FoliageTypeModelIdsToPaint = new Dictionary(); /// /// Initializes a new instance of the class. /// /// The icon. /// The editor instance. public FoliageTab(SpriteHandle icon, Editor editor) : base(string.Empty, icon) { Level.SceneLoaded += OnSceneLoaded; Editor = editor; Editor.SceneEditing.SelectionChanged += OnSelectionChanged; Selected += OnSelected; _modes = new Tabs { Orientation = Orientation.Vertical, UseScroll = true, AnchorPreset = AnchorPresets.StretchAll, Offsets = Margin.Zero, TabsSize = new Float2(50, 32), Parent = this }; // Init tool modes InitSculptMode(); InitPaintMode(); InitEditMode(); _modes.SelectedTabIndex = 0; _noFoliagePanel = new ContainerControl { AnchorPreset = AnchorPresets.StretchAll, Offsets = Margin.Zero, BackgroundColor = Style.Current.Background, Parent = this }; var noFoliageLabel = new Label { Text = "Select foliage to edit\nor\n\n\n\n", AnchorPreset = AnchorPresets.StretchAll, Offsets = Margin.Zero, Parent = _noFoliagePanel }; var buttonText = "Create new foliage"; _createNewFoliage = new Button { Text = buttonText, AnchorPreset = AnchorPresets.MiddleCenter, Offsets = new Margin(-60, 120, -12, 24), Parent = _noFoliagePanel, Enabled = false }; var textSize = Style.Current.FontMedium.MeasureText(buttonText); if (_createNewFoliage.Width < textSize.X) { _createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2; _createNewFoliage.Width = textSize.X + 6; } _createNewFoliage.Clicked += OnCreateNewFoliageClicked; } private void OnSceneLoaded(Scene arg1, Guid arg2) { _createNewFoliage.Enabled = true; Level.SceneUnloaded += OnSceneUnloaded; Level.SceneLoaded -= OnSceneLoaded; } private void OnSceneUnloaded(Scene arg1, Guid arg2) { _createNewFoliage.Enabled = false; Level.SceneLoaded += OnSceneLoaded; Level.SceneUnloaded -= OnSceneUnloaded; } private void OnSelected(Tab tab) { // Auto select first foliage actor to make usage easier if (Editor.SceneEditing.SelectionCount == 1 && Editor.SceneEditing.Selection[0] is SceneGraph.ActorNode actorNode && actorNode.Actor is FlaxEngine.Foliage) return; var actor = Level.FindActor(); if (actor) { Editor.SceneEditing.Select(actor); } } private void OnCreateNewFoliageClicked() { // Create var actor = new FlaxEngine.Foliage { StaticFlags = StaticFlags.FullyStatic, Name = "Foliage" }; // Spawn and select Editor.SceneEditing.Spawn(actor); } private void OnSelectionChanged() { var node = Editor.SceneEditing.SelectionCount > 0 ? Editor.SceneEditing.Selection[0] as FoliageNode : null; var foliage = node?.Actor as FlaxEngine.Foliage; if (foliage != SelectedFoliage) { SelectedFoliageTypeIndex = -1; SelectedFoliage = foliage; SelectedFoliageChanged?.Invoke(); } _noFoliagePanel.Visible = foliage == null; } private void InitSculptMode() { var tab = _modes.AddTab(FoliageTypes = new FoliageTypesTab(this)); tab.Selected += OnTabSelected; } private void InitPaintMode() { var tab = _modes.AddTab(Paint = new PaintTab(this, Editor.Windows.EditWin.Viewport.PaintFoliageGizmo)); tab.Selected += OnTabSelected; } private void InitEditMode() { var tab = _modes.AddTab(Edit = new EditTab(this, Editor.Windows.EditWin.Viewport.EditFoliageGizmo)); tab.Selected += OnTabSelected; } /// public override void OnSelected() { base.OnSelected(); UpdateGizmoMode(); } private void OnTabSelected(Tab tab) { UpdateGizmoMode(); } /// /// Updates the active viewport gizmo mode based on the current mode. /// private void UpdateGizmoMode() { switch (_modes.SelectedTabIndex) { case 0: Editor.Windows.EditWin.Viewport.SetActiveMode(); break; case 1: Editor.Windows.EditWin.Viewport.SetActiveMode(); break; case 2: Editor.Windows.EditWin.Viewport.SetActiveMode(); break; default: throw new IndexOutOfRangeException("Invalid foliage tab mode."); } } /// public override void Update(float deltaTime) { FoliageTypes.CheckFoliageTypesCount(); base.Update(deltaTime); } internal void OnSelectedFoliageTypesChanged() { SelectedFoliageTypesChanged?.Invoke(); } /// public override void OnDestroy() { if (_createNewFoliage.Enabled) Level.SceneUnloaded -= OnSceneUnloaded; else Level.SceneLoaded -= OnSceneLoaded; base.OnDestroy(); } } }