| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670 |
- // 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 StringHash FILENAME_VAR("FileName");
- const StringHash MODIFIED_VAR("Modified");
- const StringHash 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
- editorUIElement.priority = -1000; // All user-created UI elements have lowest priority so they do not cover editor's windows
- // 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)
- {
- MessageBox("UI element is already opened.\n" + fileName);
- return;
- }
- // Always load from the filesystem, not from resource paths
- if (!fileSystem.FileExists(fileName))
- {
- MessageBox("No such file.\n" + fileName);
- return;
- }
- File file(fileName, FILE_READ);
- if (!file.open)
- {
- MessageBox("Could not open file.\n" + fileName);
- 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();
- }
- else
- MessageBox("Could not load UI layout successfully!\nSee Urho3D.log for more detail.");
- suppressUIElementChanges = false;
- }
- bool CloseUILayout()
- {
- ui.cursor.shape = CS_BUSY;
- if (messageBoxCallback is null)
- {
- for (uint i = 0; i < selectedUIElements.length; ++i)
- {
- UIElement@ element = GetTopLevelUIElement(selectedUIElements[i]);
- if (element !is null && element.vars[MODIFIED_VAR].GetBool())
- {
- MessageBox@ messageBox = MessageBox("UI layout has been modified.\nContinue to close?", "Warning");
- if (messageBox.window !is null)
- {
- Button@ cancelButton = messageBox.window.GetChild("CancelButton", true);
- cancelButton.visible = true;
- cancelButton.focus = true;
- SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement");
- messageBoxCallback = @CloseUILayout;
- return false;
- }
- }
- }
- }
- else
- messageBoxCallback = null;
- 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;
- if (messageBoxCallback is null)
- {
- for (uint i = 0; i < editorUIElement.numChildren; ++i)
- {
- UIElement@ element = editorUIElement.children[i];
- if (element !is null && element.vars[MODIFIED_VAR].GetBool())
- {
- MessageBox@ messageBox = MessageBox("UI layout has been modified.\nContinue to close?", "Warning");
- if (messageBox.window !is null)
- {
- Button@ cancelButton = messageBox.window.GetChild("CancelButton", true);
- cancelButton.visible = true;
- cancelButton.focus = true;
- SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement");
- messageBoxCallback = @CloseAllUILayouts;
- return false;
- }
- }
- }
- }
- else
- messageBoxCallback = null;
- 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;
- MakeBackup(fileName);
- File file(fileName, FILE_WRITE);
- if (!file.open)
- {
- MessageBox("Could not open file.\n" + fileName);
- 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);
- RemoveBackup(success, fileName);
- if (success)
- {
- FilterInternalVars(rootElem);
- success = elementData.Save(file);
- if (success)
- {
- element.vars[FILENAME_VAR] = fileName;
- SetUIElementModified(element, false);
- }
- }
- if (!success)
- MessageBox("Could not save UI layout successfully!\nSee Urho3D.log for more detail.");
- 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))
- {
- MessageBox("No such file.\n" + fileName);
- return;
- }
- File file(fileName, FILE_READ);
- if (!file.open)
- {
- MessageBox("Could not open file.\n" + fileName);
- 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;
- MakeBackup(fileName);
- File file(fileName, FILE_WRITE);
- if (!file.open)
- {
- MessageBox("Could not open file.\n" + fileName);
- return false;
- }
- XMLFile@ elementData = XMLFile();
- XMLElement rootElem = elementData.CreateRoot("element");
- bool success = editUIElement.SaveXML(rootElem);
- RemoveBackup(success, fileName);
-
- if (success)
- {
- FilterInternalVars(rootElem);
- success = elementData.Save(file);
- if (success)
- editUIElement.vars[CHILD_ELEMENT_FILENAME_VAR] = fileName;
- }
- if (!success)
- MessageBox("Could not save child UI element successfully!\nSee Urho3D.log for more detail.");
- 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))
- {
- MessageBox("No such file.\n" + fileName);
- return;
- }
- File file(fileName, FILE_READ);
- if (!file.open)
- {
- MessageBox("Could not open file.\n" + fileName);
- 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;
- }
|