724 lines
22 KiB
ActionScript
724 lines
22 KiB
ActionScript
// 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;
|
|
|
|
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) !is null)
|
|
{
|
|
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 = GetVarName(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();
|
|
globalVarNames[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(bool duplication = false)
|
|
{
|
|
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;
|
|
|
|
UIElement@ pasteElement;
|
|
|
|
if (!duplication)
|
|
pasteElement = editUIElement;
|
|
else
|
|
{
|
|
if (editUIElement.parent !is null)
|
|
pasteElement = editUIElement.parent;
|
|
else
|
|
pasteElement = editUIElement;
|
|
}
|
|
|
|
if (pasteElement.LoadChildXML(rootElem, null) !is null)
|
|
{
|
|
UIElement@ element = pasteElement.children[pasteElement.numChildren - 1];
|
|
|
|
ResetDuplicateID(element);
|
|
UpdateHierarchyItem(element);
|
|
SetUIElementModified(pasteElement);
|
|
|
|
// Create an undo action
|
|
CreateUIElementAction action;
|
|
action.Define(element);
|
|
group.actions.Push(action);
|
|
}
|
|
}
|
|
|
|
SaveEditActionGroup(group);
|
|
|
|
suppressUIElementChanges = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UIElementDuplicate()
|
|
{
|
|
ui.cursor.shape = CS_BUSY;
|
|
|
|
Array<XMLFile@> copy = uiElementCopyBuffer;
|
|
UIElementCopy();
|
|
UIElementPaste(true);
|
|
uiElementCopyBuffer = copy;
|
|
|
|
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;
|
|
}
|
|
|
|
bool UIElementReorder(UIElement@ sourceElement, UIElement@ targetElement)
|
|
{
|
|
if (sourceElement is null || targetElement is null || sourceElement.parent is null || sourceElement.parent !is targetElement.parent)
|
|
return false;
|
|
if (sourceElement is targetElement)
|
|
return true; // No-op
|
|
UIElement@ parent = sourceElement.parent;
|
|
uint destIndex = parent.FindChild(targetElement);
|
|
Print("Reorder to dest index " + destIndex);
|
|
|
|
ReorderUIElementAction action;
|
|
action.Define(sourceElement, destIndex);
|
|
SaveEditAction(action);
|
|
PerformReorder(parent, sourceElement, destIndex);
|
|
|
|
return true;
|
|
}
|
|
|
|
void PerformReorder(UIElement@ parent, UIElement@ child, uint destIndex)
|
|
{
|
|
suppressSceneChanges = true;
|
|
|
|
parent.RemoveChild(child);
|
|
parent.InsertChild(destIndex, child);
|
|
UpdateHierarchyItem(parent); // Force update to make sure the order is current
|
|
SetUIElementModified(parent);
|
|
|
|
suppressSceneChanges = false;
|
|
}
|