//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using System; using System.Collections; using System.Collections.Generic; using BansheeEngine; namespace BansheeEditor { /** @addtogroup GUI-Editor * @{ */ /// /// Base class for objects that display GUI for a modifyable dictionary of elements. Elements can be added, modified or /// removed. /// public abstract class GUIDictionaryFieldBase { private const int IndentAmount = 5; protected Dictionary rows = new Dictionary(); protected GUIDictionaryFieldRow editRow; protected GUILayoutY guiLayout; protected GUILayoutX guiChildLayout; protected GUILayoutX guiTitleLayout; protected GUILayoutX guiInternalTitleLayout; protected GUILayoutY guiContentLayout; protected GUIToggle guiFoldout; protected bool isExpanded; protected int depth; protected LocString title; private int editRowIdx = -1; private object editKey; private object editValue; private object editOriginalKey; private State state; private bool isModified; /// /// Expands or collapses the entries of the dictionary. /// public bool IsExpanded { get { return isExpanded; } set { if (isExpanded != value) ToggleFoldout(value, true); } } /// /// Event that triggers when the list foldout is expanded or collapsed (rows are shown or hidden). /// public Action OnExpand; /// /// Constructs a new GUI dictionary. /// /// Label to display on the dictionary GUI title. /// Layout to which to append the list GUI elements to. /// Determines at which depth to render the background. Useful when you have multiple /// nested containers whose backgrounds are overlaping. Also determines background style, /// depths divisible by two will use an alternate style. protected GUIDictionaryFieldBase(LocString title, GUILayout layout, int depth = 0) { this.title = title; this.depth = depth; guiLayout = layout.AddLayoutY(); guiTitleLayout = guiLayout.AddLayoutX(); } /// /// Completely rebuilds the dictionary GUI elements. /// public void BuildGUI() { editKey = CreateKey(); editValue = CreateValue(); UpdateHeaderGUI(); foreach (var KVP in rows) KVP.Value.Destroy(); rows.Clear(); if (editRow != null) { editRow.Destroy(); editRow = null; } if (!IsNull()) { // Hidden dependency: BuildGUI must be called after all elements are // in the dictionary so we do it in two steps int numRows = GetNumRows(); for (int i = 0; i < numRows; i++) { GUIDictionaryFieldRow newRow = CreateRow(); rows.Add(i, newRow); } editRow = CreateRow(); editRow.Initialize(this, guiContentLayout, numRows, depth + 1); editRow.Enabled = false; for (int i = 0; i < numRows; i++) rows[i].Initialize(this, guiContentLayout, i, depth + 1); } } /// /// Rebuilds the GUI dictionary header if needed. /// protected void UpdateHeaderGUI() { Action BuildEmptyGUI = () => { guiInternalTitleLayout = guiTitleLayout.InsertLayoutX(0); guiInternalTitleLayout.AddElement(new GUILabel(title)); guiInternalTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100))); GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create), new LocEdString("Create")); GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30)); createBtn.OnClick += OnCreateButtonClicked; guiInternalTitleLayout.AddElement(createBtn); }; Action BuildFilledGUI = () => { guiInternalTitleLayout = guiTitleLayout.InsertLayoutX(0); guiFoldout = new GUIToggle(title, EditorStyles.Foldout); guiFoldout.Value = isExpanded; guiFoldout.AcceptsKeyFocus = false; guiFoldout.OnToggled += x => ToggleFoldout(x, false); GUIContent clearIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clear), new LocEdString("Clear")); GUIButton guiClearBtn = new GUIButton(clearIcon, GUIOption.FixedWidth(30)); guiClearBtn.OnClick += OnClearButtonClicked; GUIContent addIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Add), new LocEdString("Add")); GUIButton guiAddBtn = new GUIButton(addIcon, GUIOption.FixedWidth(30)); guiAddBtn.OnClick += OnAddButtonClicked; guiInternalTitleLayout.AddElement(guiFoldout); guiInternalTitleLayout.AddElement(guiAddBtn); guiInternalTitleLayout.AddElement(guiClearBtn); guiChildLayout = guiLayout.AddLayoutX(); guiChildLayout.AddSpace(IndentAmount); GUIPanel guiContentPanel = guiChildLayout.AddPanel(); GUILayoutX guiIndentLayoutX = guiContentPanel.AddLayoutX(); guiIndentLayoutX.AddSpace(IndentAmount); GUILayoutY guiIndentLayoutY = guiIndentLayoutX.AddLayoutY(); guiIndentLayoutY.AddSpace(IndentAmount); guiContentLayout = guiIndentLayoutY.AddLayoutY(); guiIndentLayoutY.AddSpace(IndentAmount); guiIndentLayoutX.AddSpace(IndentAmount); guiChildLayout.AddSpace(IndentAmount); short backgroundDepth = (short)(Inspector.START_BACKGROUND_DEPTH - depth - 1); string bgPanelStyle = depth % 2 == 0 ? EditorStylesInternal.InspectorContentBgAlternate : EditorStylesInternal.InspectorContentBg; GUIPanel backgroundPanel = guiContentPanel.AddPanel(backgroundDepth); GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle); backgroundPanel.AddElement(inspectorContentBg); ToggleFoldout(isExpanded, false); }; if (state == State.None) { if (!IsNull()) { BuildFilledGUI(); state = State.Filled; } else { BuildEmptyGUI(); state = State.Empty; } } else if (state == State.Empty) { if (!IsNull()) { guiInternalTitleLayout.Destroy(); BuildFilledGUI(); state = State.Filled; } } else if (state == State.Filled) { if (IsNull()) { guiInternalTitleLayout.Destroy(); guiChildLayout.Destroy(); BuildEmptyGUI(); state = State.Empty; } } } /// /// Destroys all rows and clears the row list. /// private void ClearRows() { foreach (var KVP in rows) KVP.Value.Destroy(); editRow.Destroy(); rows.Clear(); } /// /// Returns the layout that is used for positioning the elements in the title bar. /// /// Horizontal layout for positioning the title bar elements. public GUILayoutX GetTitleLayout() { return guiTitleLayout; } /// /// Refreshes contents of all dictionary rows and checks if anything was modified. /// /// State representing was anything modified between two last calls to . public virtual InspectableState Refresh() { InspectableState state = InspectableState.NotModified; for (int i = 0; i < rows.Count; i++) state |= rows[i].Refresh(); if (editRow != null && editRow.Enabled) state |= editRow.Refresh(); if (isModified) { state |= InspectableState.Modified; isModified = false; } return state; } /// /// Destroys the GUI elements. /// public void Destroy() { if (guiLayout != null) { guiLayout.Destroy(); guiLayout = null; } guiLayout = null; guiTitleLayout = null; guiChildLayout = null; for (int i = 0; i < rows.Count; i++) rows[i].Destroy(); rows.Clear(); if (editRow != null) editRow.Destroy(); editRow = null; } /// /// Checks is the specified row index the temporary edit row. /// /// Sequential index of the row to check. /// True if the index is of an edit row. private bool IsTemporaryRow(int rowIdx) { return rowIdx == rows.Count; } /// /// Checks is any row being currently edited. /// /// True if a row is being edited, false otherwise. private bool IsEditInProgress() { return editRowIdx != -1; } /// /// Returns the number of rows in the dictionary. /// /// Number of rows in the dictionary. protected abstract int GetNumRows(); /// /// Checks is the dictionary instance not assigned. /// /// True if there is not a dictionary instance. protected abstract bool IsNull(); /// /// Gets a value of an element at the specified index in the list. Also handles temporary edit fields. /// /// Sequential index of the row to set the value for. /// Value of the list element at the specified key. protected internal virtual object GetValueInternal(int rowIdx) { if (rowIdx == editRowIdx || IsTemporaryRow(rowIdx)) return editValue; else return GetValue(GetKey(rowIdx)); } /// /// Sets a value of an element at the specified index in the list. Also handles temporary edit fields. /// /// Sequential index of the row to set the value for. /// Value to assign to the element. Caller must ensure it is of valid type. protected internal virtual void SetValueInternal(int rowIdx, object value) { if (rowIdx == editRowIdx || IsTemporaryRow(rowIdx)) editValue = value; else SetValue(GetKey(rowIdx), value); } /// /// Changes the value of the key of the specified row. /// /// Sequential index of the row to set the key for. /// Key to assign to the specified row. protected internal void SetKey(int rowIdx, object key) { if (editRowIdx != rowIdx) { Debug.LogError("Cannot change a dictionary row that is not in edit state."); return; } editKey = key; } /// /// Gets a key for a row at the specified index. Handles the special case for the currently edited row. /// /// Sequential index of the row for which to retrieve the key. /// Key for a row at the specified index. protected internal object GetKeyInternal(int rowIdx) { if (editRowIdx == rowIdx || IsTemporaryRow(rowIdx)) return editKey; return GetKey(rowIdx); } /// /// Creates a new dictionary row GUI. /// /// Object containing the dictionary row GUI. protected abstract GUIDictionaryFieldRow CreateRow(); /// /// Gets a key for a row at the specified index. /// /// Sequential index of the row for which to retrieve the key. /// Key for a row at the specified index. protected internal abstract object GetKey(int rowIdx); /// /// Gets a value of an element at the specified index in the list. /// /// Key of the element whose value to retrieve. /// Value of the dictionary entry for the specified key. protected internal abstract object GetValue(object key); /// /// Sets a value of an element at the specified index in the list. /// /// Key of the element whose value to set. /// Value to assign to the element. Caller must ensure it is of valid type. protected internal abstract void SetValue(object key, object value); /// /// Updates both key and value of an existing entry. /// /// Original key of the entry. /// New key of the entry. /// New value of the entry. protected internal abstract void EditEntry(object oldKey, object newKey, object value); /// /// Adds a new entry to the dictionary. /// /// Key of the entry to add. /// Value of the entry to add. protected internal abstract void AddEntry(object key, object value); /// /// Removes the specified entry from the dictionary. /// /// Key of the entry to remove. protected internal abstract void RemoveEntry(object key); /// /// Creates a new empty key object of a valid type that can be used in the dictionary. /// /// New empty key object. protected internal abstract object CreateKey(); /// /// Creates a new empty value object of a valid type that can be used in the dictionary. /// /// New empty value object. protected internal abstract object CreateValue(); /// /// Checks does the element with the specified key exist in the dictionary. /// /// Key of the element to check for existence. /// True if the key exists in the dictionary, false otherwise. protected internal abstract bool Contains(object key); /// /// Clones the specified dictionary element. /// /// Sequential index of the element in the dictionary to clone. protected internal abstract KeyValuePair CloneElement(int index); /// /// Creates a brand new dictionary with zero elements in the place of the current dictionary. /// protected abstract void CreateDictionary(); /// /// Deletes the current dictionary object. /// protected abstract void DeleteDictionary(); /// /// Hides or shows the dictionary rows. /// /// True if the rows should be displayed, false otherwise. /// True if the foldout was expanded/collapsed from outside code. private void ToggleFoldout(bool expanded, bool external) { isExpanded = expanded; if (guiChildLayout != null) guiChildLayout.Active = isExpanded && (rows.Count > 0 || IsEditInProgress()); if (external) { if (guiFoldout != null) guiFoldout.Value = isExpanded; } if (OnExpand != null) OnExpand(expanded); } /// /// Triggered when the user clicks on the create button on the title bar. Creates a brand new dictionary with zero /// elements in the place of the current dictionary. /// protected void OnCreateButtonClicked() { CreateDictionary(); UpdateHeaderGUI(); editRow.Initialize(this, guiContentLayout, 0, depth + 1); editRow.Enabled = false; isModified = true; } /// /// Triggered when the user clicks on the add button on the title bar. Adds a new empty element to the dictionary. /// protected virtual void OnAddButtonClicked() { if (IsEditInProgress()) { DialogBox.Open( new LocEdString("Edit in progress."), new LocEdString("You are editing the entry with key \"" + editKey + "\". You cannot add a row " + "before applying or discarding those changes. Do you wish to apply those changes first?"), DialogBox.Type.YesNoCancel, x => { switch (x) { case DialogBox.ResultType.Yes: if (ApplyChanges()) StartAdd(); break; case DialogBox.ResultType.No: DiscardChanges(); StartAdd(); break; } }); } else { if (!isExpanded) ToggleFoldout(true, true); StartAdd(); } } /// /// Triggered when the user clicks on the clear button on the title bar. Deletes the current dictionary object. /// protected void OnClearButtonClicked() { DeleteDictionary(); UpdateHeaderGUI(); ClearRows(); isModified = true; } /// /// Triggered when the user clicks on the delete button next to a dictionary entry. Deletes an element in the /// dictionary. /// /// Sequential row index of the entry that was clicked. protected internal virtual void OnDeleteButtonClicked(int rowIdx) { if (IsEditInProgress()) DiscardChanges(); else { RemoveEntry(GetKey(rowIdx)); rows[rows.Count - 1].Destroy(); rows.Remove(rows.Count - 1); editRow.SetIndex(GetNumRows()); isModified = true; } } /// /// Triggered when the user clicks on the clone button next to a dictionary entry. Clones an element and /// adds the clone to the dictionary. /// /// Sequential row index of the entry that was clicked. protected internal virtual void OnCloneButtonClicked(int rowIdx) { if (IsEditInProgress()) { DialogBox.Open( new LocEdString("Edit in progress."), new LocEdString("You are editing the entry with key \"" + editKey + "\". You cannot clone a row " + "before applying or discarding those changes. Do you wish to apply those changes first?"), DialogBox.Type.YesNoCancel, x => { switch (x) { case DialogBox.ResultType.Yes: if (ApplyChanges()) StartClone(rowIdx); break; case DialogBox.ResultType.No: DiscardChanges(); StartClone(rowIdx); break; } }); } else StartClone(rowIdx); } /// /// Triggered when user clicks the edit or apply (depending on state) button next to the dictionary entry. Starts /// edit mode for the element, if not already in edit mode. Applies edit mode changes if already in edit mode. /// /// Sequential row index of the entry that was clicked. protected internal virtual void OnEditButtonClicked(int rowIdx) { if (editRowIdx == rowIdx) ApplyChanges(); else { if (IsEditInProgress()) { DialogBox.Open( new LocEdString("Edit in progress."), new LocEdString("You are already editing the entry with key \"" + editKey + "\". You cannot edit " + "another row before applying or discarding those changes. Do you wish to apply those changes first?"), DialogBox.Type.YesNoCancel, x => { switch (x) { case DialogBox.ResultType.Yes: if (ApplyChanges()) StartEdit(rowIdx); break; case DialogBox.ResultType.No: DiscardChanges(); StartEdit(rowIdx); break; } }); } else StartEdit(rowIdx); } } /// /// Starts an edit operation on a row with the specified key. Allows the user to change the key of the specified row. /// Caller must ensure no edit operation is already in progress. /// /// Sequential row index of the entry to edit. private void StartEdit(int rowIdx) { object key = GetKey(rowIdx); KeyValuePair clone = CloneElement(rowIdx); editKey = clone.Key; editValue = clone.Value; editOriginalKey = key; editRowIdx = rowIdx; rows[rowIdx].EditMode = true; guiChildLayout.Active = rows.Count > 0 && isExpanded; } /// /// Starts an add operation. Adds a new key/value pair and allows the user to set them up in a temporary row /// before inserting them into the dictionary. Caller must ensure no edit operation is already in progress. /// private void StartAdd() { editKey = CreateKey(); editValue = CreateValue(); editOriginalKey = null; editRowIdx = rows.Count; editRow.BuildGUI(); editRow.Enabled = true; editRow.EditMode = true; ToggleFoldout(isExpanded, false); } /// /// Starts a clone operation. Adds a new key/value pair by cloning an existing one. Allows the user to modify the /// new pair in a temporary row before inserting them into the dictionary. Caller must ensure no edit operation is /// already in progress. /// /// Sequential row index of the entry to clone. private void StartClone(int rowIdx) { KeyValuePair clone = CloneElement(rowIdx); editKey = clone.Key; editValue = clone.Value; editOriginalKey = null; editRowIdx = rows.Count; editRow.BuildGUI(); editRow.Enabled = true; editRow.EditMode = true; ToggleFoldout(isExpanded, false); } /// /// Attempts to apply any changes made to the currently edited row. /// /// True if the changes were successfully applied, false if the new key already exists in the dictionary. /// private bool ApplyChanges() { if (!IsEditInProgress()) return true; if (Contains(editKey) && (editOriginalKey == null || !editOriginalKey.Equals(editKey))) { DialogBox.Open( new LocEdString("Key already exists."), new LocEdString("Cannot add a key \"" + editKey + "\" to dictionary. Key already exists"), DialogBox.Type.OK); return false; } else { if (IsTemporaryRow(editRowIdx)) { editRow.EditMode = false; editRow.Enabled = false; } else { rows[editRowIdx].EditMode = false; } if (editOriginalKey != null) // Editing EditEntry(editOriginalKey, editKey, editValue); else // Adding/Cloning { AddEntry(editKey, editValue); // Hidden dependency: Initialize must be called after all elements are // in the dictionary so we do it in two steps int newRowIdx = rows.Count; rows[newRowIdx] = CreateRow(); rows[newRowIdx].Initialize(this, guiContentLayout, newRowIdx, depth + 1); } editRow.SetIndex(rows.Count); editKey = CreateKey(); editValue = CreateValue(); editOriginalKey = null; editRowIdx = -1; ToggleFoldout(isExpanded, false); isModified = true; return true; } } /// /// Cancels any changes made on the currently edited row. /// private void DiscardChanges() { if (IsEditInProgress()) { if (IsTemporaryRow(editRowIdx)) { editRow.EditMode = false; editRow.Enabled = false; } else { rows[editRowIdx].EditMode = false; } editKey = CreateKey(); editValue = CreateValue(); editOriginalKey = null; editRow.Enabled = false; editRowIdx = -1; ToggleFoldout(isExpanded, false); } } /// /// Possible states dictionary GUI can be in. /// private enum State { None, Empty, Filled } } /// /// Creates GUI elements that allow viewing and manipulation of a . When constructing the /// object user can provide a custom type that manages GUI for individual dictionary elements. /// /// Type of key used by the dictionary. /// Type of value stored in the dictionary. /// Type of rows that are used to handle GUI for individual dictionary elements. public class GUIDictionaryField : GUIDictionaryFieldBase where RowType : GUIDictionaryFieldRow, new() { public delegate int SortDictionaryDelegate(Key a, Key b); /// /// Triggered when the reference array has been changed. This does not include changes that only happen to its /// internal elements. /// public Action> OnChanged; /// /// Triggered when an element in the list has been added or changed. /// public Action OnValueChanged; /// /// Triggered when an element in the dictionary has been removed. /// public Action OnValueRemoved; /// /// Optional method that will be used for sorting the elements in the dictionary. /// public SortDictionaryDelegate SortMethod; /// /// Array object whose contents are displayed. /// public Dictionary Dictionary { get { return dictionary; } } protected Dictionary dictionary; private List orderedKeys = new List(); /// /// Constructs a new dictionary GUI field. /// /// Label to display on the dictionary GUI title. /// Object containing the data. Can be null. /// Layout to which to append the list GUI elements to. /// Determines at which depth to render the background. Useful when you have multiple /// nested containers whose backgrounds are overlaping. Also determines background style, /// depths divisible by two will use an alternate style. protected GUIDictionaryField(LocString title, Dictionary dictionary, GUILayout layout, int depth = 0) : base(title, layout, depth) { this.dictionary = dictionary; UpdateKeys(); } /// /// Creates a dictionary GUI field containing GUI elements required for displaying the provided dictionary. /// /// Type of rows that are used to handle GUI for individual dictionary elements. /// Label to display on the list GUI title. /// Object containing the data. Can be null. /// Layout to which to append the list GUI elements to. /// Determines at which depth to render the background. Useful when you have multiple /// nested containers whose backgrounds are overlaping. Also determines background style, /// depths divisible by two will use an alternate style. /// New instance of dictionary GUI field. public static GUIDictionaryField Create(LocString title, Dictionary dictionary, GUILayout layout, int depth = 0) { GUIDictionaryField guiDictionary = new GUIDictionaryField( title, dictionary, layout, depth); guiDictionary.BuildGUI(); return guiDictionary; } /// /// Updates the ordered set of keys used for mapping sequential indexes to keys. Should be called whenever a /// dictionary key changes. /// private void UpdateKeys() { orderedKeys.Clear(); if (dictionary != null) { foreach (var KVP in dictionary) orderedKeys.Add(KVP.Key); if (SortMethod != null) orderedKeys.Sort((x,y) => SortMethod(x, y)); else orderedKeys.Sort(); } } /// protected override GUIDictionaryFieldRow CreateRow() { return new RowType(); } /// protected override int GetNumRows() { if (dictionary != null) return dictionary.Count; return 0; } /// protected override bool IsNull() { return dictionary == null; } /// protected internal override object GetKey(int rowIdx) { return orderedKeys[rowIdx]; } /// protected internal override object GetValue(object key) { return dictionary[(Key)key]; } /// protected internal override void SetValue(object key, object value) { dictionary[(Key)key] = (Value)value; if (OnValueChanged != null) OnValueChanged((Key)key); } /// protected internal override bool Contains(object key) { return dictionary.ContainsKey((Key)key); ; } /// protected internal override void EditEntry(object oldKey, object newKey, object value) { dictionary.Remove((Key)oldKey); dictionary[(Key)newKey] = (Value)value; if (OnValueRemoved != null) OnValueRemoved((Key)oldKey); if (OnValueChanged != null) OnValueChanged((Key)newKey); UpdateKeys(); } /// protected internal override void AddEntry(object key, object value) { dictionary[(Key)key] = (Value)value; if (OnValueChanged != null) OnValueChanged((Key)key); UpdateKeys(); } /// protected internal override void RemoveEntry(object key) { dictionary.Remove((Key) key); if (OnValueRemoved != null) OnValueRemoved((Key)key); UpdateKeys(); } /// protected internal override object CreateKey() { return SerializableUtility.Create(); } /// protected internal override object CreateValue() { return SerializableUtility.Create(); } /// protected internal override KeyValuePair CloneElement(int index) { object key = GetKey(index); object value = GetValue(key); KeyValuePair clone = new KeyValuePair( SerializableUtility.Clone(key), SerializableUtility.Clone(value)); return clone; } /// protected override void CreateDictionary() { dictionary = new Dictionary(); if (OnChanged != null) OnChanged(dictionary); UpdateKeys(); } /// protected override void DeleteDictionary() { dictionary = null; if (OnChanged != null) OnChanged(dictionary); UpdateKeys(); } } /// /// Contains GUI elements for a single entry in a dictionary. /// public abstract class GUIDictionaryFieldRow { private GUILayoutY rowLayout; private GUILayoutX keyRowLayout; private GUILayoutY keyLayout; private GUILayoutY valueLayout; private GUILayoutX titleLayout; private GUIButton cloneBtn; private GUIButton deleteBtn; private GUIButton editBtn; private bool localTitleLayout; private bool enabled = true; private bool editMode = false; private int rowIdx; private int depth; private InspectableState modifiedState; protected GUIDictionaryFieldBase parent; /// /// Returns the depth at which the row is rendered. /// protected int Depth { get { return depth; } } /// /// Determines is the row currently being displayed. /// internal bool Enabled { get { return enabled; } set { enabled = value; rowLayout.Active = value; } } /// /// Sequential index of the entry in the dictionary. /// internal int RowIdx { get { return rowIdx; } } /// /// Enables or disables the row's edit mode. This determines what type of buttons are shown on the row title bar. /// internal bool EditMode { set { if (value) { GUIContent cancelIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Cancel), new LocEdString("Cancel")); GUIContent applyIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Apply), new LocEdString("Apply")); deleteBtn.SetContent(cancelIcon); editBtn.SetContent(applyIcon); } else { GUIContent deleteIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Delete), new LocEdString("Delete")); GUIContent editIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Edit), new LocEdString("Edit")); deleteBtn.SetContent(deleteIcon); editBtn.SetContent(editIcon); } editMode = value; OnEditModeChanged(value); } } /// /// Constructs a new dictionary row object. /// protected GUIDictionaryFieldRow() { } /// /// Initializes the row and creates row GUI elements. /// /// Parent array GUI object that the entry is contained in. /// Parent layout that row GUI elements will be added to. /// Sequential index of the row. /// Determines the depth at which the element is rendered. internal void Initialize(GUIDictionaryFieldBase parent, GUILayout parentLayout, int rowIdx, int depth) { this.parent = parent; this.rowIdx = rowIdx; this.depth = depth; rowLayout = parentLayout.AddLayoutY(); keyRowLayout = rowLayout.AddLayoutX(); keyLayout = keyRowLayout.AddLayoutY(); valueLayout = rowLayout.AddLayoutY(); BuildGUI(); } /// /// Changes the index of the dictionary element this row represents. /// /// Sequential index of the dictionary entry. internal void SetIndex(int seqIndex) { this.rowIdx = seqIndex; } /// /// (Re)creates all row GUI elements. /// internal protected void BuildGUI() { keyLayout.Clear(); valueLayout.Clear(); GUILayoutX externalTitleLayout = CreateKeyGUI(keyLayout); CreateValueGUI(valueLayout); if (localTitleLayout || (titleLayout != null && titleLayout == externalTitleLayout)) return; if (externalTitleLayout != null) { localTitleLayout = false; titleLayout = externalTitleLayout; } else { GUILayoutY buttonCenter = keyRowLayout.AddLayoutY(); buttonCenter.AddFlexibleSpace(); titleLayout = buttonCenter.AddLayoutX(); buttonCenter.AddFlexibleSpace(); localTitleLayout = true; } GUIContent cloneIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clone), new LocEdString("Clone")); GUIContent deleteIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Delete), new LocEdString("Delete")); GUIContent editIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Edit), new LocEdString("Edit")); cloneBtn = new GUIButton(cloneIcon, GUIOption.FixedWidth(30)); deleteBtn = new GUIButton(deleteIcon, GUIOption.FixedWidth(30)); editBtn = new GUIButton(editIcon, GUIOption.FixedWidth(30)); cloneBtn.OnClick += () => parent.OnCloneButtonClicked(rowIdx); deleteBtn.OnClick += () => parent.OnDeleteButtonClicked(rowIdx); editBtn.OnClick += () => parent.OnEditButtonClicked(rowIdx); titleLayout.AddElement(cloneBtn); titleLayout.AddElement(deleteBtn); titleLayout.AddSpace(10); titleLayout.AddElement(editBtn); EditMode = editMode; } /// /// Creates GUI elements specific to type in the key portion of a dictionary entry. /// /// Layout to insert the row GUI elements to. /// An optional title bar layout that the standard dictionary buttons will be appended to. protected abstract GUILayoutX CreateKeyGUI(GUILayoutY layout); /// /// Creates GUI elements specific to type in the key portion of a dictionary entry. /// /// Layout to insert the row GUI elements to. protected abstract void CreateValueGUI(GUILayoutY layout); /// /// Triggered when a GUI rows enters or leaves edit mode. Allows the row GUI to be updated accordingly. /// /// True if the edit mode is being enabled, false otherwise. protected virtual void OnEditModeChanged(bool editMode) { } /// /// Refreshes the GUI for the dictionary row and checks if anything was modified. /// /// State representing was anything modified between two last calls to . internal protected virtual InspectableState Refresh() { InspectableState oldState = modifiedState; if (modifiedState.HasFlag(InspectableState.Modified)) modifiedState = InspectableState.NotModified; return oldState; } /// /// Marks the contents of the row as modified. /// protected void MarkAsModified() { modifiedState |= InspectableState.ModifyInProgress; } /// /// Confirms any queued modifications, signaling parent elements. /// protected void ConfirmModify() { if (modifiedState.HasFlag(InspectableState.ModifyInProgress)) modifiedState |= InspectableState.Modified; } /// /// Gets the key contained in this dictionary's row. /// /// Type of the key. Must match the dictionary's element type. /// Key in this dictionary's row. protected T GetKey() { return (T)parent.GetKeyInternal(rowIdx); } /// /// Sets the key for in this dictionary's row. /// /// Type of the key. Must match the dictionary's element type. /// Key to assign to this row. protected void SetKey(T key) { parent.SetKey(rowIdx, key); } /// /// Gets the value contained in this dictionary's row. /// /// Type of the value. Must match the dictionary's element type. /// Value in this dictionary's row. protected T GetValue() { return (T)parent.GetValueInternal(rowIdx); } /// /// Sets the value contained in this dictionary's row. /// /// Type of the value. Must match the dictionary's element type. /// Value to set. protected void SetValue(T value) { parent.SetValueInternal(rowIdx, value); } /// /// Destroys all row GUI elements. /// public void Destroy() { if (rowLayout != null) { rowLayout.Destroy(); rowLayout = null; } keyRowLayout = null; keyLayout = null; valueLayout = null; titleLayout = null; cloneBtn = null; deleteBtn = null; editBtn = null; localTitleLayout = false; } } /** @} */ }