-- réed selection bar r2.SelectBar = { Content={}, -- array of instance for the current select bar that is displayed ObserverHandles = {}, -- for each observed instance, gives its observer handle Touched = false, LastMenuPopper = nil, -- last instance in the scenario that popped the mnu LastMenuHideTrigger = nil, -- last element that triggered the disparition of the select bar menu InstancesType = "", InstancesTypes = { {["type"]=i18n.get("uiR2EDScene"):toUtf8(), ["icon"]="r2_palette_entities.tga" }, {["type"]=i18n.get("uiR2EDMacroComponents"):toUtf8(), ["icon"]="r2_palette_components.tga" }, {["type"]=i18n.get("uiR2EDbotObjects"):toUtf8(), ["icon"]="r2_palette_objets.tga" }, {["type"]=i18n.get("uiR2EDPlotItems"):toUtf8(), ["icon"]="ICO_mission_purse.tga" } }, } -- select bar observer : allows to know when a targeted object has been modified, or -- if its name has changed local selectBarObserver = {} function selectBarObserver:onInstanceCreated(instance) -- no-op end function selectBarObserver:onInstanceErased(instance) -- debugInfo(tostring(instance)) -- nb : we don't use the 'instance' as a key because it is a pointer -- to the C++ "weak pointer object", thus 2 references to the same instance may have a different -- value when tested with rawequal ( == operator gives correct results however ...) r2:removeInstanceObserver(r2.SelectBar.ObserverHandles[instance.InstanceId]) r2.SelectBar.ObserverHandles[instance.InstanceId] = nil -- object is not erased yet (notification is sent before the true erase), so need to clean the table before the update local maxElem = r2.SelectBar:getMaxNumEntries() for k, v in pairs(r2.SelectBar.Content) do if v == instance then for index = k, maxElem do r2.SelectBar.Content[index] = nil end break end end if instance ~= r2:getSelectedInstance() then -- nb : if instance is the selection, no need to update the bar right now, because setSelecteInstance(nil) is -- called just after 'erase' notification messages have been sent r2.SelectBar:touch() end r2.SelectBar:touch() end function selectBarObserver:onPostHrcMove(instance) r2.SelectBar:touch() end function selectBarObserver:onAttrModified(instance, attrName, attrIndex) if attrName == "Name" or attrName == "Title" then r2.SelectBar:touch() end end -------------------------------------------------------- -- retrieve a reference to the select bar function r2.SelectBar:getBar() return getUI("ui:interface:r2ed_select_bar:buttons") end -------------------------------------------------------- -- get a button in the bar function r2.SelectBar:getButton(index) return self:getBar():find(string.format("b%d", index)) end -------------------------------------------------------- -- return the max number of button in the select bar function r2.SelectBar:getMaxNumEntries() return tonumber(getDefine("r2ed_max_num_select_bar_button")) end -------------------------------------------------------- -- init the select bar function r2.SelectBar:init() assert(next(self.ObserverHandles) == nil) -- observer table should be empty local bar = self:getBar() bar:clear() for k = 1, self:getMaxNumEntries() do local butt = createGroupInstance("r2ed_select_bar_button", bar.id, { id = string.format("b%d", k), onclick_l="lua", onclick_r="lua", params_l=string.format("r2.SelectBar:onButtonPushed(%d)", k), params_r=string.format("r2.SelectBar:onRightButtonPushed(%d)", k), }) butt.active = false bar:addChild(butt) end bar.active = false end -------------------------------------------------------- -- mark the select bar as modified for update function r2.SelectBar:touch() self.Touched = true local selection = r2:getSelectedInstance() if selection and selection:isKindOf("Act") then self.InstancesType = "" end end --------------------------------------------------------------- -- update bar in special case of act function r2.SelectBar:openInstancesOfType(instancesType) r2.SelectBar:touch() self.InstancesType = instancesType end -------------------------------------------------------- -- update select bar content if necessary function r2.SelectBar:update() if not self.Touched then return end if r2.Scenario == nil then return -- no scenario created for now end self.Touched = false -- clear the observer table for k, v in pairs(self.ObserverHandles) do r2:removeInstanceObserver(v) end table.clear(self.ObserverHandles) local selection = r2:getSelectedInstance() if selection then self:getBar().active = (selection.Ghost ~= true) else self:getBar().active = true end local function setupButtonWithIconAndText(butt, text, icon, buttIndex) butt.active = true butt.b.uc_hardtext = text local icon = icon if icon ~= "" and icon ~= nil then butt.icon.texture = icon butt.icon.active = true -- butt.icon.color_rgba = CRGBA(255, 255, 255, 255) butt.b.text_x = 28 butt.b.wmargin = 10 else -- butt.icon.color_rgba = CRGBA(127, 127, 127, 255) butt.icon.active = false butt.b.text_x = 12 butt.b.wmargin = 12 end --butt.b.pushed = (instance == selection) butt:invalidateCoords() end -- setup a button from an instance local function setupButton(butt, instance, buttIndex) setupButtonWithIconAndText(butt, instance:getDisplayName(), instance:getSelectBarIcon(), buttIndex) butt.Env.Types = false end -- setup a button from a type local function setupButtonType(butt, type, buttIndex) local icon for k, v in pairs(self.InstancesTypes) do if v.type == type then icon = v.icon break end end setupButtonWithIconAndText(butt, ucstring(type), icon, buttIndex) butt.Env.Types = true end -- count number of elligible elements for display in the select bar local buttCount = 0 local currElem = selection if selection then while currElem do if currElem:displayInSelectBar() then buttCount = buttCount + 1 if currElem:isKindOf("Act") and self.InstancesType~="" then buttCount = buttCount + 1 end end currElem = currElem.ParentInstance end end local target = selection if buttCount < 1 then target = r2:getCurrentAct() -- ensure that at least an act is visible buttCount = 1 if self.InstancesType~="" then buttCount = buttCount + 1 end end local buttIndex = buttCount -- special case for scenario wide objects (only plot items for now) if selection and selection:isGlobalObject() then -- current act currElem = r2:getCurrentAct() setupButton(self:getButton(1), currElem, 1) self.Content[1] = currElem self.ObserverHandles[currElem.InstanceId] = r2:addInstanceObserver(currElem.InstanceId, selectBarObserver) -- special button for type if self.InstancesType~="" then setupButtonType(self:getButton(2), self.InstancesType, 2) self.Content[2] = currElem end -- plot item currElem = selection setupButton(self:getButton(3), currElem, 3) self.Content[3] = currElem self.ObserverHandles[currElem.InstanceId] = r2:addInstanceObserver(currElem.InstanceId, selectBarObserver) buttIndex = 4 -- special case if current selection is the scenario (display a single button) elseif selection and selection:isSameObjectThan(r2.Scenario) then setupButton(self:getButton(1), selection, buttIndex) self.Content[1] = selection self.ObserverHandles[selection.InstanceId] = r2:addInstanceObserver(selection.InstanceId, selectBarObserver) buttIndex = 2 target = r2:getCurrentAct() -- continue with sons of current act else -- special case for plot items -- add the buttons for real -- parents local currElem = target while currElem do if currElem:displayInSelectBar() then if buttIndex <= self:getMaxNumEntries() then if currElem:isKindOf("Act") then if currElem:isBaseAct() then currElem = r2:getCurrentAct() end if self.InstancesType~="" then -- special button for different types local ucname = ucstring() ucname:fromUtf8(self.InstancesType) setupButtonType(self:getButton(buttIndex), ucname, buttIndex) self.Content[buttIndex] = currElem buttIndex = buttIndex - 1 end end setupButton(self:getButton(buttIndex), currElem, buttIndex) self.Content[buttIndex] = currElem self.ObserverHandles[currElem.InstanceId] = r2:addInstanceObserver(currElem.InstanceId, selectBarObserver) end buttIndex = buttIndex - 1 end currElem = currElem.ParentInstance end buttIndex = buttCount + 1 -- sons -- preserve previous sons if they where in previous selection, -- possibly renaming them -- (nb : update may be triggered by modification of sons from a third party, so -- the simpler approach 'if curr selection was in previous then leave unchanged' -- doesn't work here ...) -- get next son that can be displayed in the select bar --local currParent = target --while self.Content[buttIndex] ~= nil and not self.Content[buttIndex].isNil do -- currElem = self.Content[buttIndex] -- if currElem:getFirstSelectBarParent() == currParent then -- -- there's a match so keep this entry -- setupButton(self:getButton(buttIndex), currElem) -- currParent = self.Content[buttIndex] -- buttIndex = buttIndex + 1 -- self.ObserverHandles[currElem.InstanceId] = r2:addInstanceObserver(currElem.InstanceId, selectBarObserver) -- else -- -- no match -> exit -- break -- end --end end -- old version -- walk down sons until we reach the max number of buttons -- local currSon = target:getFirstSelectBarSon() -- while currSon and buttIndex <= self:getMaxNumEntries() do -- setupButton(self:getButton(buttIndex), currSon) -- self.Content[buttIndex] = currSon -- self.ObserverHandles[currSon.InstanceId] = r2:addInstanceObserver(currSon.InstanceId, selectBarObserver) -- currSon = currSon:getFirstSelectBarSon() -- buttIndex = buttIndex +1 -- end -- if there's room, add a selection button to traverse down the hierarchy if buttIndex <= self:getMaxNumEntries() then local butt = self:getButton(buttIndex) butt.active = true butt.b.uc_hardtext = i18n.get("uiR2EDSelectSubObject") butt.icon.active = false butt.b.text_x = 12 butt.b.wmargin = 12 butt:invalidateCoords() --butt.b.pushed = false self.Content[buttIndex] = nil buttIndex = buttIndex + 1 end -- hide remaining buttons for k = buttIndex, self:getMaxNumEntries() do self:getButton(k).active = false self.Content[k] = nil end -- show/hide sequence browser if necessary local sequenceMenuButton = self:getSequenceButton() if not selection then sequenceMenuButton.active = false else sequenceMenuButton.active = selection:isSequencable() and (selection.Ghost ~= true) end -- touch the contextual toolbar, because it y depends on us r2.ContextualCommands:getToolbar():invalidateCoords() end -------------------------------------------------------- -- called by the ui when one of the select bar button has been pushed -- function r2.SelectBar:onButtonPushed(index) -- local instanceId = self.Content[index].InstanceId -- local selectedInstance = r2:getSelectedInstance() -- if selectedInstance and instanceId == selectedInstance.InstanceId then -- -- on second click the contextual menu is displayed -- self:popMenu(index) -- end -- r2:setSelectedInstanceId(instanceId) -- self:getButton(index).b.pushed = true -- end -- -- -------------------------------------------------------- -- -- called by the ui when one of the select bar button has been pushed with the right button -- function r2.SelectBar:onRightButtonPushed(index) -- r2:setSelectedInstanceId(self.Content[index].InstanceId) -- self:update() -- self:onButtonPushed(index) -- end -------------------------------------------------------- -- mark the select bar as modified for update function r2.SelectBar:onMenuPostClickOut() local uiCaller = getUICaller() if uiCaller and uiCaller.parent and uiCaller.parent.parent then local parentGroup = uiCaller.parent.parent if parentGroup == getUI("ui:interface:r2ed_select_bar:buttons") then self.LastMenuHideTrigger = getUICaller() return end end self.LastMenuHideTrigger = nil self.LastMenuPopper = nil end -------------------------------------------------------- -- called by the ui when one of the select bar button has been pushed function r2.SelectBar:onButtonPushed(index) local butt = self:getButton(index) local selectedInstance = self.Content[index] if selectedInstance == nil then -- pop menu for sons of parents self:popMenu(index) return end if butt.b == self.LastMenuHideTrigger and selectedInstance == self.LastMenuPopper then self.LastMenuHideTrigger = nil return end self.LastMenuPopper = selectedInstance self.LastMenuHideTrigger = nil r2:setSelectedInstanceId(selectedInstance.InstanceId) self:touch() -- on second click the contextual menu is displayed self:popMenu(index) end -------------------------------------------------------- -- called by the ui when one of the select bar button has been pushed with the right button function r2.SelectBar:onRightButtonPushed(index) self:onButtonPushed(index) end -------------------------------------------------------- -- get root menu for the select bar function r2.SelectBar:getRootMenu() local menu = getUI("ui:interface:r2ed_select_bar_menu") return menu:getRootMenu() end -------------------------------------------------------- -- get menu for the select bar function r2.SelectBar:getMenu() return getUI("ui:interface:r2ed_select_bar_menu") end -------------------------------------------------------- -- get the 'sequences' button function r2.SelectBar:getSequenceButton() return getUI("ui:interface:r2ed_select_bar:sequences") end -------------------------------------------------------- -- display sub-instance menu for the given button function r2.SelectBar:getMenu() return getUI("ui:interface:r2ed_select_bar_menu") end local allSons = {} -------------------------------------------------------- -- display sub-instance menu for the given button function r2.SelectBar:popMenu(index) local target if self.Content[index] == nil then -- the "select" button was pressed target = r2:getSelectedInstance() if target == nil then target = r2:getCurrentAct() end else target = r2:getInstanceFromId(self.Content[index].InstanceId) if not target:isSameObjectThan(r2.Scenario) then -- to get objects of same type, just enumerate from the parent (except for scenario) target = target:getFirstSelectBarParent() end end --r2:setSelectedInstanceId(target.InstanceId) local menu = self:getMenu() local rm = self:getRootMenu() r2:clearMenu(rm) local st = os.clock() -- retrieve all sons (optimize this if needed ...) table.clear(allSons) if target:isKindOf("Scenario") then -- special case for scenario : 'appendInstancesByType' would only add the active act, and we want them all for k, v in specPairs(target.Acts) do if not v:isBaseAct() then table.insert(allSons, v) end end elseif target:isKindOf("Act") and self:getButton(index).Env.Types~=true then for k, v in specPairs(r2.Scenario.PlotItems) do table.insert(allSons, v) end r2.Scenario:getBaseAct():appendInstancesByType(allSons, "BaseClass") r2.Scenario:getCurrentAct():appendInstancesByType(allSons, "BaseClass") local allSonsTemp = {} for k, v in pairs(allSons) do if v:getSelectBarType()==self.InstancesType then table.insert(allSonsTemp, v) end end allSons = allSonsTemp elseif target ~= nil then target:appendInstancesByType(allSons, "BaseClass") end local et = os.clock() --debugInfo("#1 " .. tostring(et - st)) st = os.clock() local sons = {} -- retrieve direct selectable sons for k,v in pairs(allSons) do if v:displayInSelectBar() and (v:getFirstSelectBarParent()==target or (target:isKindOf("Act") and v:getFirstSelectBarParent()==r2.Scenario:getBaseAct())) then table.insert(sons, v) end end et = os.clock() --debugInfo("#2 " .. tostring(et - st)) st = os.clock() -- sort by category, then by icon local function sorter(lhs, rhs) if lhs:getClassName() ~= rhs:getClassName() then return lhs:getClassName() < rhs:getClassName() end if lhs:getSelectBarIcon() ~= rhs:getSelectBarIcon() then return lhs:getSelectBarIcon() < rhs:getSelectBarIcon() end return lhs:getDisplayName() < rhs:getDisplayName() end table.sort(sons, sorter) et = os.clock() --debugInfo("#3 " .. tostring(et - st)) st = os.clock() -- fill menu -- special case for act if index==2 then for k, v in pairs(self.InstancesTypes) do local ucname = ucstring() ucname:fromUtf8(v.type) r2:addMenuLine(rm, ucname, "lua", "r2.SelectBar:openInstancesOfType('".. v.type .."','" .. v.icon .."')", v.type, v.icon, 14) end else for k, v in pairs(sons) do r2:addMenuLine(rm, v:getDisplayName(), "lua", "r2:setSelectedInstanceId('" .. v.InstanceId .."')", tostring(k), v:getSelectBarIcon(), 14) end end target:completeSelectBarMenu(rm) if rm:getNumLine() == 0 then rm:addLine(i18n.get("uiR2EDEmpty"), "", "", "empty") end if self.Content[index] and self.Content[index].InstanceId == r2:getSelectedInstance().InstanceId then if r2:getSelectedInstance().BuildPropertySheet then rm:addSeparator() r2:addMenuLine(rm, concatUCString(i18n.get("uiRE2DPropertiesOf"), r2:getSelectedInstance():getDisplayName()), "lua", "r2:showProperties(r2:getSelectedInstance())", "prop", "r2_icon_properties.tga", 14) end end et = os.clock() --debugInfo("#4 " .. tostring(et - st)) et = os.clock() rm:setMaxVisibleLine(15) launchContextMenuInGame(menu.id) local butt = self:getButton(index) menu.x = butt.x_real menu.y = butt.y_real + butt.h_real menu:updateCoords() et = os.clock() --debugInfo("#5 " .. tostring(et - st)) end -------------------------------------------------------- -- TMP placeholder: called when the "sequence" menu is clicked -------------------------------------------------------- -- called when the "sequence" menu is clicked function r2.SelectBar:browseSequences() --r2:updateActivitiesAndChatsUI(r2:getSelectedInstance()) r2.activities:initEditorAfterFirstCall() local logicEntity = r2:getSelectedInstance() local activitySequences = logicEntity:getBehavior().Activities local menu = self:getMenu() local rm = menu:getRootMenu() r2:clearMenu(rm) for s = 0, activitySequences.Size - 1 do local sequence = activitySequences[s] local uc_sequ = ucstring() uc_sequ:fromUtf8(sequence:getName()) rm:addLine(uc_sequ, "lua", "r2.activities:triggerSelectSequence('".. sequence.InstanceId .. "')", sequence.InstanceId) end rm:addSeparator() r2:addMenuLine(rm, i18n.get("uiR2EDNewSequence"), "lua", "r2.activities:newSequenceInst()", "new_sequence", "r2_icon_create.tga", 14) local sequenceMenuButton = self:getSequenceButton() sequenceMenuButton:updateCoords() launchContextMenuInGame(menu.id) menu.x = sequenceMenuButton.x_real menu.y = sequenceMenuButton.y_real + sequenceMenuButton.h_real menu:updateCoords() end