|
|
@@ -1,596 +1,596 @@
|
|
|
-// Urho3D editor UI-element handling
|
|
|
-
|
|
|
-UIElement@ editorUIElement;
|
|
|
-XMLFile@ uiElementDefaultStyle;
|
|
|
-Array<String> availableStyles;
|
|
|
-
|
|
|
-UIElement@ editUIElement;
|
|
|
-Array<Serializable@> selectedUIElements;
|
|
|
-Array<Serializable@> editUIElements;
|
|
|
-
|
|
|
-Array<XMLFile@> uiElementCopyBuffer;
|
|
|
-
|
|
|
-bool suppressUIElementChanges = false;
|
|
|
-
|
|
|
-// Registered UIElement user variable reverse mappings
|
|
|
-VariantMap uiElementVarNames;
|
|
|
-
|
|
|
-const ShortStringHash FILENAME_VAR("FileName");
|
|
|
-const ShortStringHash MODIFIED_VAR("Modified");
|
|
|
-const ShortStringHash CHILD_ELEMENT_FILENAME_VAR("ChildElemFileName");
|
|
|
-
|
|
|
-void ClearUIElementSelection()
|
|
|
-{
|
|
|
- editUIElement = null;
|
|
|
- selectedUIElements.Clear();
|
|
|
- editUIElements.Clear();
|
|
|
-}
|
|
|
-
|
|
|
-void CreateRootUIElement()
|
|
|
-{
|
|
|
- // Create a root UIElement only once here, do not confuse this with ui.root itself
|
|
|
- editorUIElement = ui.root.CreateChild("UIElement");
|
|
|
- editorUIElement.name = "UI";
|
|
|
- editorUIElement.SetSize(graphics.width, graphics.height);
|
|
|
- editorUIElement.traversalMode = TM_DEPTH_FIRST; // This is needed for root-like element to prevent artifacts
|
|
|
-
|
|
|
- // This is needed to distinguish our own element events from Editor's UI element events
|
|
|
- editorUIElement.elementEventSender = true;
|
|
|
- SubscribeToEvent(editorUIElement, "ElementAdded", "HandleUIElementAdded");
|
|
|
- SubscribeToEvent(editorUIElement, "ElementRemoved", "HandleUIElementRemoved");
|
|
|
-
|
|
|
- // Since this root UIElement is not being handled by above handlers, update it into hierarchy list manually as another list root item
|
|
|
- UpdateHierarchyItem(M_MAX_UNSIGNED, editorUIElement, null);
|
|
|
-}
|
|
|
-
|
|
|
-bool NewUIElement(const String&in typeName)
|
|
|
-{
|
|
|
- // If no edit element then parented to root
|
|
|
- UIElement@ parent = editUIElement !is null ? editUIElement : editorUIElement;
|
|
|
- UIElement@ element = parent.CreateChild(typeName);
|
|
|
- if (element !is null)
|
|
|
- {
|
|
|
- // Use the predefined UI style if set, otherwise use editor's own UI style
|
|
|
- XMLFile@ defaultStyle = uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle;
|
|
|
-
|
|
|
- if (editUIElement is null)
|
|
|
- {
|
|
|
- // If parented to root, set the internal variables
|
|
|
- element.vars[FILENAME_VAR] = "";
|
|
|
- element.vars[MODIFIED_VAR] = false;
|
|
|
- // set a default UI style
|
|
|
- element.defaultStyle = defaultStyle;
|
|
|
- // and position the newly created element at center
|
|
|
- CenterDialog(element);
|
|
|
- }
|
|
|
- // Apply the auto style
|
|
|
- element.style = AUTO_STYLE;
|
|
|
- // Do not allow UI subsystem to reorder children while editing the element in the editor
|
|
|
- element.sortChildren = false;
|
|
|
-
|
|
|
- // Create an undo action for the create
|
|
|
- CreateUIElementAction action;
|
|
|
- action.Define(element);
|
|
|
- SaveEditAction(action);
|
|
|
- SetUIElementModified(element);
|
|
|
-
|
|
|
- FocusUIElement(element);
|
|
|
- }
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-void ResetSortChildren(UIElement@ element)
|
|
|
-{
|
|
|
- element.sortChildren = false;
|
|
|
-
|
|
|
- // Perform the action recursively for child elements
|
|
|
- for (uint i = 0; i < element.numChildren; ++i)
|
|
|
- ResetSortChildren(element.children[i]);
|
|
|
-}
|
|
|
-
|
|
|
-void OpenUILayout(const String&in fileName)
|
|
|
-{
|
|
|
- if (fileName.empty)
|
|
|
- return;
|
|
|
-
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- // Check if the UI element has been opened before
|
|
|
- if (editorUIElement.GetChild(FILENAME_VAR, Variant(fileName)) !is null)
|
|
|
- {
|
|
|
- log.Warning("UI element is already opened: " + fileName);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Always load from the filesystem, not from resource paths
|
|
|
- if (!fileSystem.FileExists(fileName))
|
|
|
- {
|
|
|
- log.Error("No such file: " + fileName);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- File file(fileName, FILE_READ);
|
|
|
- if (!file.open)
|
|
|
- return;
|
|
|
-
|
|
|
- // Add the new resource path
|
|
|
- SetResourcePath(GetPath(fileName));
|
|
|
-
|
|
|
- XMLFile@ xmlFile = XMLFile();
|
|
|
- xmlFile.Load(file);
|
|
|
-
|
|
|
- suppressUIElementChanges = true;
|
|
|
-
|
|
|
- // If uiElementDefaultStyle is not set then automatically fallback to use the editor's own default style
|
|
|
- UIElement@ element = ui.LoadLayout(xmlFile, uiElementDefaultStyle);
|
|
|
- if (element !is null)
|
|
|
- {
|
|
|
- element.vars[FILENAME_VAR] = fileName;
|
|
|
- element.vars[MODIFIED_VAR] = false;
|
|
|
-
|
|
|
- // Do not allow UI subsystem to reorder children while editing the element in the editor
|
|
|
- ResetSortChildren(element);
|
|
|
- // Register variable names from the 'enriched' XMLElement, if any
|
|
|
- RegisterUIElementVar(xmlFile.root);
|
|
|
-
|
|
|
- editorUIElement.AddChild(element);
|
|
|
-
|
|
|
- UpdateHierarchyItem(element);
|
|
|
- FocusUIElement(element);
|
|
|
-
|
|
|
- ClearEditActions();
|
|
|
- }
|
|
|
-
|
|
|
- suppressUIElementChanges = false;
|
|
|
-}
|
|
|
-
|
|
|
-bool CloseUILayout()
|
|
|
-{
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- suppressUIElementChanges = true;
|
|
|
-
|
|
|
- for (uint i = 0; i < selectedUIElements.length; ++i)
|
|
|
- {
|
|
|
- UIElement@ element = GetTopLevelUIElement(selectedUIElements[i]);
|
|
|
- if (element !is null)
|
|
|
- {
|
|
|
- element.Remove();
|
|
|
- UpdateHierarchyItem(GetListIndex(element), null, null);
|
|
|
- }
|
|
|
- }
|
|
|
- hierarchyList.ClearSelection();
|
|
|
- ClearEditActions();
|
|
|
-
|
|
|
- suppressUIElementChanges = false;
|
|
|
-
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-bool CloseAllUILayouts()
|
|
|
-{
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- suppressUIElementChanges = true;
|
|
|
-
|
|
|
- editorUIElement.RemoveAllChildren();
|
|
|
- UpdateHierarchyItem(editorUIElement, true);
|
|
|
-
|
|
|
- // Reset element ID number generator
|
|
|
- uiElementNextID = UI_ELEMENT_BASE_ID + 1;
|
|
|
-
|
|
|
- hierarchyList.ClearSelection();
|
|
|
- ClearEditActions();
|
|
|
-
|
|
|
- suppressUIElementChanges = false;
|
|
|
-
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-bool SaveUILayout(const String&in fileName)
|
|
|
-{
|
|
|
- if (fileName.empty)
|
|
|
- return false;
|
|
|
-
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- File file(fileName, FILE_WRITE);
|
|
|
- if (!file.open)
|
|
|
- return false;
|
|
|
-
|
|
|
- UIElement@ element = GetTopLevelUIElement(editUIElement);
|
|
|
- if (element is null)
|
|
|
- return false;
|
|
|
-
|
|
|
- XMLFile@ elementData = XMLFile();
|
|
|
- XMLElement rootElem = elementData.CreateRoot("element");
|
|
|
- bool success = element.SaveXML(rootElem);
|
|
|
- if (success)
|
|
|
- {
|
|
|
- FilterInternalVars(rootElem);
|
|
|
- success = elementData.Save(file);
|
|
|
- if (success)
|
|
|
- {
|
|
|
- element.vars[FILENAME_VAR] = fileName;
|
|
|
- SetUIElementModified(element, false);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return success;
|
|
|
-}
|
|
|
-
|
|
|
-bool SaveUILayoutWithExistingName()
|
|
|
-{
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- UIElement@ element = GetTopLevelUIElement(editUIElement);
|
|
|
- if (element is null)
|
|
|
- return false;
|
|
|
-
|
|
|
- String fileName = element.GetVar(FILENAME_VAR).GetString();
|
|
|
- if (fileName.empty)
|
|
|
- return PickFile(); // No name yet, so pick one
|
|
|
- else
|
|
|
- return SaveUILayout(fileName);
|
|
|
-}
|
|
|
-
|
|
|
-void LoadChildUIElement(const String&in fileName)
|
|
|
-{
|
|
|
- if (fileName.empty)
|
|
|
- return;
|
|
|
-
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- if (!fileSystem.FileExists(fileName))
|
|
|
- {
|
|
|
- log.Error("No such file: " + fileName);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- File file(fileName, FILE_READ);
|
|
|
- if (!file.open)
|
|
|
- return;
|
|
|
-
|
|
|
- XMLFile@ xmlFile = XMLFile();
|
|
|
- xmlFile.Load(file);
|
|
|
-
|
|
|
- suppressUIElementChanges = true;
|
|
|
-
|
|
|
- if (editUIElement.LoadChildXML(xmlFile, uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle))
|
|
|
- {
|
|
|
- XMLElement rootElem = xmlFile.root;
|
|
|
- uint index = rootElem.HasAttribute("index") ? rootElem.GetUInt("index") : editUIElement.numChildren - 1;
|
|
|
- UIElement@ element = editUIElement.children[index];
|
|
|
- ResetSortChildren(element);
|
|
|
- RegisterUIElementVar(xmlFile.root);
|
|
|
- element.vars[CHILD_ELEMENT_FILENAME_VAR] = fileName;
|
|
|
- if (index == editUIElement.numChildren - 1)
|
|
|
- UpdateHierarchyItem(element);
|
|
|
- else
|
|
|
- // If not last child, find the list index of the next sibling as the insertion index
|
|
|
- UpdateHierarchyItem(GetListIndex(editUIElement.children[index + 1]), element, hierarchyList.items[GetListIndex(editUIElement)]);
|
|
|
- SetUIElementModified(element);
|
|
|
-
|
|
|
- // Create an undo action for the load
|
|
|
- CreateUIElementAction action;
|
|
|
- action.Define(element);
|
|
|
- SaveEditAction(action);
|
|
|
-
|
|
|
- FocusUIElement(element);
|
|
|
- }
|
|
|
-
|
|
|
- suppressUIElementChanges = false;
|
|
|
-}
|
|
|
-
|
|
|
-bool SaveChildUIElement(const String&in fileName)
|
|
|
-{
|
|
|
- if (fileName.empty)
|
|
|
- return false;
|
|
|
-
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- File file(fileName, FILE_WRITE);
|
|
|
- if (!file.open)
|
|
|
- return false;
|
|
|
-
|
|
|
- XMLFile@ elementData = XMLFile();
|
|
|
- XMLElement rootElem = elementData.CreateRoot("element");
|
|
|
- bool success = editUIElement.SaveXML(rootElem);
|
|
|
- if (success)
|
|
|
- {
|
|
|
- FilterInternalVars(rootElem);
|
|
|
- success = elementData.Save(file);
|
|
|
- if (success)
|
|
|
- editUIElement.vars[CHILD_ELEMENT_FILENAME_VAR] = fileName;
|
|
|
- }
|
|
|
-
|
|
|
- return success;
|
|
|
-}
|
|
|
-
|
|
|
-void SetUIElementDefaultStyle(const String&in fileName)
|
|
|
-{
|
|
|
- if (fileName.empty)
|
|
|
- return;
|
|
|
-
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- // Always load from the filesystem, not from resource paths
|
|
|
- if (!fileSystem.FileExists(fileName))
|
|
|
- {
|
|
|
- log.Error("No such file: " + fileName);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- File file(fileName, FILE_READ);
|
|
|
- if (!file.open)
|
|
|
- return;
|
|
|
-
|
|
|
- uiElementDefaultStyle = XMLFile();
|
|
|
- uiElementDefaultStyle.Load(file);
|
|
|
-
|
|
|
- // Remove the existing style list to ensure it gets repopulated again with the new default style file
|
|
|
- availableStyles.Clear();
|
|
|
-
|
|
|
- // Refresh Attribute Inspector when it is currently showing attributes of UI-element item type as the existing styles in the style drop down list are not valid anymore
|
|
|
- if (!editUIElements.empty)
|
|
|
- attributesFullDirty = true;
|
|
|
-}
|
|
|
-
|
|
|
-// Prepare XPath query object only once and use it multiple times
|
|
|
-XPathQuery filterInternalVarsQuery("//attribute[@name='Variables']/variant");
|
|
|
-
|
|
|
-void FilterInternalVars(XMLElement source)
|
|
|
-{
|
|
|
- XPathResultSet resultSet = filterInternalVarsQuery.Evaluate(source);
|
|
|
- XMLElement resultElem = resultSet.firstResult;
|
|
|
- while (resultElem.notNull)
|
|
|
- {
|
|
|
- String name = GetVariableName(resultElem.GetUInt("hash"));
|
|
|
- if (name.empty)
|
|
|
- {
|
|
|
- XMLElement parent = resultElem.parent;
|
|
|
-
|
|
|
- // If variable name is empty (or unregistered) then it is an internal variable and should be removed
|
|
|
- if (parent.RemoveChild(resultElem))
|
|
|
- {
|
|
|
- // If parent does not have any children anymore then remove the parent also
|
|
|
- if (!parent.HasChild("variant"))
|
|
|
- parent.parent.RemoveChild(parent);
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- // If it is registered then it is a user-defined variable, so 'enrich' the XMLElement to store the variable name in plaintext
|
|
|
- resultElem.SetAttribute("name", name);
|
|
|
- resultElem = resultElem.nextResult;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-XPathQuery registerUIElemenVarsQuery("//attribute[@name='Variables']/variant/@name");
|
|
|
-
|
|
|
-void RegisterUIElementVar(XMLElement source)
|
|
|
-{
|
|
|
- XPathResultSet resultSet = registerUIElemenVarsQuery.Evaluate(source);
|
|
|
- XMLElement resultAttr = resultSet.firstResult; // Since we are selecting attribute, the resultset is in attribute context
|
|
|
- while (resultAttr.notNull)
|
|
|
- {
|
|
|
- String name = resultAttr.GetAttribute();
|
|
|
- uiElementVarNames[name] = name;
|
|
|
- resultAttr = resultAttr.nextResult;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-UIElement@ GetTopLevelUIElement(UIElement@ element)
|
|
|
-{
|
|
|
- // Only top level UI-element contains the FILENAME_VAR
|
|
|
- while (element !is null && !element.vars.Contains(FILENAME_VAR))
|
|
|
- element = element.parent;
|
|
|
- return element;
|
|
|
-}
|
|
|
-
|
|
|
-void SetUIElementModified(UIElement@ element, bool flag = true)
|
|
|
-{
|
|
|
- element = GetTopLevelUIElement(element);
|
|
|
- if (element !is null && element.GetVar(MODIFIED_VAR).GetBool() != flag)
|
|
|
- {
|
|
|
- element.vars[MODIFIED_VAR] = flag;
|
|
|
- UpdateHierarchyItemText(GetListIndex(element), element.visible, GetUIElementTitle(element));
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-XPathQuery availableStylesXPathQuery("/elements/element[@auto='false']/@type");
|
|
|
-
|
|
|
-void GetAvailableStyles()
|
|
|
-{
|
|
|
- // Use the predefined UI style if set, otherwise use editor's own UI style
|
|
|
- XMLFile@ defaultStyle = uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle;
|
|
|
- XMLElement rootElem = defaultStyle.root;
|
|
|
- XPathResultSet resultSet = availableStylesXPathQuery.Evaluate(rootElem);
|
|
|
- XMLElement resultElem = resultSet.firstResult;
|
|
|
- while (resultElem.notNull)
|
|
|
- {
|
|
|
- availableStyles.Push(resultElem.GetAttribute());
|
|
|
- resultElem = resultElem.nextResult;
|
|
|
- }
|
|
|
-
|
|
|
- availableStyles.Sort();
|
|
|
-}
|
|
|
-
|
|
|
-void PopulateStyleList(DropDownList@ styleList)
|
|
|
-{
|
|
|
- if (availableStyles.empty)
|
|
|
- GetAvailableStyles();
|
|
|
-
|
|
|
- for (uint i = 0; i < availableStyles.length; ++i)
|
|
|
- {
|
|
|
- Text@ choice = Text();
|
|
|
- styleList.AddItem(choice);
|
|
|
- choice.style = "EditorEnumAttributeText";
|
|
|
- choice.text = availableStyles[i];
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-bool UIElementCut()
|
|
|
-{
|
|
|
- return UIElementCopy() && UIElementDelete();
|
|
|
-}
|
|
|
-
|
|
|
-bool UIElementCopy()
|
|
|
-{
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- uiElementCopyBuffer.Clear();
|
|
|
-
|
|
|
- for (uint i = 0; i < selectedUIElements.length; ++i)
|
|
|
- {
|
|
|
- XMLFile@ xml = XMLFile();
|
|
|
- XMLElement rootElem = xml.CreateRoot("element");
|
|
|
- selectedUIElements[i].SaveXML(rootElem);
|
|
|
- uiElementCopyBuffer.Push(xml);
|
|
|
- }
|
|
|
-
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-void ResetDuplicateID(UIElement@ element)
|
|
|
-{
|
|
|
- // If it is a duplicate copy then the element ID need to be regenerated by resetting it now to empty
|
|
|
- if (GetListIndex(element) != NO_ITEM)
|
|
|
- element.vars[UI_ELEMENT_ID_VAR] = Variant();
|
|
|
-
|
|
|
- // Perform the action recursively for child elements
|
|
|
- for (uint i = 0; i < element.numChildren; ++i)
|
|
|
- ResetDuplicateID(element.children[i]);
|
|
|
-}
|
|
|
-
|
|
|
-bool UIElementPaste()
|
|
|
-{
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- // Group for storing undo actions
|
|
|
- EditActionGroup group;
|
|
|
-
|
|
|
- // Have to update manually because the element ID var is not set yet when the E_ELEMENTADDED event is sent
|
|
|
- suppressUIElementChanges = true;
|
|
|
-
|
|
|
- for (uint i = 0; i < uiElementCopyBuffer.length; ++i)
|
|
|
- {
|
|
|
- XMLElement rootElem = uiElementCopyBuffer[i].root;
|
|
|
- if (editUIElement.LoadChildXML(rootElem, null))
|
|
|
- {
|
|
|
- UIElement@ element = editUIElement.children[editUIElement.numChildren - 1];
|
|
|
- ResetDuplicateID(element);
|
|
|
- UpdateHierarchyItem(element);
|
|
|
- SetUIElementModified(editUIElement);
|
|
|
-
|
|
|
- // Create an undo action
|
|
|
- CreateUIElementAction action;
|
|
|
- action.Define(element);
|
|
|
- group.actions.Push(action);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- SaveEditActionGroup(group);
|
|
|
-
|
|
|
- suppressUIElementChanges = false;
|
|
|
-
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-bool UIElementDelete()
|
|
|
-{
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- BeginSelectionModify();
|
|
|
-
|
|
|
- // Clear the selection now to prevent deleted elements from being reselected
|
|
|
- hierarchyList.ClearSelection();
|
|
|
-
|
|
|
- // Group for storing undo actions
|
|
|
- EditActionGroup group;
|
|
|
-
|
|
|
- for (uint i = 0; i < selectedUIElements.length; ++i)
|
|
|
- {
|
|
|
- UIElement@ element = selectedUIElements[i];
|
|
|
- if (element.parent is null)
|
|
|
- continue; // Already deleted
|
|
|
-
|
|
|
- uint index = GetListIndex(element);
|
|
|
-
|
|
|
- // Create undo action
|
|
|
- DeleteUIElementAction action;
|
|
|
- action.Define(element);
|
|
|
- group.actions.Push(action);
|
|
|
-
|
|
|
- SetUIElementModified(element);
|
|
|
- element.Remove();
|
|
|
-
|
|
|
- // If deleting only one element, select the next item in the same index
|
|
|
- if (selectedUIElements.length == 1)
|
|
|
- hierarchyList.selection = index;
|
|
|
- }
|
|
|
-
|
|
|
- SaveEditActionGroup(group);
|
|
|
-
|
|
|
- EndSelectionModify();
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-bool UIElementSelectAll()
|
|
|
-{
|
|
|
- BeginSelectionModify();
|
|
|
- Array<uint> indices;
|
|
|
- uint baseIndex = GetListIndex(editorUIElement);
|
|
|
- indices.Push(baseIndex);
|
|
|
- int baseIndent = hierarchyList.items[baseIndex].indent;
|
|
|
- for (uint i = baseIndex + 1; i < hierarchyList.numItems; ++i)
|
|
|
- {
|
|
|
- if (hierarchyList.items[i].indent <= baseIndent)
|
|
|
- break;
|
|
|
- indices.Push(i);
|
|
|
- }
|
|
|
- hierarchyList.SetSelections(indices);
|
|
|
- EndSelectionModify();
|
|
|
-
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-bool UIElementResetToDefault()
|
|
|
-{
|
|
|
- ui.cursor.shape = CS_BUSY;
|
|
|
-
|
|
|
- // Group for storing undo actions
|
|
|
- EditActionGroup group;
|
|
|
-
|
|
|
- // Reset selected elements to their default values
|
|
|
- for (uint i = 0; i < selectedUIElements.length; ++i)
|
|
|
- {
|
|
|
- UIElement@ element = selectedUIElements[i];
|
|
|
-
|
|
|
- ResetAttributesAction action;
|
|
|
- action.Define(element);
|
|
|
- group.actions.Push(action);
|
|
|
-
|
|
|
- element.ResetToDefault();
|
|
|
- action.SetInternalVars(element);
|
|
|
- element.ApplyAttributes();
|
|
|
- for (uint j = 0; j < element.numAttributes; ++j)
|
|
|
- PostEditAttribute(element, j);
|
|
|
- SetUIElementModified(element);
|
|
|
- }
|
|
|
-
|
|
|
- SaveEditActionGroup(group);
|
|
|
- attributesFullDirty = true;
|
|
|
-
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-bool UIElementChangeParent(UIElement@ sourceElement, UIElement@ targetElement)
|
|
|
-{
|
|
|
- ReparentUIElementAction action;
|
|
|
- action.Define(sourceElement, targetElement);
|
|
|
- SaveEditAction(action);
|
|
|
-
|
|
|
- sourceElement.parent = targetElement;
|
|
|
- SetUIElementModified(targetElement);
|
|
|
- return sourceElement.parent is targetElement;
|
|
|
-}
|
|
|
+// Urho3D editor UI-element handling
|
|
|
+
|
|
|
+UIElement@ editorUIElement;
|
|
|
+XMLFile@ uiElementDefaultStyle;
|
|
|
+Array<String> availableStyles;
|
|
|
+
|
|
|
+UIElement@ editUIElement;
|
|
|
+Array<Serializable@> selectedUIElements;
|
|
|
+Array<Serializable@> editUIElements;
|
|
|
+
|
|
|
+Array<XMLFile@> uiElementCopyBuffer;
|
|
|
+
|
|
|
+bool suppressUIElementChanges = false;
|
|
|
+
|
|
|
+// Registered UIElement user variable reverse mappings
|
|
|
+VariantMap uiElementVarNames;
|
|
|
+
|
|
|
+const ShortStringHash FILENAME_VAR("FileName");
|
|
|
+const ShortStringHash MODIFIED_VAR("Modified");
|
|
|
+const ShortStringHash CHILD_ELEMENT_FILENAME_VAR("ChildElemFileName");
|
|
|
+
|
|
|
+void ClearUIElementSelection()
|
|
|
+{
|
|
|
+ editUIElement = null;
|
|
|
+ selectedUIElements.Clear();
|
|
|
+ editUIElements.Clear();
|
|
|
+}
|
|
|
+
|
|
|
+void CreateRootUIElement()
|
|
|
+{
|
|
|
+ // Create a root UIElement only once here, do not confuse this with ui.root itself
|
|
|
+ editorUIElement = ui.root.CreateChild("UIElement");
|
|
|
+ editorUIElement.name = "UI";
|
|
|
+ editorUIElement.SetSize(graphics.width, graphics.height);
|
|
|
+ editorUIElement.traversalMode = TM_DEPTH_FIRST; // This is needed for root-like element to prevent artifacts
|
|
|
+
|
|
|
+ // This is needed to distinguish our own element events from Editor's UI element events
|
|
|
+ editorUIElement.elementEventSender = true;
|
|
|
+ SubscribeToEvent(editorUIElement, "ElementAdded", "HandleUIElementAdded");
|
|
|
+ SubscribeToEvent(editorUIElement, "ElementRemoved", "HandleUIElementRemoved");
|
|
|
+
|
|
|
+ // Since this root UIElement is not being handled by above handlers, update it into hierarchy list manually as another list root item
|
|
|
+ UpdateHierarchyItem(M_MAX_UNSIGNED, editorUIElement, null);
|
|
|
+}
|
|
|
+
|
|
|
+bool NewUIElement(const String&in typeName)
|
|
|
+{
|
|
|
+ // If no edit element then parented to root
|
|
|
+ UIElement@ parent = editUIElement !is null ? editUIElement : editorUIElement;
|
|
|
+ UIElement@ element = parent.CreateChild(typeName);
|
|
|
+ if (element !is null)
|
|
|
+ {
|
|
|
+ // Use the predefined UI style if set, otherwise use editor's own UI style
|
|
|
+ XMLFile@ defaultStyle = uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle;
|
|
|
+
|
|
|
+ if (editUIElement is null)
|
|
|
+ {
|
|
|
+ // If parented to root, set the internal variables
|
|
|
+ element.vars[FILENAME_VAR] = "";
|
|
|
+ element.vars[MODIFIED_VAR] = false;
|
|
|
+ // set a default UI style
|
|
|
+ element.defaultStyle = defaultStyle;
|
|
|
+ // and position the newly created element at center
|
|
|
+ CenterDialog(element);
|
|
|
+ }
|
|
|
+ // Apply the auto style
|
|
|
+ element.style = AUTO_STYLE;
|
|
|
+ // Do not allow UI subsystem to reorder children while editing the element in the editor
|
|
|
+ element.sortChildren = false;
|
|
|
+
|
|
|
+ // Create an undo action for the create
|
|
|
+ CreateUIElementAction action;
|
|
|
+ action.Define(element);
|
|
|
+ SaveEditAction(action);
|
|
|
+ SetUIElementModified(element);
|
|
|
+
|
|
|
+ FocusUIElement(element);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+void ResetSortChildren(UIElement@ element)
|
|
|
+{
|
|
|
+ element.sortChildren = false;
|
|
|
+
|
|
|
+ // Perform the action recursively for child elements
|
|
|
+ for (uint i = 0; i < element.numChildren; ++i)
|
|
|
+ ResetSortChildren(element.children[i]);
|
|
|
+}
|
|
|
+
|
|
|
+void OpenUILayout(const String&in fileName)
|
|
|
+{
|
|
|
+ if (fileName.empty)
|
|
|
+ return;
|
|
|
+
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ // Check if the UI element has been opened before
|
|
|
+ if (editorUIElement.GetChild(FILENAME_VAR, Variant(fileName)) !is null)
|
|
|
+ {
|
|
|
+ log.Warning("UI element is already opened: " + fileName);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Always load from the filesystem, not from resource paths
|
|
|
+ if (!fileSystem.FileExists(fileName))
|
|
|
+ {
|
|
|
+ log.Error("No such file: " + fileName);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ File file(fileName, FILE_READ);
|
|
|
+ if (!file.open)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // Add the UI layout's resource path in case it's necessary
|
|
|
+ SetResourcePath(GetPath(fileName), true, true);
|
|
|
+
|
|
|
+ XMLFile@ xmlFile = XMLFile();
|
|
|
+ xmlFile.Load(file);
|
|
|
+
|
|
|
+ suppressUIElementChanges = true;
|
|
|
+
|
|
|
+ // If uiElementDefaultStyle is not set then automatically fallback to use the editor's own default style
|
|
|
+ UIElement@ element = ui.LoadLayout(xmlFile, uiElementDefaultStyle);
|
|
|
+ if (element !is null)
|
|
|
+ {
|
|
|
+ element.vars[FILENAME_VAR] = fileName;
|
|
|
+ element.vars[MODIFIED_VAR] = false;
|
|
|
+
|
|
|
+ // Do not allow UI subsystem to reorder children while editing the element in the editor
|
|
|
+ ResetSortChildren(element);
|
|
|
+ // Register variable names from the 'enriched' XMLElement, if any
|
|
|
+ RegisterUIElementVar(xmlFile.root);
|
|
|
+
|
|
|
+ editorUIElement.AddChild(element);
|
|
|
+
|
|
|
+ UpdateHierarchyItem(element);
|
|
|
+ FocusUIElement(element);
|
|
|
+
|
|
|
+ ClearEditActions();
|
|
|
+ }
|
|
|
+
|
|
|
+ suppressUIElementChanges = false;
|
|
|
+}
|
|
|
+
|
|
|
+bool CloseUILayout()
|
|
|
+{
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ suppressUIElementChanges = true;
|
|
|
+
|
|
|
+ for (uint i = 0; i < selectedUIElements.length; ++i)
|
|
|
+ {
|
|
|
+ UIElement@ element = GetTopLevelUIElement(selectedUIElements[i]);
|
|
|
+ if (element !is null)
|
|
|
+ {
|
|
|
+ element.Remove();
|
|
|
+ UpdateHierarchyItem(GetListIndex(element), null, null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ hierarchyList.ClearSelection();
|
|
|
+ ClearEditActions();
|
|
|
+
|
|
|
+ suppressUIElementChanges = false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool CloseAllUILayouts()
|
|
|
+{
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ suppressUIElementChanges = true;
|
|
|
+
|
|
|
+ editorUIElement.RemoveAllChildren();
|
|
|
+ UpdateHierarchyItem(editorUIElement, true);
|
|
|
+
|
|
|
+ // Reset element ID number generator
|
|
|
+ uiElementNextID = UI_ELEMENT_BASE_ID + 1;
|
|
|
+
|
|
|
+ hierarchyList.ClearSelection();
|
|
|
+ ClearEditActions();
|
|
|
+
|
|
|
+ suppressUIElementChanges = false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool SaveUILayout(const String&in fileName)
|
|
|
+{
|
|
|
+ if (fileName.empty)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ File file(fileName, FILE_WRITE);
|
|
|
+ if (!file.open)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ UIElement@ element = GetTopLevelUIElement(editUIElement);
|
|
|
+ if (element is null)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ XMLFile@ elementData = XMLFile();
|
|
|
+ XMLElement rootElem = elementData.CreateRoot("element");
|
|
|
+ bool success = element.SaveXML(rootElem);
|
|
|
+ if (success)
|
|
|
+ {
|
|
|
+ FilterInternalVars(rootElem);
|
|
|
+ success = elementData.Save(file);
|
|
|
+ if (success)
|
|
|
+ {
|
|
|
+ element.vars[FILENAME_VAR] = fileName;
|
|
|
+ SetUIElementModified(element, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return success;
|
|
|
+}
|
|
|
+
|
|
|
+bool SaveUILayoutWithExistingName()
|
|
|
+{
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ UIElement@ element = GetTopLevelUIElement(editUIElement);
|
|
|
+ if (element is null)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ String fileName = element.GetVar(FILENAME_VAR).GetString();
|
|
|
+ if (fileName.empty)
|
|
|
+ return PickFile(); // No name yet, so pick one
|
|
|
+ else
|
|
|
+ return SaveUILayout(fileName);
|
|
|
+}
|
|
|
+
|
|
|
+void LoadChildUIElement(const String&in fileName)
|
|
|
+{
|
|
|
+ if (fileName.empty)
|
|
|
+ return;
|
|
|
+
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ if (!fileSystem.FileExists(fileName))
|
|
|
+ {
|
|
|
+ log.Error("No such file: " + fileName);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ File file(fileName, FILE_READ);
|
|
|
+ if (!file.open)
|
|
|
+ return;
|
|
|
+
|
|
|
+ XMLFile@ xmlFile = XMLFile();
|
|
|
+ xmlFile.Load(file);
|
|
|
+
|
|
|
+ suppressUIElementChanges = true;
|
|
|
+
|
|
|
+ if (editUIElement.LoadChildXML(xmlFile, uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle))
|
|
|
+ {
|
|
|
+ XMLElement rootElem = xmlFile.root;
|
|
|
+ uint index = rootElem.HasAttribute("index") ? rootElem.GetUInt("index") : editUIElement.numChildren - 1;
|
|
|
+ UIElement@ element = editUIElement.children[index];
|
|
|
+ ResetSortChildren(element);
|
|
|
+ RegisterUIElementVar(xmlFile.root);
|
|
|
+ element.vars[CHILD_ELEMENT_FILENAME_VAR] = fileName;
|
|
|
+ if (index == editUIElement.numChildren - 1)
|
|
|
+ UpdateHierarchyItem(element);
|
|
|
+ else
|
|
|
+ // If not last child, find the list index of the next sibling as the insertion index
|
|
|
+ UpdateHierarchyItem(GetListIndex(editUIElement.children[index + 1]), element, hierarchyList.items[GetListIndex(editUIElement)]);
|
|
|
+ SetUIElementModified(element);
|
|
|
+
|
|
|
+ // Create an undo action for the load
|
|
|
+ CreateUIElementAction action;
|
|
|
+ action.Define(element);
|
|
|
+ SaveEditAction(action);
|
|
|
+
|
|
|
+ FocusUIElement(element);
|
|
|
+ }
|
|
|
+
|
|
|
+ suppressUIElementChanges = false;
|
|
|
+}
|
|
|
+
|
|
|
+bool SaveChildUIElement(const String&in fileName)
|
|
|
+{
|
|
|
+ if (fileName.empty)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ File file(fileName, FILE_WRITE);
|
|
|
+ if (!file.open)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ XMLFile@ elementData = XMLFile();
|
|
|
+ XMLElement rootElem = elementData.CreateRoot("element");
|
|
|
+ bool success = editUIElement.SaveXML(rootElem);
|
|
|
+ if (success)
|
|
|
+ {
|
|
|
+ FilterInternalVars(rootElem);
|
|
|
+ success = elementData.Save(file);
|
|
|
+ if (success)
|
|
|
+ editUIElement.vars[CHILD_ELEMENT_FILENAME_VAR] = fileName;
|
|
|
+ }
|
|
|
+
|
|
|
+ return success;
|
|
|
+}
|
|
|
+
|
|
|
+void SetUIElementDefaultStyle(const String&in fileName)
|
|
|
+{
|
|
|
+ if (fileName.empty)
|
|
|
+ return;
|
|
|
+
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ // Always load from the filesystem, not from resource paths
|
|
|
+ if (!fileSystem.FileExists(fileName))
|
|
|
+ {
|
|
|
+ log.Error("No such file: " + fileName);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ File file(fileName, FILE_READ);
|
|
|
+ if (!file.open)
|
|
|
+ return;
|
|
|
+
|
|
|
+ uiElementDefaultStyle = XMLFile();
|
|
|
+ uiElementDefaultStyle.Load(file);
|
|
|
+
|
|
|
+ // Remove the existing style list to ensure it gets repopulated again with the new default style file
|
|
|
+ availableStyles.Clear();
|
|
|
+
|
|
|
+ // Refresh Attribute Inspector when it is currently showing attributes of UI-element item type as the existing styles in the style drop down list are not valid anymore
|
|
|
+ if (!editUIElements.empty)
|
|
|
+ attributesFullDirty = true;
|
|
|
+}
|
|
|
+
|
|
|
+// Prepare XPath query object only once and use it multiple times
|
|
|
+XPathQuery filterInternalVarsQuery("//attribute[@name='Variables']/variant");
|
|
|
+
|
|
|
+void FilterInternalVars(XMLElement source)
|
|
|
+{
|
|
|
+ XPathResultSet resultSet = filterInternalVarsQuery.Evaluate(source);
|
|
|
+ XMLElement resultElem = resultSet.firstResult;
|
|
|
+ while (resultElem.notNull)
|
|
|
+ {
|
|
|
+ String name = GetVariableName(resultElem.GetUInt("hash"));
|
|
|
+ if (name.empty)
|
|
|
+ {
|
|
|
+ XMLElement parent = resultElem.parent;
|
|
|
+
|
|
|
+ // If variable name is empty (or unregistered) then it is an internal variable and should be removed
|
|
|
+ if (parent.RemoveChild(resultElem))
|
|
|
+ {
|
|
|
+ // If parent does not have any children anymore then remove the parent also
|
|
|
+ if (!parent.HasChild("variant"))
|
|
|
+ parent.parent.RemoveChild(parent);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ // If it is registered then it is a user-defined variable, so 'enrich' the XMLElement to store the variable name in plaintext
|
|
|
+ resultElem.SetAttribute("name", name);
|
|
|
+ resultElem = resultElem.nextResult;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+XPathQuery registerUIElemenVarsQuery("//attribute[@name='Variables']/variant/@name");
|
|
|
+
|
|
|
+void RegisterUIElementVar(XMLElement source)
|
|
|
+{
|
|
|
+ XPathResultSet resultSet = registerUIElemenVarsQuery.Evaluate(source);
|
|
|
+ XMLElement resultAttr = resultSet.firstResult; // Since we are selecting attribute, the resultset is in attribute context
|
|
|
+ while (resultAttr.notNull)
|
|
|
+ {
|
|
|
+ String name = resultAttr.GetAttribute();
|
|
|
+ uiElementVarNames[name] = name;
|
|
|
+ resultAttr = resultAttr.nextResult;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+UIElement@ GetTopLevelUIElement(UIElement@ element)
|
|
|
+{
|
|
|
+ // Only top level UI-element contains the FILENAME_VAR
|
|
|
+ while (element !is null && !element.vars.Contains(FILENAME_VAR))
|
|
|
+ element = element.parent;
|
|
|
+ return element;
|
|
|
+}
|
|
|
+
|
|
|
+void SetUIElementModified(UIElement@ element, bool flag = true)
|
|
|
+{
|
|
|
+ element = GetTopLevelUIElement(element);
|
|
|
+ if (element !is null && element.GetVar(MODIFIED_VAR).GetBool() != flag)
|
|
|
+ {
|
|
|
+ element.vars[MODIFIED_VAR] = flag;
|
|
|
+ UpdateHierarchyItemText(GetListIndex(element), element.visible, GetUIElementTitle(element));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+XPathQuery availableStylesXPathQuery("/elements/element[@auto='false']/@type");
|
|
|
+
|
|
|
+void GetAvailableStyles()
|
|
|
+{
|
|
|
+ // Use the predefined UI style if set, otherwise use editor's own UI style
|
|
|
+ XMLFile@ defaultStyle = uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle;
|
|
|
+ XMLElement rootElem = defaultStyle.root;
|
|
|
+ XPathResultSet resultSet = availableStylesXPathQuery.Evaluate(rootElem);
|
|
|
+ XMLElement resultElem = resultSet.firstResult;
|
|
|
+ while (resultElem.notNull)
|
|
|
+ {
|
|
|
+ availableStyles.Push(resultElem.GetAttribute());
|
|
|
+ resultElem = resultElem.nextResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ availableStyles.Sort();
|
|
|
+}
|
|
|
+
|
|
|
+void PopulateStyleList(DropDownList@ styleList)
|
|
|
+{
|
|
|
+ if (availableStyles.empty)
|
|
|
+ GetAvailableStyles();
|
|
|
+
|
|
|
+ for (uint i = 0; i < availableStyles.length; ++i)
|
|
|
+ {
|
|
|
+ Text@ choice = Text();
|
|
|
+ styleList.AddItem(choice);
|
|
|
+ choice.style = "EditorEnumAttributeText";
|
|
|
+ choice.text = availableStyles[i];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+bool UIElementCut()
|
|
|
+{
|
|
|
+ return UIElementCopy() && UIElementDelete();
|
|
|
+}
|
|
|
+
|
|
|
+bool UIElementCopy()
|
|
|
+{
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ uiElementCopyBuffer.Clear();
|
|
|
+
|
|
|
+ for (uint i = 0; i < selectedUIElements.length; ++i)
|
|
|
+ {
|
|
|
+ XMLFile@ xml = XMLFile();
|
|
|
+ XMLElement rootElem = xml.CreateRoot("element");
|
|
|
+ selectedUIElements[i].SaveXML(rootElem);
|
|
|
+ uiElementCopyBuffer.Push(xml);
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+void ResetDuplicateID(UIElement@ element)
|
|
|
+{
|
|
|
+ // If it is a duplicate copy then the element ID need to be regenerated by resetting it now to empty
|
|
|
+ if (GetListIndex(element) != NO_ITEM)
|
|
|
+ element.vars[UI_ELEMENT_ID_VAR] = Variant();
|
|
|
+
|
|
|
+ // Perform the action recursively for child elements
|
|
|
+ for (uint i = 0; i < element.numChildren; ++i)
|
|
|
+ ResetDuplicateID(element.children[i]);
|
|
|
+}
|
|
|
+
|
|
|
+bool UIElementPaste()
|
|
|
+{
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ // Group for storing undo actions
|
|
|
+ EditActionGroup group;
|
|
|
+
|
|
|
+ // Have to update manually because the element ID var is not set yet when the E_ELEMENTADDED event is sent
|
|
|
+ suppressUIElementChanges = true;
|
|
|
+
|
|
|
+ for (uint i = 0; i < uiElementCopyBuffer.length; ++i)
|
|
|
+ {
|
|
|
+ XMLElement rootElem = uiElementCopyBuffer[i].root;
|
|
|
+ if (editUIElement.LoadChildXML(rootElem, null))
|
|
|
+ {
|
|
|
+ UIElement@ element = editUIElement.children[editUIElement.numChildren - 1];
|
|
|
+ ResetDuplicateID(element);
|
|
|
+ UpdateHierarchyItem(element);
|
|
|
+ SetUIElementModified(editUIElement);
|
|
|
+
|
|
|
+ // Create an undo action
|
|
|
+ CreateUIElementAction action;
|
|
|
+ action.Define(element);
|
|
|
+ group.actions.Push(action);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ SaveEditActionGroup(group);
|
|
|
+
|
|
|
+ suppressUIElementChanges = false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool UIElementDelete()
|
|
|
+{
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ BeginSelectionModify();
|
|
|
+
|
|
|
+ // Clear the selection now to prevent deleted elements from being reselected
|
|
|
+ hierarchyList.ClearSelection();
|
|
|
+
|
|
|
+ // Group for storing undo actions
|
|
|
+ EditActionGroup group;
|
|
|
+
|
|
|
+ for (uint i = 0; i < selectedUIElements.length; ++i)
|
|
|
+ {
|
|
|
+ UIElement@ element = selectedUIElements[i];
|
|
|
+ if (element.parent is null)
|
|
|
+ continue; // Already deleted
|
|
|
+
|
|
|
+ uint index = GetListIndex(element);
|
|
|
+
|
|
|
+ // Create undo action
|
|
|
+ DeleteUIElementAction action;
|
|
|
+ action.Define(element);
|
|
|
+ group.actions.Push(action);
|
|
|
+
|
|
|
+ SetUIElementModified(element);
|
|
|
+ element.Remove();
|
|
|
+
|
|
|
+ // If deleting only one element, select the next item in the same index
|
|
|
+ if (selectedUIElements.length == 1)
|
|
|
+ hierarchyList.selection = index;
|
|
|
+ }
|
|
|
+
|
|
|
+ SaveEditActionGroup(group);
|
|
|
+
|
|
|
+ EndSelectionModify();
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool UIElementSelectAll()
|
|
|
+{
|
|
|
+ BeginSelectionModify();
|
|
|
+ Array<uint> indices;
|
|
|
+ uint baseIndex = GetListIndex(editorUIElement);
|
|
|
+ indices.Push(baseIndex);
|
|
|
+ int baseIndent = hierarchyList.items[baseIndex].indent;
|
|
|
+ for (uint i = baseIndex + 1; i < hierarchyList.numItems; ++i)
|
|
|
+ {
|
|
|
+ if (hierarchyList.items[i].indent <= baseIndent)
|
|
|
+ break;
|
|
|
+ indices.Push(i);
|
|
|
+ }
|
|
|
+ hierarchyList.SetSelections(indices);
|
|
|
+ EndSelectionModify();
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool UIElementResetToDefault()
|
|
|
+{
|
|
|
+ ui.cursor.shape = CS_BUSY;
|
|
|
+
|
|
|
+ // Group for storing undo actions
|
|
|
+ EditActionGroup group;
|
|
|
+
|
|
|
+ // Reset selected elements to their default values
|
|
|
+ for (uint i = 0; i < selectedUIElements.length; ++i)
|
|
|
+ {
|
|
|
+ UIElement@ element = selectedUIElements[i];
|
|
|
+
|
|
|
+ ResetAttributesAction action;
|
|
|
+ action.Define(element);
|
|
|
+ group.actions.Push(action);
|
|
|
+
|
|
|
+ element.ResetToDefault();
|
|
|
+ action.SetInternalVars(element);
|
|
|
+ element.ApplyAttributes();
|
|
|
+ for (uint j = 0; j < element.numAttributes; ++j)
|
|
|
+ PostEditAttribute(element, j);
|
|
|
+ SetUIElementModified(element);
|
|
|
+ }
|
|
|
+
|
|
|
+ SaveEditActionGroup(group);
|
|
|
+ attributesFullDirty = true;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool UIElementChangeParent(UIElement@ sourceElement, UIElement@ targetElement)
|
|
|
+{
|
|
|
+ ReparentUIElementAction action;
|
|
|
+ action.Define(sourceElement, targetElement);
|
|
|
+ SaveEditAction(action);
|
|
|
+
|
|
|
+ sourceElement.parent = targetElement;
|
|
|
+ SetUIElementModified(targetElement);
|
|
|
+ return sourceElement.parent is targetElement;
|
|
|
+}
|