Merge branch 'fixes' into main/gingo-test
[ryzomcore.git] / ryzom / common / data_common / r2 / r2_ui_displayers.lua
blob45fd1d14e0a47e7dfbd5bf115fc5b70e9dcaca92
1 -----------------
2 -----------------
3 -- DISPLAYERS --
4 -----------------
5 -----------------
7 -- Displayer are objects attached to instance in the scenario
8 -- They react to modification events (creations of new objects such as nps, groups ...)
9 -- and update their display accordingly
10 -- There is zero or one displayer attached per category of display for each instance in the map
11 -- For now this include :
12 -- UI displayers : - They update the scenario window to display new things added to the map
13 -- Property displayers : - They update the property sheet for an instance when one is displayed
14 -- Visual displayers : - For now they are only implemented in C++. Their fonction is to update the display of a instance
15 -- - in the 3D scene
17 -- Displayer at attached at creation time by the C++ code
18 -- The displayers to add to a specific object are given its the class definition
19 -- (see r2_base_class.lua for details)
21 -- helper : update the context toolbar for the given instance if it is the current selected instance
22 local function updateContextToolbar(instance)
23 if r2:getSelectedInstance() == instance then
24 r2.ContextualCommands:update()
25 end
26 end
31 -----------------------------------------------------------------------------------------------------
32 -----------------------------------------------------------------------------------------------------
33 -----------------------------------------------------------------------------------------------------
35 -- The following code describes how to create a basic displayer that just
36 -- output the events it handles in the log
37 -- Mots of the time, when creating a new displayer, one will
38 -- just construct an existing displayer, and redefine methods of interest,
39 -- possibly calling the parent one
41 function r2:exampleUIDisplayer()
42 local handler = {}
43 local ct = colorTag(0, 0, 255)
44 ------------------------------------------------
45 -- Called by C++ at creation
46 function handler:onCreate(instance)
47 debugInfo(ct .. "Instance " .. instance.InstanceId .." was created")
48 end
49 ------------------------------------------------
50 -- Called by C++ just before object is removed (so properties are still readable)
51 function handler:onErase(instance)
52 debugInfo(ct .. "Instance " .. instance.InstanceId .." was erased")
53 end
54 ------------------------------------------------
55 -- Called by C++ just before object is moved in the object hierarchy
56 function handler:onPreHrcMove(instance)
57 updateContextToolbar(instance)
58 debugInfo(ct .. "Instance " .. instance.InstanceId .." is about to move")
59 end
60 ------------------------------------------------
61 -- Called by C++ just after object is move in the object hierarchy
62 function handler:onPostHrcMove(instance)
63 updateContextToolbar(instance)
64 debugInfo(ct .. "Instance " .. instance.InstanceId .." has moved")
65 end
66 ------------------------------------------------
67 -- Called by C++ just after object is highlighted by mouse
68 function handler:onFocus(instance, hasFocus)
69 if (instance.User.HasFocus ~= hasFocus) then
70 if hasFocus == true then
71 debugInfo(ct .. "Instance " .. instance.InstanceId .." has gained focus")
72 else
73 debugInfo(ct .. "Instance " .. instance.InstanceId .." has lost focus")
74 end
75 instance.User.HasFocus = hasFocus
76 end
77 end
78 ------------------------------------------------
79 -- Called by C++ just after object has been selected
80 function handler:onSelect(instance, isSelected)
81 if (isSelected == true) then
82 debugInfo(ct .. "Instance " .. instance.InstanceId .." is selected")
83 else
84 debugInfo(ct .. "Instance " .. instance.InstanceId .." is unselected")
85 end
86 end
87 ------------------------------------------------
88 -- Called by C++ when an attribute of this object has been modified
89 -- An attribute inside this object has been modified
90 -- attributeName :Name of the attribute inside this object, as given by its class definition. If the attribute
91 -- 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)
92 function handler:onAttrModified(instance, attributeName, indexInArray)
93 updateContextToolbar(instance)
94 debugInfo(ct .. "Instance " .. instance.InstanceId .." has an attribute modified : " .. attributeName)
95 end
96 end
99 function r2:onInstanceSelectedInTree(id)
100 -- is there's an active pick tool then
101 local currTool = r2:getCurrentTool()
102 if currTool and currTool:isPickTool() then
103 local tree = getUICaller()
104 tree:cancelNextSelectLine() -- don't want real selection, actually ...
105 if currTool:canPick() then
106 currTool:pick()
107 end
108 -- no-op else ...
109 return
111 --debugInfo("Seleting instance with id = " .. tostring(id) )
112 r2:setSelectedInstanceId(id)
115 function r2:onInstanceRightClickInTree(id)
116 r2:setSelectedInstanceId(id)
117 r2:displayContextMenu()
121 r2.VerboseEvents = false;
123 -- before to go to "test mode", store opened/closed nodes in scenario window tree
124 -- to correctly initialize tree when go back in edition mode
125 r2.storedClosedTreeNodes = {}
127 -----------------------------------------------------------------------------------------------------
128 -----------------------------------------------------------------------------------------------------
129 -----------------------------------------------------------------------------------------------------
131 -- displayer that update the tree control (scenario window)
132 function r2:defaultUIDisplayer()
133 local function eventDebugInfo(msg)
134 if r2.VerboseEvents == true then
135 debugInfo(msg)
139 local handler = {}
140 local ct = colorTag(255, 0, 255)
141 ------------------------------------------------
142 -- helper function : notify current act ui displayer that its quota has been modified
143 function handler:updateCurrentActQuota()
144 -- defer update to the next frame (many element can be added at once)
145 r2.UIMainLoop.LeftQuotaModified = true
147 ------------------------------------------------
148 function handler:onCut(instance, cutted)
149 -- NOT IMPLEMENTED
150 -- debugInfo("On cut " .. tostring(cutted))
151 -- local tree = getUI(r2.InstanceTreePath)
152 -- debugInfo(tostring(select(cutted, 127, 255)))
153 -- instance.User.TreeNode.Color.G = select(cutted, 0, 255)
154 -- tree:forceRebuild()
155 end
156 ------------------------------------------------
157 function handler:onCreate(instance)
158 --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." was created")
159 self:addTreeNode(instance)
160 -- if my quota is not 0, then we should update
161 -- the current act quota ..
162 --if instance:getUsedQuota() ~= 0 then
163 -- self:updateCurrentActQuota()
164 --end
165 if instance:hasScenarioCost() ~= false then
166 self:updateCurrentActQuota()
170 ------------------------------------------------
171 function handler:onPostCreate(instance)
173 -- Special : if the cookie 'AskName' is set (by C++ or lua), then show property and ask name
174 -- to user for that object
175 if instance.User.AskName then
176 if instance.User.ShowProps then
177 r2:showProperties(instance)
178 instance.User.ShowProps = nil
179 end
180 if instance.User.Select then
181 r2:setSelectedInstanceId(instance.InstanceId)
182 end
183 local propWindow = r2.CurrentPropertyWindow
185 -- tmp : quick & dirty access to the widget ...
186 if propWindow and propWindow.active then
187 local editBox = propWindow:find("Name"):find("eb")
188 if editBox then
189 setCaptureKeyboard(editBox)
190 editBox:setSelectionAll()
193 instance.User.AskName = nil -- get rid of cookie
194 end
195 -- Special : if the cookie 'Select' is set (by C++ or lua), then the object should be selected after creation
196 if instance.User.Select then
197 r2:setSelectedInstanceId(instance.InstanceId)
199 if type(instance.User.CreateFunc) == "function" then
200 instance.User.CreateFunc(instance)
203 ------------------------------------------------
204 function handler:onErase(instance)
205 --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." was erased")
206 self:removeTreeNode(instance)
207 -- if my quota is not 0, then we should update
208 -- the current act quota ..
209 if instance:hasScenarioCost() ~= false then
210 self:updateCurrentActQuota()
211 end
213 ------------------------------------------------
214 function handler:onPreHrcMove(instance)
215 updateContextToolbar(instance)
216 --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." is about to move")
217 self:removeTreeNode(instance)
219 ------------------------------------------------
220 function handler:onPostHrcMove(instance)
222 -- if parent is a group, for its creation you don't know category of children : people or creature
223 -- you check it for first child
224 local parent = instance.ParentInstance
225 if instance:isGrouped() and parent.Components.Size==1 then
226 self:onErase(parent)
227 self:onCreate(parent)
228 self:onPostCreate(parent)
231 updateContextToolbar(instance)
232 --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." has moved")
233 --eventDebugInfo(ct .. "New parent is " .. instance.ParentInstance.InstanceId)
234 local nodes = self:addTreeNode(instance)
235 if (r2:getSelectedInstance() == instance) and nodes then
236 for k, node in pairs(nodes) do
237 assert(node)
238 assert(node:getParentTree())
239 assert(node:getParentTree().selectNodeById)
240 node:getParentTree():selectNodeById(node.Id, false)
243 -- if my quota is not 0, then we should update
244 -- the current act quota ..
245 if instance:hasScenarioCost() ~= false then
246 self:updateCurrentActQuota()
249 -- if instance has Components, its children's nodes have been deleted at onPreHrcMove call
250 if instance.Components then
251 for i=0, instance.Components.Size-1 do
252 local child = instance.Components[i]
253 self:onCreate(child)
256 self:onPostCreate(instance)
259 ------------------------------------------------
260 function handler:onFocus(instance, hasFocus)
261 if (instance.User.HasFocus ~= hasFocus) then
262 if hasFocus == true then
263 --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." has gained focus")
264 else
265 --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." has lost focus")
267 instance.User.HasFocus = hasFocus
268 end
270 ------------------------------------------------
271 function handler:onSelect(instance, isSelected)
272 if not instance.User.TreeNodes then
273 return
274 end
275 for k, treeNode in pairs(instance.User.TreeNodes) do
277 if not (treeNode == nil or treeNode.isNil == true) then
279 local tree = treeNode:getParentTree()
280 if (isSelected == true) then
281 --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." is selected")
282 tree:selectNodeById(instance.InstanceId, false)
283 else
284 --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." is unselected")
285 tree:unselect()
290 ------------------------------------------------
291 function handler:onAttrModified(instance, attributeName, indexInArray)
292 if attributeName == "Position" or attributeName == "Angle" then
293 return
295 if attributeName == "Selectable" then
296 self:removeTreeNode(instance)
297 self:addTreeNode(instance)
300 updateContextToolbar(instance)
301 if not instance.User.TreeNodes then
302 return
303 end
304 local nodes = instance.User.TreeNodes
305 for k, node in pairs(nodes) do
306 local tree = node:getParentTree()
307 if attributeName == 'Name' then
308 setupNodeName(instance)
309 if node:getFather() then
310 node:getFather():sortByBitmap()
312 tree:forceRebuild()
313 tree:selectNodeById(node.Id, false) -- reforce the selection
317 if attr == "Ghost" then
318 if instance.Ghost then
319 self:removeTreeNode(instance)
322 --eventDebugInfo(ct .. "Instance " .. instance.InstanceId .." has an attribute modified : " .. attributeName)
325 function setupNodeName(instance)
326 local treeNodes = instance.User.TreeNodes
327 if not treeNodes then return end
329 for k, treeNode in pairs(treeNodes) do
330 if not (treeNode == nil or treeNode.isNil == true) then
331 local tree = treeNode:getParentTree()
333 treeNode.Text = instance:getDisplayName()
334 if tree then -- nb : tree may be nil if node is setupped before being attached to its parent tree
335 tree:forceRebuild()
341 function handler:storeClosedTreeNodes()
343 function downInTree(node, nodeTable)
345 for i=0, node:getNumChildren()-1 do
346 local child = node:getChild(i)
347 assert(child)
349 nodeTable[child.Id] = child.Opened
351 if child:getNumChildren()>0 then
352 downInTree(child, nodeTable)
357 r2.storedClosedTreeNodes = {}
359 -- scenary objects
360 r2.storedClosedTreeNodes[r2.Scenario:getBaseAct().InstanceId] = {}
361 local objectNodes = r2.storedClosedTreeNodes[r2.Scenario:getBaseAct().InstanceId]
363 local container = getUI("ui:interface:r2ed_scenario")
364 --local objectsRoot = container:find("content_tree_list"):getRootNode():getNodeFromId("scenery_objects")
365 local objectsRoot = container:find("content_tree_list"):getRootNode()
366 assert(objectsRoot)
367 downInTree(objectsRoot, objectNodes)
369 -- entities and components
370 if r2.Scenario.Acts.Size>1 then
371 for i=1, r2.Scenario.Acts.Size-1 do
372 local act = r2.Scenario.Acts[i]
373 local peopleRoot = act:getContentTree():getRootNode():getNodeFromId("people")
374 assert(peopleRoot)
375 local creatureRoot = act:getContentTree():getRootNode():getNodeFromId("creatures")
376 assert(creatureRoot)
377 --local componentRoot = act:getMacroContentTree():getRootNode():getNodeFromId("macro_components")
378 local componentRoot = act:getMacroContentTree():getRootNode()
379 assert(componentRoot)
381 r2.storedClosedTreeNodes[act.InstanceId] = {}
382 local actNodes = r2.storedClosedTreeNodes[act.InstanceId]
384 downInTree(peopleRoot, actNodes)
385 downInTree(creatureRoot, actNodes)
386 downInTree(componentRoot, actNodes)
391 function handler:addPermanentNodes()
393 if r2.ScenarioInstanceId then
394 local scenario = r2:getInstanceFromId(r2.ScenarioInstanceId)
395 if scenario and scenario.Acts.Size>0 then
396 local addToTreesTable = {}
397 scenario:getBaseAct():appendInstancesByType(addToTreesTable, "LogicEntity")
398 for k, instance in pairs(addToTreesTable) do
399 self:addTreeNode(instance)
405 -- private
406 function handler:addTreeNode(instance)
408 if instance.Ghost then return nil end
410 local parentNodes = instance:getParentTreeNode()
412 if parentNodes==nil then return nil end
414 if instance.User.TreeNodes==nil then instance.User.TreeNodes = {} end
416 for actId,parentNode in pairs(parentNodes) do
418 local alreadyAdded = false
420 for k2, treeNode in pairs(instance.User.TreeNodes) do
421 if not (treeNode==nil or treeNode.isNil==true) then
423 local father = treeNode:getFather()
424 if father==parentNode then
425 alreadyAdded=true
426 break
431 if not alreadyAdded then
433 if parentNode == nil then
434 return nil -- one of the ancestors may be unselectable
436 if not instance.SelectableFromRoot then
437 return nil
439 local tree = parentNode:getParentTree()
440 local treeNode = SNode()
442 -- store reference in object
443 table.insert(instance.User.TreeNodes, treeNode)
445 treeNode.Bitmap = instance:getPermanentStatutIcon()
446 local openTree = true
447 if r2.storedClosedTreeNodes[actId] then
448 openTree = (r2.storedClosedTreeNodes[actId][instance.InstanceId]==true)
450 treeNode.Opened = openTree
452 treeNode.Id = instance.InstanceId
453 treeNode.AHName = "lua"
454 local ahParams = "r2:onInstanceSelectedInTree('" .. instance.InstanceId .. "')"
455 --eventDebugInfo(ahParams)
456 treeNode.AHParams = ahParams
457 treeNode.AHNameRight = "lua"
458 treeNode.AHParamsRight = "r2:onInstanceRightClickInTree('" .. instance.InstanceId .. "')"
459 treeNode.AHNameClose = "lua"
460 treeNode.AHParamsClose = "r2.storedClosedTreeNodes = {}"
462 setupNodeName(instance)
464 assert(parentNode)
465 parentNode:addChildSortedByBitmap(treeNode)
466 parentNode.Show = (parentNode:getNumChildren() ~= 0)
468 tree:forceRebuild()
472 return instance.User.TreeNodes
474 function handler:removeTreeNode(instance)
476 local nodes = instance.User.TreeNodes
477 if nodes == nil or nodes.isNil then
478 return
480 for k, node in pairs(nodes) do
481 if not (node == nil or node.isNil == true) then
482 local tree = node:getParentTree()
483 if node:getFather().isNil then
484 if (node == node:getParentTree():getRootNode()) then
485 --debugInfo("ROOT NODE")
486 node:getParentTree():setRootNode(nil)
487 else
488 --debugInfo("ISOLATED NODE")
489 deleteReflectable(node) -- isolated node (the tree was never built ?)
491 else
492 -- update parent node visibility only if a direct son of the root node
493 if node:getFather() then
494 if (node:getFather():getFather() == tree:getRootNode()) then
495 node:getFather().Show = (node:getFather():getNumChildren() > 1)
497 node:getFather():deleteChild(node)
501 tree:forceRebuild()
504 instance.User.TreeNodes = nil
505 end
507 return handler
511 -----------------------------------------------------------------------------------------------------
512 -----------------------------------------------------------------------------------------------------
513 -----------------------------------------------------------------------------------------------------
515 -- special display for groups in scenario window
516 function r2:groupUIDisplayer()
517 local handler = self:defaultUIDisplayer()
518 function handler:updateLeaderColor(instance)
519 if not instance.User.TreeNodes then
520 return
522 for k, node in pairs(instance.User.TreeNodes) do
523 local tree = node:getParentTree()
524 for i = 0, instance.Components.Size - 1 do
525 --debugInfo("I = " .. tostring(i))
526 local treeNodes = instance.Components[i].User.TreeNodes
527 if treeNodes then
528 for k2, treeNode in pairs(treeNodes) do
529 if i == 0 then
530 treeNode.Color = CRGBA(255, 0, 0) -- mark leader in red
531 else
532 treeNode.Color = CRGBA(255, 255, 255)
536 end
537 tree:forceRebuild()
541 local oldOnAttrModified = handler.onAttrModified
542 function handler:onAttrModified(instance, attrName, indexInArray)
543 if attrName == "Components" then
544 self:updateLeaderColor(instance)
546 oldOnAttrModified(self, instance, attrName, indexInArray)
549 -- local oldOnCreate = handler.onCreate
550 -- function handler:onCreate(instance)
551 -- debugInfo("On create group")
552 -- oldOnCreate(self, instance)
553 -- end
555 local oldOnPostCreate = handler.onPostCreate
556 function handler:onPostCreate(instance)
557 oldOnPostCreate(self, instance)
558 self:updateLeaderColor(instance)
561 return handler
566 -----------------------------------------------------------------------------------------------------
567 -----------------------------------------------------------------------------------------------------
568 -----------------------------------------------------------------------------------------------------
570 -- Displayer for ACTS. In the ui, acts are added into the act combo box --
571 -- in the environment of the container we store a table that gives the act Instance id,
572 -- and the index of the tree control for each line in the combo box
573 -- Table has the following look
574 -- ActTable = { { Act = ..., TreeIndex = ... }, -- combo box line 1
575 -- { Act = ..., TreeIndex = ... }, -- combo box line 2
576 -- { Act = ..., TreeIndex = ... }, -- combo box line 3 etc.
577 -- }
580 r2.ActUIDisplayer = {}
581 r2.ActUIDisplayer.ActTable = {} -- table that map each line of the combo box to an act
582 r2.ActUIDisplayer.LastSelfCreatedActInstanceId = nil -- id of the last act created by the pionner (not by another pionner)
583 -- When created, an act change will automatically occur
585 ------------------------------------------------
586 -- helper function : notify current act ui displayer that its quota has been modified
587 function r2.ActUIDisplayer:updateCurrentActQuota()
588 -- defer update to the next frame (many element can be added at once)
589 r2.UIMainLoop.LeftQuotaModified = true
592 ------------------------------------------------
593 function r2.ActUIDisplayer:updateActName(act)
595 if act and not act:isBaseAct() then
597 local actTable = self:getActTable()
598 for index, entry in pairs(actTable) do
599 if entry.Act == act then
600 local comboBox = self:getActComboBox()
602 local actTitle = act:getName()
603 if act==r2.Scenario:getCurrentAct() then
604 actTitle = actTitle .. " [" .. i18n.get("uiR2EDCurrentActComboBox"):toUtf8() .."]"
606 local text = ucstring()
607 text:fromUtf8(actTitle)
608 comboBox:setText(index - 1, text)
609 return
611 end
615 ------------------------------------------------
616 function r2.ActUIDisplayer:onAttrModified(instance, attributeName, indexInArray)
618 -- if title is modified, then must update names of all entities in the scene
619 if attributeName == "Name" then
620 local npcs = {}
621 r2:getCurrentAct():appendInstancesByType(npcs, "Npc")
622 for k, npc in pairs(npcs) do
623 npc.DisplayerVisual:updateName()
626 self:updateActName(instance)
632 ------------------------------------------------
633 function r2.ActUIDisplayer:onCreate(act)
635 local container = self:getContainer()
636 local comboBox = self:getActComboBox()
638 local tree, macroTree
639 if not act:isBaseAct() then
640 local text = ucstring()
641 local index = r2.logicComponents:searchElementIndex(act)-2
642 local actTitle = act:getName()
643 if type(actTitle) ~= "string" then
644 text:fromUtf8("bad type for title : " .. type(actTitle))
645 comboBox:insertText(index, text)
646 else
647 text:fromUtf8(actTitle)
648 comboBox:insertText(index, text)
651 tree = self:findFreeTreeCtrl()
652 macroTree = self:findFreeTreeCtrl(true)
653 local actTable = self:getActTable()
654 table.insert(actTable, index+1, { Act = act, Tree = tree , MacroTree = macroTree})
657 -- store tree in the act for future insertion of items
658 act.User.ContentTree = tree
659 act.User.MacroContentTree = macroTree
660 self:updateCurrentActQuota()
662 -- add permanent nodes to act node
663 r2:defaultUIDisplayer():addPermanentNodes()
666 ------------------------------------------------
667 function r2.ActUIDisplayer:onPostCreate(act)
668 -- when a new act is created, select this act as the default
669 if act.InstanceId == self.LastSelfCreatedActInstanceId then
670 -- the act was just created by pionner on that computer, so change right now
671 r2.ScenarioWindow:setAct(act)
672 self.LastSelfCreatedActInstanceId = nil
674 r2.ScenarioWindow:updateUIFromCurrentAct()
675 self:updateCurrentActQuota()
678 ------------------------------------------------
679 function r2.ActUIDisplayer:onErase(erasedAct)
680 -- clean tree content
681 local tree = erasedAct.User.ContentTree
682 local macroTree = erasedAct.User.MacroContentTree
683 if tree then
684 r2:cleanTreeNode(tree, "people")
685 r2:cleanTreeNode(tree, "creatures")
687 if macroTree then
688 --r2:cleanTreeNode(macroTree, "macro_components")
689 r2:cleanTreeRootNode(macroTree)
691 local actTable = self:getActTable()
692 for index, entry in pairs(actTable) do
693 if entry.Act == erasedAct then
694 self:getActComboBox():removeTextByIndex(index - 1)
695 table.remove(actTable, index)
696 return
698 end
700 self:updateCurrentActQuota()
702 ------------------------------------------------
703 function r2.ActUIDisplayer:getActTable()
704 return self.ActTable
707 ------------------------------------------------
708 function r2.ActUIDisplayer:getContainer()
709 return getUI("ui:interface:r2ed_scenario")
712 ------------------------------------------------
713 function r2.ActUIDisplayer:getActComboBox()
714 return self:getContainer():find("act_combo_box")
717 -----------------------------------------------
718 function r2.ActUIDisplayer:findFreeTreeCtrl(macroTree)
720 local treeName = "act_tree_"
721 if macroTree==true then treeName="macro_act_tree_" end
722 for i = 0, r2:getMaxNumberOfAdditionnalActs() - 1 do
723 local tree = self:getContainer():find(treeName .. tostring(i))
724 local used = false
725 for index, entry in pairs(self:getActTable()) do
726 local entryTree = entry.Tree
727 if macroTree==true then entryTree = entry.MacroTree end
728 if entryTree == tree then
729 used = true
730 break
733 if not used then
734 return tree
737 return nil
741 function r2:createActUIDisplayer()
742 return r2.ActUIDisplayer