----------------- ----------------- -- DISPLAYERS -- ----------------- ----------------- -- Displayer are objects attached to instance in the scenario -- They react to modification events (creations of new objects such as nps, groups ...) -- and update their display accordingly -- There is zero or one displayer attached per category of display for each instance in the map -- For now this include : -- UI displayers : - They update the scenario window to display new things added to the map -- Property displayers : - They update the property sheet for an instance when one is displayed -- Visual displayers : - For now they are only implemented in C++. Their fonction is to update the display of a instance -- - in the 3D scene -- -- Displayer at attached at creation time by the C++ code -- The displayers to add to a specific object are given its the class definition -- (see r2_base_class.lua for details) -- helper : update the context toolbar for the given instance if it is the current selected instance local function updateContextToolbar(instance) if r2:getSelectedInstance() == instance then r2.ContextualCommands:update() end end ----------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------- -- The following code describes how to create a basic displayer that just -- output the events it handles in the log -- Mots of the time, when creating a new displayer, one will -- just construct an existing displayer, and redefine methods of interest, -- possibly calling the parent one function r2:exampleUIDisplayer() local handler = {} local ct = colorTag(0, 0, 255) ------------------------------------------------ -- Called by C++ at creation function handler:onCreate(instance) debugInfo(ct .. "Instance " .. instance.InstanceId .." was created") end ------------------------------------------------ -- Called by C++ just before object is removed (so properties are still readable) function handler:onErase(instance) debugInfo(ct .. "Instance " .. instance.InstanceId .." was erased") end ------------------------------------------------ -- Called by C++ just before object is moved in the object hierarchy function handler:onPreHrcMove(instance) updateContextToolbar(instance) debugInfo(ct .. "Instance " .. instance.InstanceId .." is about to move") end ------------------------------------------------ -- Called by C++ just after object is move in the object hierarchy function handler:onPostHrcMove(instance) updateContextToolbar(instance) debugInfo(ct .. "Instance " .. instance.InstanceId .." has moved") end ------------------------------------------------ -- Called by C++ just after object is highlighted by mouse function handler:onFocus(instance, hasFocus) if (instance.User.HasFocus ~= hasFocus) then if hasFocus == true then debugInfo(ct .. "Instance " .. instance.InstanceId .." has gained focus") else debugInfo(ct .. "Instance " .. instance.InstanceId .." has lost focus") end instance.User.HasFocus = hasFocus end end ------------------------------------------------ -- Called by C++ just after object has been selected function handler:onSelect(instance, isSelected) if (isSelected == true) then debugInfo(ct .. "Instance " .. instance.InstanceId .." is selected") else debugInfo(ct .. "Instance " .. instance.InstanceId .." is unselected") end end ------------------------------------------------ -- Called by C++ when an attribute of this object has been modified -- An attribute inside this object has been modified -- attributeName :Name of the attribute inside this object, as given by its class definition. If the attribute -- is an array, then an additionnal parameter gives the index of the element being modified in the array (or -1 if the whole array is set) function handler:onAttrModified(instance, attributeName, indexInArray) updateContextToolbar(instance) debugInfo(ct .. "Instance " .. instance.InstanceId .." has an attribute modified : " .. attributeName) end end function r2:onInstanceSelectedInTree(id) -- is there's an active pick tool then local currTool = r2:getCurrentTool() if currTool and currTool:isPickTool() then local tree = getUICaller() tree:cancelNextSelectLine() -- don't want real selection, actually ... if currTool:canPick() then currTool:pick() end -- no-op else ... return end --debugInfo("Seleting instance with id = " .. tostring(id) ) r2:setSelectedInstanceId(id) end function r2:onInstanceRightClickInTree(id) r2:setSelectedInstanceId(id) r2:displayContextMenu() end r2.VerboseEvents = false; -- before to go to "test mode", store opened/closed nodes in scenario window tree -- to correctly initialize tree when go back in edition mode r2.storedClosedTreeNodes = {} ----------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------- -- displayer that update the tree control (scenario window) function r2:defaultUIDisplayer() local function eventDebugInfo(msg) if r2.VerboseEvents == true then debugInfo(msg) end end local handler = {} local ct = colorTag(255, 0, 255) ------------------------------------------------ -- helper function : notify current act ui displayer that its quota has been modified function handler:updateCurrentActQuota() -- defer update to the next frame (many element can be added at once) r2.UIMainLoop.LeftQuotaModified = true end ------------------------------------------------ function handler:onCut(instance, cutted) -- NOT IMPLEMENTED -- debugInfo("On cut " .. tostring(cutted)) -- local tree = getUI(r2.InstanceTreePath) -- debugInfo(tostring(select(cutted, 127, 255))) -- instance.User.TreeNode.Color.G = select(cutted, 0, 255) -- tree:forceRebuild() end ------------------------------------------------ function handler:onCreate(instance) --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." was created") self:addTreeNode(instance) -- if my quota is not 0, then we should update -- the current act quota .. --if instance:getUsedQuota() ~= 0 then -- self:updateCurrentActQuota() --end if instance:hasScenarioCost() ~= false then self:updateCurrentActQuota() end end ------------------------------------------------ function handler:onPostCreate(instance) -- Special : if the cookie 'AskName' is set (by C++ or lua), then show property and ask name -- to user for that object if instance.User.AskName then if instance.User.ShowProps then r2:showProperties(instance) instance.User.ShowProps = nil end if instance.User.Select then r2:setSelectedInstanceId(instance.InstanceId) end local propWindow = r2.CurrentPropertyWindow -- tmp : quick & dirty access to the widget ... if propWindow and propWindow.active then local editBox = propWindow:find("Name"):find("eb") if editBox then setCaptureKeyboard(editBox) editBox:setSelectionAll() end end instance.User.AskName = nil -- get rid of cookie end -- Special : if the cookie 'Select' is set (by C++ or lua), then the object should be selected after creation if instance.User.Select then r2:setSelectedInstanceId(instance.InstanceId) end if type(instance.User.CreateFunc) == "function" then instance.User.CreateFunc(instance) end end ------------------------------------------------ function handler:onErase(instance) --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." was erased") self:removeTreeNode(instance) -- if my quota is not 0, then we should update -- the current act quota .. if instance:hasScenarioCost() ~= false then self:updateCurrentActQuota() end end ------------------------------------------------ function handler:onPreHrcMove(instance) updateContextToolbar(instance) --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." is about to move") self:removeTreeNode(instance) end ------------------------------------------------ function handler:onPostHrcMove(instance) -- if parent is a group, for its creation you don't know category of children : people or creature -- you check it for first child local parent = instance.ParentInstance if instance:isGrouped() and parent.Components.Size==1 then self:onErase(parent) self:onCreate(parent) self:onPostCreate(parent) end updateContextToolbar(instance) --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." has moved") --eventDebugInfo(ct .. "New parent is " .. instance.ParentInstance.InstanceId) local nodes = self:addTreeNode(instance) if (r2:getSelectedInstance() == instance) and nodes then for k, node in pairs(nodes) do assert(node) assert(node:getParentTree()) assert(node:getParentTree().selectNodeById) node:getParentTree():selectNodeById(node.Id, false) end end -- if my quota is not 0, then we should update -- the current act quota .. if instance:hasScenarioCost() ~= false then self:updateCurrentActQuota() end -- if instance has Components, its children's nodes have been deleted at onPreHrcMove call if instance.Components then for i=0, instance.Components.Size-1 do local child = instance.Components[i] self:onCreate(child) end self:onPostCreate(instance) end end ------------------------------------------------ function handler:onFocus(instance, hasFocus) if (instance.User.HasFocus ~= hasFocus) then if hasFocus == true then --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." has gained focus") else --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." has lost focus") end instance.User.HasFocus = hasFocus end end ------------------------------------------------ function handler:onSelect(instance, isSelected) if not instance.User.TreeNodes then return end for k, treeNode in pairs(instance.User.TreeNodes) do if not (treeNode == nil or treeNode.isNil == true) then local tree = treeNode:getParentTree() if (isSelected == true) then --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." is selected") tree:selectNodeById(instance.InstanceId, false) else --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." is unselected") tree:unselect() end end end end ------------------------------------------------ function handler:onAttrModified(instance, attributeName, indexInArray) if attributeName == "Position" or attributeName == "Angle" then return end if attributeName == "Selectable" then self:removeTreeNode(instance) self:addTreeNode(instance) end updateContextToolbar(instance) if not instance.User.TreeNodes then return end local nodes = instance.User.TreeNodes for k, node in pairs(nodes) do local tree = node:getParentTree() if attributeName == 'Name' then setupNodeName(instance) if node:getFather() then node:getFather():sortByBitmap() end tree:forceRebuild() tree:selectNodeById(node.Id, false) -- reforce the selection end end if attr == "Ghost" then if instance.Ghost then self:removeTreeNode(instance) end end --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." has an attribute modified : " .. attributeName) end function setupNodeName(instance) local treeNodes = instance.User.TreeNodes if not treeNodes then return end for k, treeNode in pairs(treeNodes) do if not (treeNode == nil or treeNode.isNil == true) then local tree = treeNode:getParentTree() treeNode.Text = instance:getDisplayName() if tree then -- nb : tree may be nil if node is setupped before being attached to its parent tree tree:forceRebuild() end end end end function handler:storeClosedTreeNodes() function downInTree(node, nodeTable) for i=0, node:getNumChildren()-1 do local child = node:getChild(i) assert(child) nodeTable[child.Id] = child.Opened if child:getNumChildren()>0 then downInTree(child, nodeTable) end end end r2.storedClosedTreeNodes = {} -- scenary objects r2.storedClosedTreeNodes[r2.Scenario:getBaseAct().InstanceId] = {} local objectNodes = r2.storedClosedTreeNodes[r2.Scenario:getBaseAct().InstanceId] local container = getUI("ui:interface:r2ed_scenario") --local objectsRoot = container:find("content_tree_list"):getRootNode():getNodeFromId("scenery_objects") local objectsRoot = container:find("content_tree_list"):getRootNode() assert(objectsRoot) downInTree(objectsRoot, objectNodes) -- entities and components if r2.Scenario.Acts.Size>1 then for i=1, r2.Scenario.Acts.Size-1 do local act = r2.Scenario.Acts[i] local peopleRoot = act:getContentTree():getRootNode():getNodeFromId("people") assert(peopleRoot) local creatureRoot = act:getContentTree():getRootNode():getNodeFromId("creatures") assert(creatureRoot) --local componentRoot = act:getMacroContentTree():getRootNode():getNodeFromId("macro_components") local componentRoot = act:getMacroContentTree():getRootNode() assert(componentRoot) r2.storedClosedTreeNodes[act.InstanceId] = {} local actNodes = r2.storedClosedTreeNodes[act.InstanceId] downInTree(peopleRoot, actNodes) downInTree(creatureRoot, actNodes) downInTree(componentRoot, actNodes) end end end function handler:addPermanentNodes() if r2.ScenarioInstanceId then local scenario = r2:getInstanceFromId(r2.ScenarioInstanceId) if scenario and scenario.Acts.Size>0 then local addToTreesTable = {} scenario:getBaseAct():appendInstancesByType(addToTreesTable, "LogicEntity") for k, instance in pairs(addToTreesTable) do self:addTreeNode(instance) end end end end -- private function handler:addTreeNode(instance) if instance.Ghost then return nil end local parentNodes = instance:getParentTreeNode() if parentNodes==nil then return nil end if instance.User.TreeNodes==nil then instance.User.TreeNodes = {} end for actId,parentNode in pairs(parentNodes) do local alreadyAdded = false for k2, treeNode in pairs(instance.User.TreeNodes) do if not (treeNode==nil or treeNode.isNil==true) then local father = treeNode:getFather() if father==parentNode then alreadyAdded=true break end end end if not alreadyAdded then if parentNode == nil then return nil -- one of the ancestors may be unselectable end if not instance.SelectableFromRoot then return nil end local tree = parentNode:getParentTree() local treeNode = SNode() -- store reference in object table.insert(instance.User.TreeNodes, treeNode) treeNode.Bitmap = instance:getPermanentStatutIcon() local openTree = true if r2.storedClosedTreeNodes[actId] then openTree = (r2.storedClosedTreeNodes[actId][instance.InstanceId]==true) end treeNode.Opened = openTree treeNode.Id = instance.InstanceId treeNode.AHName = "lua" local ahParams = "r2:onInstanceSelectedInTree('" .. instance.InstanceId .. "')" --eventDebugInfo(ahParams) treeNode.AHParams = ahParams treeNode.AHNameRight = "lua" treeNode.AHParamsRight = "r2:onInstanceRightClickInTree('" .. instance.InstanceId .. "')" treeNode.AHNameClose = "lua" treeNode.AHParamsClose = "r2.storedClosedTreeNodes = {}" setupNodeName(instance) assert(parentNode) parentNode:addChildSortedByBitmap(treeNode) parentNode.Show = (parentNode:getNumChildren() ~= 0) tree:forceRebuild() end end return instance.User.TreeNodes end function handler:removeTreeNode(instance) local nodes = instance.User.TreeNodes if nodes == nil or nodes.isNil then return end for k, node in pairs(nodes) do if not (node == nil or node.isNil == true) then local tree = node:getParentTree() if node:getFather().isNil then if (node == node:getParentTree():getRootNode()) then --debugInfo("ROOT NODE") node:getParentTree():setRootNode(nil) else --debugInfo("ISOLATED NODE") deleteReflectable(node) -- isolated node (the tree was never built ?) end else -- update parent node visibility only if a direct son of the root node if node:getFather() then if (node:getFather():getFather() == tree:getRootNode()) then node:getFather().Show = (node:getFather():getNumChildren() > 1) end node:getFather():deleteChild(node) end end tree:forceRebuild() end end instance.User.TreeNodes = nil end return handler end ----------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------- -- special display for groups in scenario window function r2:groupUIDisplayer() local handler = self:defaultUIDisplayer() function handler:updateLeaderColor(instance) if not instance.User.TreeNodes then return end for k, node in pairs(instance.User.TreeNodes) do local tree = node:getParentTree() for i = 0, instance.Components.Size - 1 do --debugInfo("I = " .. tostring(i)) local treeNodes = instance.Components[i].User.TreeNodes if treeNodes then for k2, treeNode in pairs(treeNodes) do if i == 0 then treeNode.Color = CRGBA(255, 0, 0) -- mark leader in red else treeNode.Color = CRGBA(255, 255, 255) end end end end tree:forceRebuild() end end -- local oldOnAttrModified = handler.onAttrModified function handler:onAttrModified(instance, attrName, indexInArray) if attrName == "Components" then self:updateLeaderColor(instance) end oldOnAttrModified(self, instance, attrName, indexInArray) end -- -- local oldOnCreate = handler.onCreate -- function handler:onCreate(instance) -- debugInfo("On create group") -- oldOnCreate(self, instance) -- end -- local oldOnPostCreate = handler.onPostCreate function handler:onPostCreate(instance) oldOnPostCreate(self, instance) self:updateLeaderColor(instance) end -- return handler end ----------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------- -- Displayer for ACTS. In the ui, acts are added into the act combo box -- -- in the environment of the container we store a table that gives the act Instance id, -- and the index of the tree control for each line in the combo box -- Table has the following look -- ActTable = { { Act = ..., TreeIndex = ... }, -- combo box line 1 -- { Act = ..., TreeIndex = ... }, -- combo box line 2 -- { Act = ..., TreeIndex = ... }, -- combo box line 3 etc. -- } r2.ActUIDisplayer = {} r2.ActUIDisplayer.ActTable = {} -- table that map each line of the combo box to an act r2.ActUIDisplayer.LastSelfCreatedActInstanceId = nil -- id of the last act created by the pionner (not by another pionner) -- When created, an act change will automatically occur ------------------------------------------------ -- helper function : notify current act ui displayer that its quota has been modified function r2.ActUIDisplayer:updateCurrentActQuota() -- defer update to the next frame (many element can be added at once) r2.UIMainLoop.LeftQuotaModified = true end ------------------------------------------------ function r2.ActUIDisplayer:updateActName(act) if act and not act:isBaseAct() then local actTable = self:getActTable() for index, entry in pairs(actTable) do if entry.Act == act then local comboBox = self:getActComboBox() local actTitle = act:getName() if act==r2.Scenario:getCurrentAct() then actTitle = actTitle .. " [" .. i18n.get("uiR2EDCurrentActComboBox"):toUtf8() .."]" end local text = ucstring() text:fromUtf8(actTitle) comboBox:setText(index - 1, text) return end end end end ------------------------------------------------ function r2.ActUIDisplayer:onAttrModified(instance, attributeName, indexInArray) -- if title is modified, then must update names of all entities in the scene if attributeName == "Name" then local npcs = {} r2:getCurrentAct():appendInstancesByType(npcs, "Npc") for k, npc in pairs(npcs) do npc.DisplayerVisual:updateName() end self:updateActName(instance) end end ------------------------------------------------ function r2.ActUIDisplayer:onCreate(act) local container = self:getContainer() local comboBox = self:getActComboBox() local tree, macroTree if not act:isBaseAct() then local text = ucstring() local index = r2.logicComponents:searchElementIndex(act)-2 local actTitle = act:getName() if type(actTitle) ~= "string" then text:fromUtf8("bad type for title : " .. type(actTitle)) comboBox:insertText(index, text) else text:fromUtf8(actTitle) comboBox:insertText(index, text) end tree = self:findFreeTreeCtrl() macroTree = self:findFreeTreeCtrl(true) local actTable = self:getActTable() table.insert(actTable, index+1, { Act = act, Tree = tree , MacroTree = macroTree}) end -- store tree in the act for future insertion of items act.User.ContentTree = tree act.User.MacroContentTree = macroTree self:updateCurrentActQuota() -- add permanent nodes to act node r2:defaultUIDisplayer():addPermanentNodes() end ------------------------------------------------ function r2.ActUIDisplayer:onPostCreate(act) -- when a new act is created, select this act as the default if act.InstanceId == self.LastSelfCreatedActInstanceId then -- the act was just created by pionner on that computer, so change right now r2.ScenarioWindow:setAct(act) self.LastSelfCreatedActInstanceId = nil end r2.ScenarioWindow:updateUIFromCurrentAct() self:updateCurrentActQuota() end ------------------------------------------------ function r2.ActUIDisplayer:onErase(erasedAct) -- clean tree content local tree = erasedAct.User.ContentTree local macroTree = erasedAct.User.MacroContentTree if tree then r2:cleanTreeNode(tree, "people") r2:cleanTreeNode(tree, "creatures") end if macroTree then --r2:cleanTreeNode(macroTree, "macro_components") r2:cleanTreeRootNode(macroTree) end local actTable = self:getActTable() for index, entry in pairs(actTable) do if entry.Act == erasedAct then self:getActComboBox():removeTextByIndex(index - 1) table.remove(actTable, index) return end end self:updateCurrentActQuota() end ------------------------------------------------ function r2.ActUIDisplayer:getActTable() return self.ActTable end ------------------------------------------------ function r2.ActUIDisplayer:getContainer() return getUI("ui:interface:r2ed_scenario") end ------------------------------------------------ function r2.ActUIDisplayer:getActComboBox() return self:getContainer():find("act_combo_box") end ----------------------------------------------- function r2.ActUIDisplayer:findFreeTreeCtrl(macroTree) local treeName = "act_tree_" if macroTree==true then treeName="macro_act_tree_" end for i = 0, r2:getMaxNumberOfAdditionnalActs() - 1 do local tree = self:getContainer():find(treeName .. tostring(i)) local used = false for index, entry in pairs(self:getActTable()) do local entryTree = entry.Tree if macroTree==true then entryTree = entry.MacroTree end if entryTree == tree then used = true break end end if not used then return tree end end return nil end function r2:createActUIDisplayer() return r2.ActUIDisplayer end