// Copyright (c) Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.Linq; using System.Xml; using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.GUI.Input; using FlaxEditor.SceneGraph; using FlaxEditor.Viewport; using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.Windows { /// /// Window used to present collection of selected object(s) properties in a grid. Supports Undo/Redo operations. /// /// /// public class PropertiesWindow : SceneEditorWindow, IPresenterOwner { private IEnumerable undoRecordObjects; private readonly Dictionary _actorScrollValues = new Dictionary(); private bool _lockObjects = false; private SearchBox _searchBox; private Panel _scrollingPanel; /// public override bool UseLayoutData => true; /// /// The editor. /// public readonly CustomEditorPresenter Presenter; /// /// Indication of if the scale is locked. /// public bool ScaleLinked = false; /// /// Indication of if UI elements should size relative to the pivot point. /// public bool UIPivotRelative = true; /// /// Indication of if the properties window is locked on specific objects. /// public bool LockSelection { get => _lockObjects; set { if (value == _lockObjects) return; _lockObjects = value; if (!value) OnSelectionChanged(); } } /// public ISceneEditingContext SceneContext => Editor.Windows.EditWin; /// /// Initializes a new instance of the class. /// /// The editor. public PropertiesWindow(Editor editor) : base(editor, true, ScrollBars.None) { Title = "Properties"; Icon = editor.Icons.Build64; AutoFocus = true; var headerPanel = new ContainerControl { AnchorPreset = AnchorPresets.HorizontalStretchTop, BackgroundColor = Style.Current.Background, IsScrollable = false, Offsets = new Margin(0, 0, 0, 18 + 6), Parent = this, }; _searchBox = new SearchBox { AnchorPreset = AnchorPresets.HorizontalStretchMiddle, Parent = headerPanel, Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18), }; _searchBox.TextChanged += ApplySearchFilter; _scrollingPanel = new Panel(ScrollBars.Vertical) { AnchorPreset = AnchorPresets.StretchAll, Offsets = new Margin(0, 0, headerPanel.Bottom, 0), Parent = this, }; Presenter = new CustomEditorPresenter(editor.Undo, null, this); Presenter.Panel.Parent = _scrollingPanel; Presenter.GetUndoObjects += GetUndoObjects; Presenter.Features |= FeatureFlags.CacheExpandedGroups; Presenter.AfterLayout += OnPresenterAfterLayout; _scrollingPanel.VScrollBar.ValueChanged += OnScrollValueChanged; Editor.SceneEditing.SelectionChanged += OnSelectionChanged; } /// public override void OnSceneLoaded(Scene scene, Guid sceneId) { base.OnSceneLoaded(scene, sceneId); // Clear scroll values if new scene is loaded non additively if (Level.ScenesCount > 1) return; _actorScrollValues.Clear(); if (LockSelection) { LockSelection = false; Presenter.Deselect(); } } private void OnScrollValueChanged() { if (Editor.SceneEditing.SelectionCount > 1) return; // Clear first 10 scroll values to keep the memory down. Dont need to cache very single value in a scene. We could expose this as a editor setting in the future. if (_actorScrollValues.Count >= 20) { int i = 0; foreach (var e in _actorScrollValues) { if (i >= 10) break; _actorScrollValues.Remove(e.Key); i += 1; } } if (_scrollingPanel.VScrollBar != null) _actorScrollValues[Editor.SceneEditing.Selection[0].ID] = _scrollingPanel.VScrollBar.TargetValue; } private IEnumerable GetUndoObjects(CustomEditorPresenter customEditorPresenter) { return undoRecordObjects; } private void OnSelectionChanged() { if (LockSelection) return; // Update selected objects // TODO: use cached collection for less memory allocations undoRecordObjects = Editor.SceneEditing.Selection.ConvertAll(x => x.UndoRecordObject).Distinct(); var objects = Editor.SceneEditing.Selection.ConvertAll(x => x.EditableObject).Distinct(); Presenter.Select(objects); // Set scroll value of window if it exists if (Editor.SceneEditing.SelectionCount == 1 && _scrollingPanel.VScrollBar != null) _scrollingPanel.VScrollBar.TargetValue = _actorScrollValues.GetValueOrDefault(Editor.SceneEditing.Selection[0].ID, 0); } private void OnPresenterAfterLayout(LayoutElementsContainer layout) { ApplySearchFilter(); } private void ApplySearchFilter() { Presenter.ApplySearchFilter(_searchBox.Text); } /// public override void OnLayoutSerialize(XmlWriter writer) { writer.WriteAttributeString("ScaleLinked", ScaleLinked.ToString()); writer.WriteAttributeString("UIPivotRelative", UIPivotRelative.ToString()); } /// public override void OnLayoutDeserialize(XmlElement node) { if (bool.TryParse(node.GetAttribute("ScaleLinked"), out bool value1)) ScaleLinked = value1; if (bool.TryParse(node.GetAttribute("UIPivotRelative"), out value1)) UIPivotRelative = value1; } /// public EditorViewport PresenterViewport => Editor.Windows.EditWin.Viewport; /// public void Select(List nodes) { Editor.SceneEditing.Select(nodes); } } }