// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors { /// /// Represents a container control for . Can contain child elements. /// /// [HideInEditor] public abstract class LayoutElementsContainer : LayoutElement { /// /// Helper flag that is set to true if this container is in root presenter area, otherwise it's one of child groups. /// It's used to collapse all child groups and open the root ones by auto. /// internal bool isRootGroup = true; /// /// Parent container who created this one. /// internal LayoutElementsContainer _parent; /// /// The children. /// public readonly List Children = new List(); /// /// The child custom editors. /// public readonly List Editors = new List(); /// /// Gets the control represented by this element. /// public abstract ContainerControl ContainerControl { get; } /// /// Gets the Custom Editors layout presenter. /// internal CustomEditorPresenter Presenter { get { CustomEditorPresenter result; var container = this; do { result = container as CustomEditorPresenter; container = container._parent; } while (container != null); return result; } } /// /// Adds new group element. /// /// The title. /// The custom editor to be linked for a group. Used to provide more utility functions for a drop panel UI via context menu. /// True if use drop down icon and transparent group header, otherwise use normal style. /// The created element. public GroupElement Group(string title, CustomEditor linkedEditor, bool useTransparentHeader = false) { var element = Group(title, useTransparentHeader); element.Panel.Tag = linkedEditor; element.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked; return element; } private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Float2 location) { var linkedEditor = (CustomEditor)groupPanel.Tag; var menu = new ContextMenu(); var revertToPrefab = menu.AddButton("Revert to Prefab", linkedEditor.RevertToReferenceValue); revertToPrefab.Enabled = linkedEditor.CanRevertReferenceValue; var resetToDefault = menu.AddButton("Reset to default", linkedEditor.RevertToDefaultValue); resetToDefault.Enabled = linkedEditor.CanRevertDefaultValue; menu.AddSeparator(); menu.AddButton("Copy", linkedEditor.Copy); var paste = menu.AddButton("Paste", linkedEditor.Paste); paste.Enabled = linkedEditor.CanPaste; menu.Show(groupPanel, location); } /// /// Adds new group element. /// /// The title. /// True if use drop down icon and transparent group header, otherwise use normal style. /// The created element. public GroupElement Group(string title, bool useTransparentHeader = false) { var element = new GroupElement(); var presenter = Presenter; var isSubGroup = !isRootGroup; if (isSubGroup) element.Panel.Close(); if (presenter != null && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { // Build group identifier (made of path from group titles) var expandPath = title; var container = this; while (container != null && !(container is CustomEditorPresenter)) { if (container.ContainerControl is DropPanel dropPanel) expandPath = dropPanel.HeaderText + "/" + expandPath; container = container._parent; } // Caching/restoring expanded groups (non-root groups cache expanded state so invert boolean expression) if (Editor.Instance.ProjectCache.IsGroupToggled(expandPath) ^ isSubGroup) element.Panel.Close(); else element.Panel.Open(); element.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(expandPath, panel.IsClosed ^ isSubGroup); } element.isRootGroup = false; element._parent = this; element.Panel.HeaderText = title; if (useTransparentHeader) { element.Panel.EnableDropDownIcon = true; element.Panel.EnableContainmentLines = false; element.Panel.HeaderColor = element.Panel.HeaderColorMouseOver = Color.Transparent; } OnAddElement(element); return element; } /// /// Adds new horizontal panel element. /// /// The created element. public HorizontalPanelElement HorizontalPanel() { var element = new HorizontalPanelElement(); OnAddElement(element); return element; } /// /// Adds new vertical panel element. /// /// The created element. public VerticalPanelElement VerticalPanel() { var element = new VerticalPanelElement(); OnAddElement(element); return element; } /// /// Adds new button element. /// /// The text. /// The tooltip text. /// The created element. public ButtonElement Button(string text, string tooltip = null) { var element = new ButtonElement(); element.Button.Text = text; element.Button.TooltipText = tooltip; OnAddElement(element); return element; } /// /// Adds new button element with custom color. /// /// The text. /// The color. /// The tooltip text. /// The created element. public ButtonElement Button(string text, Color color, string tooltip = null) { ButtonElement element = new ButtonElement(); element.Button.Text = text; element.Button.TooltipText = tooltip; element.Button.SetColors(color); OnAddElement(element); return element; } /// /// Adds new custom element. /// /// The custom control. /// The created element. public CustomElement Custom() where T : Control, new() { var element = new CustomElement(); OnAddElement(element); return element; } /// /// Adds new custom element with name label. /// /// The property name. /// The custom control. /// The property label tooltip text. /// The created element. public CustomElement Custom(string name, string tooltip = null) where T : Control, new() { var property = AddPropertyItem(name, tooltip); return property.Custom(); } /// /// Adds new custom elements container. /// /// The custom control. /// The created element. public CustomElementsContainer CustomContainer() where T : ContainerControl, new() { var element = new CustomElementsContainer(); OnAddElement(element); return element; } /// /// Adds new custom elements container with name label. /// /// The property name. /// The custom control. /// The property label tooltip text. /// The created element. public CustomElementsContainer CustomContainer(string name, string tooltip = null) where T : ContainerControl, new() { var property = AddPropertyItem(name); return property.CustomContainer(); } /// /// Adds new space. /// /// The space height. /// The created element. public SpaceElement Space(float height) { var element = new SpaceElement(); element.Init(height); OnAddElement(element); return element; } /// /// Adds sprite image to the layout. /// /// The sprite. /// The created element. public ImageElement Image(SpriteHandle sprite) { var element = new ImageElement(); element.Image.Brush = new SpriteBrush(sprite); OnAddElement(element); return element; } /// /// Adds texture image to the layout. /// /// The texture. /// The created element. public ImageElement Image(Texture texture) { var element = new ImageElement(); element.Image.Brush = new TextureBrush(texture); OnAddElement(element); return element; } /// /// Adds GPU texture image to the layout. /// /// The GPU texture. /// The created element. public ImageElement Image(GPUTexture texture) { var element = new ImageElement(); element.Image.Brush = new GPUTextureBrush(texture); OnAddElement(element); return element; } /// /// Adds new header control. /// /// The header text. /// The created element. public LabelElement Header(string text) { var element = Label(text); element.Label.Font = new FontReference(Style.Current.FontLarge); return element; } internal LabelElement Header(HeaderAttribute header) { var element = Header(header.Text); if (header.FontSize > 0) element.Label.Font = new FontReference(element.Label.Font.Font, header.FontSize); if (header.Color > 0) element.Label.TextColor = Color.FromRGB(header.Color); var size = element.Label.Font.GetFont().MeasureText(header.Text); element.Label.Height = size.Y; return element; } /// /// Adds new text box element. /// /// Enable/disable multiline text input support /// The created element. public TextBoxElement TextBox(bool isMultiline = false) { var element = new TextBoxElement(isMultiline); OnAddElement(element); return element; } /// /// Adds new check box element. /// /// The created element. public CheckBoxElement Checkbox() { var element = new CheckBoxElement(); OnAddElement(element); return element; } /// /// Adds new check box element with name label. /// /// The property name. /// The property label tooltip text. /// The created element. public CheckBoxElement Checkbox(string name, string tooltip = null) { var property = AddPropertyItem(name, tooltip); return property.Checkbox(); } /// /// Adds new tree element. /// /// The created element. public TreeElement Tree() { var element = new TreeElement(); OnAddElement(element); return element; } /// /// Adds new label element. /// /// The label text. /// The label text horizontal alignment. /// The created element. public LabelElement Label(string text, TextAlignment horizontalAlignment = TextAlignment.Near) { var element = new LabelElement(); element.Label.Text = text; element.Label.HorizontalAlignment = horizontalAlignment; OnAddElement(element); return element; } /// /// Adds new label element with name label. /// /// The property name. /// The label text. /// The property label tooltip text. /// The created element. public LabelElement Label(string name, string text, string tooltip = null) { var property = AddPropertyItem(name, tooltip); return property.Label(text); } /// /// Adds new label element. /// /// The label text. /// The label text horizontal alignment. /// The created element. public CustomElement ClickableLabel(string text, TextAlignment horizontalAlignment = TextAlignment.Near) { var element = new CustomElement(); element.CustomControl.Height = 18.0f; element.CustomControl.Text = text; element.CustomControl.HorizontalAlignment = horizontalAlignment; OnAddElement(element); return element; } /// /// Adds new label element with name label. /// /// The property name. /// The label text. /// The property label tooltip text. /// The created element. public CustomElement ClickableLabel(string name, string text, string tooltip = null) { var property = AddPropertyItem(name, tooltip); return property.ClickableLabel(text); } /// /// Adds new float value element. /// /// The created element. public FloatValueElement FloatValue() { var element = new FloatValueElement(); OnAddElement(element); return element; } /// /// Adds new float value element with name label. /// /// The property name. /// The property label tooltip text. /// The created element. public FloatValueElement FloatValue(string name, string tooltip = null) { var property = AddPropertyItem(name, tooltip); return property.FloatValue(); } /// /// Adds new double value element. /// /// The created element. public DoubleValueElement DoubleValue() { var element = new DoubleValueElement(); OnAddElement(element); return element; } /// /// Adds new double value element with name label. /// /// The property name. /// The property label tooltip text. /// The created element. public DoubleValueElement DoubleValue(string name, string tooltip = null) { var property = AddPropertyItem(name, tooltip); return property.DoubleValue(); } /// /// Adds new slider element. /// /// The created element. public SliderElement Slider() { var element = new SliderElement(); OnAddElement(element); return element; } /// /// Adds new slider element with name label. /// /// The property name. /// The property label tooltip text. /// The created element. public SliderElement Slider(string name, string tooltip = null) { var property = AddPropertyItem(name, tooltip); return property.Slider(); } /// /// Adds new signed integer (up to long range) value element. /// /// The created element. public SignedIntegerValueElement SignedIntegerValue() { var element = new SignedIntegerValueElement(); OnAddElement(element); return element; } /// /// Adds new unsigned signed integer (up to ulong range) value element. /// /// The created element. public UnsignedIntegerValueElement UnsignedIntegerValue() { var element = new UnsignedIntegerValueElement(); OnAddElement(element); return element; } /// /// Adds new integer value element. /// /// The created element. public IntegerValueElement IntegerValue() { var element = new IntegerValueElement(); OnAddElement(element); return element; } /// /// Adds new integer value element with name label. /// /// The property name. /// The property label tooltip text. /// The created element. public IntegerValueElement IntegerValue(string name, string tooltip = null) { var property = AddPropertyItem(name, tooltip); return property.IntegerValue(); } /// /// Adds new combobox element. /// /// The created element. public ComboBoxElement ComboBox() { var element = new ComboBoxElement(); OnAddElement(element); return element; } /// /// Adds new combobox element with name label. /// /// The property name. /// The property label tooltip text. /// The created element. public ComboBoxElement ComboBox(string name, string tooltip = null) { var property = AddPropertyItem(name, tooltip); return property.ComboBox(); } /// /// Adds new enum value element. /// /// The enum type. /// The custom entries layout builder. Allows to hide existing or add different enum values to editor. /// The formatting mode. /// The created element. public EnumElement Enum(Type type, EnumComboBox.BuildEntriesDelegate customBuildEntriesDelegate = null, EnumDisplayAttribute.FormatMode formatMode = EnumDisplayAttribute.FormatMode.Default) { var element = new EnumElement(type, customBuildEntriesDelegate, formatMode); OnAddElement(element); return element; } /// /// Adds new enum value element with name label. /// /// The property name. /// The enum type. /// The custom entries layout builder. Allows to hide existing or add different enum values to editor. /// The property label tooltip text. /// The formatting mode. /// The created element. public EnumElement Enum(string name, Type type, EnumComboBox.BuildEntriesDelegate customBuildEntriesDelegate = null, string tooltip = null, EnumDisplayAttribute.FormatMode formatMode = EnumDisplayAttribute.FormatMode.Default) { var property = AddPropertyItem(name, tooltip); return property.Enum(type, customBuildEntriesDelegate, formatMode); } /// /// Adds object(s) editor. Selects proper based on overrides. /// /// The values. /// The custom editor to use. If null will detect it by auto. /// The created element. public CustomEditor Object(ValueContainer values, CustomEditor overrideEditor = null) { if (values == null) throw new ArgumentNullException(); var editor = CustomEditorsUtil.CreateEditor(values, overrideEditor); OnAddEditor(editor); editor.Initialize(CustomEditor.CurrentCustomEditor.Presenter, this, values); return editor; } /// /// Adds object(s) editor with name label. Selects proper based on overrides. /// /// The property name. /// The values. /// The custom editor to use. If null will detect it by auto. /// The property label tooltip text. /// The created element. public CustomEditor Object(string name, ValueContainer values, CustomEditor overrideEditor = null, string tooltip = null) { var property = AddPropertyItem(name, tooltip); return property.Object(values, overrideEditor); } /// /// Adds object(s) editor with name label. Selects proper based on overrides. /// /// The property label. /// The values. /// The custom editor to use. If null will detect it by auto. /// The property label tooltip text. /// The created element. public CustomEditor Object(PropertyNameLabel label, ValueContainer values, CustomEditor overrideEditor = null, string tooltip = null) { var property = AddPropertyItem(label, tooltip); return property.Object(values, overrideEditor); } /// /// Adds object property editor. Selects proper based on overrides. /// /// The property name. /// The values. /// The custom editor to use. If null will detect it by auto. /// The property label tooltip text. /// The created element. public CustomEditor Property(string name, ValueContainer values, CustomEditor overrideEditor = null, string tooltip = null) { var editor = CustomEditorsUtil.CreateEditor(values, overrideEditor); var style = editor.Style; if (style == DisplayStyle.InlineIntoParent || name == EditorDisplayAttribute.InlineStyle) { return Object(values, editor); } if (style == DisplayStyle.Group) { var group = Group(name, editor, true); group.Panel.TooltipText = tooltip; return group.Object(values, editor); } var property = AddPropertyItem(name, tooltip); return property.Object(values, editor); } /// /// Adds object property editor. Selects proper based on overrides. /// /// The property label. /// The values. /// The custom editor to use. If null will detect it by auto. /// The property label tooltip text. /// The created element. public CustomEditor Property(PropertyNameLabel label, ValueContainer values, CustomEditor overrideEditor = null, string tooltip = null) { var editor = CustomEditorsUtil.CreateEditor(values, overrideEditor); var style = editor.Style; if (style == DisplayStyle.InlineIntoParent) { return Object(values, editor); } if (style == DisplayStyle.Group) { var group = Group(label.Text, editor, true); group.Panel.TooltipText = tooltip; return group.Object(values, editor); } var property = AddPropertyItem(label, tooltip); return property.Object(values, editor); } private PropertiesListElement AddPropertyItem() { // Try to reuse previous control PropertiesListElement element; if (Children.Count > 0 && Children[Children.Count - 1] is PropertiesListElement propertiesListElement) { element = propertiesListElement; } else { element = new PropertiesListElement(); OnAddElement(element); } return element; } /// /// Adds the to the current layout or reuses the previous one. Used to inject properties. /// /// The property label name. /// The property label tooltip text. /// The element. public PropertiesListElement AddPropertyItem(string name, string tooltip = null) { var element = AddPropertyItem(); element.OnAddProperty(name, tooltip); return element; } /// /// Adds the to the current layout or reuses the previous one. Used to inject properties. /// /// The property label. /// The property label tooltip text. /// The element. public PropertiesListElement AddPropertyItem(PropertyNameLabel label, string tooltip = null) { if (label == null) throw new ArgumentNullException(); var element = AddPropertyItem(); element.OnAddProperty(label, tooltip); return element; } /// /// Adds custom element to the layout. /// /// The element. public void AddElement(LayoutElement element) { if (element == null) throw new ArgumentNullException(); OnAddElement(element); } /// /// Called when element is added to the layout. /// /// The element. protected virtual void OnAddElement(LayoutElement element) { element.Control.Parent = ContainerControl; Children.Add(element); } /// /// Called when editor is added. /// /// The editor. protected virtual void OnAddEditor(CustomEditor editor) { // This could be passed by the calling code but it's easier to hide it from the user // Note: we need that custom editor to link generated editor into the parent var customEditor = CustomEditor.CurrentCustomEditor; Assert.IsNotNull(customEditor); customEditor.OnChildCreated(editor); Editors.Add(editor); } /// /// Clears the layout. /// public virtual void ClearLayout() { Children.Clear(); Editors.Clear(); } /// public override Control Control => ContainerControl; } }