local COMPAT, _, T = select(4,GetBuildInfo()), ... local L, EV, PC, TS, XU, config, KR = T.L, T.Evie, T.OPieCore, T.TenSettings, T.exUI, T.config, OPie.ActionBook:compatible("Kindred", 1, 0) local MODERN = COMPAT >= 10e4 local frame = TS:CreateOptionsPanel(L"Ring Bindings", "OPie") frame.desc:SetText(L"Customize OPie key bindings below. Hover over a binding button for additional information and options." .. (MODERN and "\n" .. L"Profiles activate automatically when you switch character specializations." or "")) local OBC_Profile = CreateFrame("Frame", "OBC_Profile", frame, "UIDropDownMenuTemplate") OBC_Profile:SetPoint("TOPLEFT", 0, -80) UIDropDownMenu_SetWidth(OBC_Profile, 200) OBC_Profile.initialize, OBC_Profile.text = OPC_Profile.initialize, OPC_Profile.text local bindSet = CreateFrame("Frame", "OPC_BindingSet", frame, "UIDropDownMenuTemplate") bindSet:SetPoint("LEFT", OBC_Profile, "RIGHT") UIDropDownMenu_SetWidth(bindSet, 250) local bindLines, bindLines2, bindZone, bindZoneOrigin = {}, {}, CreateFrame("Frame", nil, frame) do bindZone:SetClipsChildren(true) bindZone:SetHitRectInsets(0, -22, 0, 0) bindZoneOrigin = CreateFrame("Frame", nil, bindZone) bindZoneOrigin:SetHeight(1) bindZoneOrigin:SetPoint("TOPLEFT") bindZoneOrigin:SetPoint("TOPRIGHT") bindZoneOrigin:Hide() bindZone.clipContainer, bindZone.bindingContainerFrame = bindZone, frame for i=1,19 do local bind = config.createBindingButton(bindZone, 170) bind:SetPoint("TOPLEFT", bindZoneOrigin, "TOPLEFT", 220, 22-24*i) local bind2 = config.createBindingButton(bindZone, 170) bind2:SetPoint("LEFT", bind, "RIGHT", 4, 0) local label = bind:CreateFontString(nil, "OVERLAY", "GameFontHighlight") label:SetPoint("TOPLEFT", bindZoneOrigin, 5, 18-24*i) bind.warn = bind:CreateTexture(nil, "ARTWORK") bind.warn:SetTexture("Interface/EncounterJournal/UI-EJ-WarningTextIcon") bind.warn:SetSize(14, 14) bind.warn:SetPoint("RIGHT", bind, "LEFT", -3, 0) bindLines[i], bind.label, bindLines2[i] = bind, label, bind2 end bindZone:SetPoint("TOP", OBC_Profile, "BOTTOM", 0, -4) bindZone:SetPoint("LEFT", 15, 0) bindZone:SetPoint("RIGHT", -30, 0) bindZone:SetHeight((#bindLines-1)*24+3) end local bindZoneBar = XU:Create("ScrollBar", nil, frame) bindZoneBar:SetPoint("TOPLEFT", bindZone, "TOPRIGHT", 1, 14) bindZoneBar:SetPoint("BOTTOMLEFT", bindZone, "BOTTOMRIGHT", 1, -10) bindZoneBar:SetWindowRange(#bindLines-1) bindZoneBar:SetStepsPerPage(#bindLines-5) local bindZoneCover = CreateFrame("Frame", nil, bindZone) bindZoneCover:SetAllPoints() bindZoneCover:EnableMouse(true) bindZoneCover:Hide() bindZoneCover:SetScript("OnShow", function(self) self:SetFrameLevel(bindLines[#bindLines]:GetFrameLevel()+10) end) local ringBindings = {map={}, name=L"Ring Bindings", TwoBindingSlots=true} function ringBindings:refresh() local pos, map = 1, self.map for key in PC:IterateRings(IsAltKeyDown()) do map[pos], pos = key, pos + 1 end for i=#map,pos,-1 do map[i] = nil end self.count = #map end local function ringBindings_getInner(key, bidx) local bind, cBind, isOverride, isActiveInt, isActiveExt = PC:GetRingBinding(key, bidx) local showWarning, prefix, tipTitle, tipText = false local cebind = cBind or (bind and KR:EvaluateCmdOptions(bind)) local BC_HEADER_PREFIX = "|TInterface/EncounterJournal/UI-EJ-WarningTextIcon:0:0:0:2|t " if not isOverride and not PC:GetOption("UseDefaultBindings", key) then if bind then showWarning, prefix, tipTitle = true, "|cffa0a0a0", BC_HEADER_PREFIX .. L"Default binding disabled" tipText = (L"Choose a binding for this ring, or enable the %s option in OPie options."):format(HIGHLIGHT_FONT_COLOR_CODE .. L"Use default ring bindings" .. "|r") end elseif cBind and isActiveExt ~= true then showWarning, tipTitle = true, BC_HEADER_PREFIX .. L"Binding conflict" if isActiveInt == false then prefix = isOverride and "|cfffa2800" or "|cffa0a0a0" tipText = L"This binding is not currently active because it conflicts with another." else prefix, tipText = "|cfffa2800", L"This binding is currently used by another addon." end if isActiveExt then local lab = _G["BINDING_NAME_" .. isActiveExt] if not (lab and type(lab) == "string" and lab:match("%S")) then lab = tostring(isActiveExt) end tipText = tipText .. "\n\n" .. (L"Conflicts with: %s"):format("|cffe0e0e0" .. lab .. "|r") end elseif cBind == nil and cebind and not isActiveInt then showWarning, tipTitle = true, BC_HEADER_PREFIX .. L"Binding conflict" prefix, tipText = "|cffa0a0a0", L"This binding is not currently active because it conflicts with another." elseif isOverride and bind ~= nil then prefix = "|cffffffff" end return bind, prefix, cBind, tipTitle, tipText, showWarning end function ringBindings:get(id) local name, key = PC:GetRingInfo(self.map[id]) local text = name or key or "?" local bind, prefix, cBind, title, tip, warning = ringBindings_getInner(key, 1) local bind2, prefix2, _cBind2, title2, tip2, warning2 = ringBindings_getInner(key, 2) return bind, text, prefix, cBind, title, tip, warning, bind2, prefix2, title2, tip2, warning2 end function ringBindings:set(id, key, bidx) id = self.map[id] config.undo:saveActiveProfile() PC:SetRingBinding(id, bidx, key) end function ringBindings:default() PC:ResetRingBindings() end function ringBindings:altClick() -- self is the binding button local id, bidx = self:GetID() id, bidx = id < 0 and -id or id, id < 0 and 2 or 1 self:ToggleAlternateEditor(PC:GetRingBinding(ringBindings.map[id], bidx)) end function ringBindings:shiftClick() local name, key, macro = PC:GetRingInfo(ringBindings.map[math.abs(self:GetID())]) TS:ShowPromptOverlay(frame, name or key, (L"The following macro command opens this ring:"):format("|cffFFD029" .. (name or key) .. "|r"), false, false, nil, 0.90, nil, macro) end local subBindings = { name=L"In-Ring Bindings", options={"ScrollNestedRingUpButton", "ScrollNestedRingDownButton", "OpenNestedRingButton", "SelectedSliceBind"}, optionNames={L"Scroll nested ring (up)", L"Scroll nested ring (down)", L"Open nested ring", L"Selected slice (keep ring open)"}, count=0, t={}, } function subBindings.allowWheel(btn) return btn:GetID() <= 2 and not subBindings.scope end function subBindings:refresh(scope) local ringName = scope and PC:GetRingInfo(scope) scope = ringName and scope or nil self.scope, self.nameSuffix = scope, scope and (" (|cffacd7e6" .. ringName .. "|r)") or (" (" .. L"Defaults" .. ")") local t, ni = self.t, 1 for s in PC:GetOption("SliceBindingString", scope):gmatch("%S+") do t[ni], ni = s, ni + 1 end for i=#t,ni,-1 do t[i] = nil end subBindings.count = ni+(scope and 1 or 4) end function subBindings:get(id) local firstListSize = self.scope and 1 or 4 if id <= firstListSize then id = (self.scope and 3 or 0) + id local value, setting = PC:GetOption(self.options[id], self.scope) return value, self.optionNames[id], setting and "|cffffffff" or nil else id = id - firstListSize end return self.t[id] == "false" and "" or self.t[id], (L"Slice #%d"):format(id) end function subBindings:set(id, bind) local firstListSize = self.scope and 1 or 4 if id <= firstListSize then id = (self.scope and 3 or 0) + id config.undo:saveActiveProfile() PC:SetOption(self.options[id], bind == false and "" or bind, self.scope) return else id = id - firstListSize end if bind == nil then local i, s, s2 = 1, select(self.scope == nil and 5 or 4, PC:GetOption("SliceBindingString", self.scope)) for f in (s or s2):gmatch("%S+") do if i == id then bind = f break end i = i + 1 end end local t, bind = self.t, bind or "false" if bind ~= "false" then for i=1,#t do if t[i] == bind then t[i] = "false" end end end t[id] = bind for j=#t,1,-1 do if t[j] == "false" then t[j] = nil else break end end self.count = #t + firstListSize + 1 local _, _, _, global, default = PC:GetOption("SliceBindingString", self.scope) local v = table.concat(t, " ") if self.scope == nil and v == default then v = nil elseif self.scope ~= nil and v == (global or default) then v = nil end config.undo:saveActiveProfile() PC:SetOption("SliceBindingString", v, self.scope) end local subBindings_List = {} local function subBindings_ScopeClick(_, key) return bindSet.set(nil, subBindings, key or nil) end local function subBindings_ScopeFormat(key, list) return list[key], list[0] and (key or nil) == subBindings.scope end function subBindings:scopes(level, checked) local list = subBindings_List wipe(list) -- Reusing the table to maintain the scroll position key list[0], list[1], list[false] = checked, false, L"Defaults for all rings" local ct = T.OPC_RingScopePrefixes for key, name, scope in PC:IterateRings(true) do local color = ct and ct[scope] or "|cffacd7e6" list[#list+1], list[key] = key, (L"Ring: %s"):format(color .. (name or key) .. "|r") end XU:Create("ScrollableDropDownList", level, list, subBindings_ScopeFormat, subBindings_ScopeClick) end function subBindings:default() for i=0,#self.options do local on = i == 0 and "SliceBindingString" or self.options[i] PC:SetOption(on, nil) if self.scope then PC:SetOption(on, nil, self.scope) end end end local currentOwner, bindingTypes = ringBindings, {ringBindings, subBindings} local function updatePanelContent() local m = currentOwner.count bindZoneBar:SetShown(m >= #bindLines) bindZoneBar:SetMinMaxValues(0, m > #bindLines and m - #bindLines + 1 or 1) local csv = bindZoneBar:GetValue() local csPartial = csv % 1 local csBase = csv - csPartial bindZoneOrigin:SetPoint("TOPLEFT", 0, csPartial*24) for i=1,#bindLines do local j, e, e2 = csBase+i, bindLines[i], bindLines2[i] if j > m then e:Hide() e2:Hide() else local binding, text, prefix, _, title, tip, showWarningIcon, binding2, prefix2, title2, tip2, warning2 = currentOwner:get(j) e.bindingName, e.tooltipTitle, e.tooltipText = text, title, tip e.label:SetText(text) e.warn:SetShown(showWarningIcon or warning2) e:SetBindingText(binding, prefix) e:SetID(j) e:Hide() e:Show() e2:Hide() if currentOwner.TwoBindingSlots then e2.bindingName, e2.tooltipTitle, e2.tooltipText = text, title2, tip2 e2:SetBindingText(binding2, prefix2) e2:SetID(-j) e2:Show() e:SetPoint("TOPLEFT", bindZoneOrigin, "TOPLEFT", 221, 22-24*i) e:SetWidth(170) else e:SetPoint("TOPLEFT", bindZoneOrigin, "TOPLEFT", 350, 22-24*i) e:SetWidth(215) end end end bindZone.OnBindingAltClick = currentOwner.altClick bindZone.OnBindingShiftClick = currentOwner.shiftClick UIDropDownMenu_SetText(bindSet, currentOwner.name .. (currentOwner.nameSuffix or "")) end function bindZone.SetBinding(buttonOrId, binding) local id, bidx = type(buttonOrId) == "number" and buttonOrId or buttonOrId:GetID() id, bidx = id < 0 and -id or id, id < 0 and 2 or 1 currentOwner:set(id, binding, bidx) updatePanelContent() end bindZone:SetScript("OnMouseWheel", function(_, delta) bindZoneBar:Step(-delta*6, true) updatePanelContent() end) bindZoneBar:SetScript("OnValueChanged", function(self, _, userEvent) bindZoneCover:SetShown(not self:IsValueAtRest()) if userEvent then updatePanelContent() end end) function bindSet:initialize(level) local info = {func=bindSet.set, minWidth=bindSet:GetWidth()-40} for i=1,#bindingTypes do local v = bindingTypes[i] if v.scopes then UIDropDownMenu_AddSeparator(level) info.text, info.isTitle, info.notCheckable, info.justifyH = v.name, true, true, "CENTER" UIDropDownMenu_AddButton(info, level) v:scopes(level, currentOwner == v) else info.notCheckable, info.isTitle, info.justifyH = nil info.text, info.arg1, info.checked = v.name, v, v == currentOwner UIDropDownMenu_AddButton(info, level) end end end function bindSet:set(owner, scope) currentOwner, bindZone.AllowWheelBinding = owner, owner and owner.allowWheel bindZoneBar:SetValue(0) if owner.refresh then owner:refresh(scope) end updatePanelContent() CloseDropDownMenus() frame.resetOnHide = nil end function frame.refresh() for _, v in pairs(bindingTypes) do if v.refresh then v:refresh(v.scope) end end OBC_Profile:text() updatePanelContent() config.checkSVState(frame) end function frame.default() config.undo:saveActiveProfile() for _, v in pairs(bindingTypes) do if v.default then v:default() end end frame.refresh() end local function resetView() currentOwner, frame.resetOnHide = ringBindings, nil bindZoneBar:SetValue(0) for _, v in pairs(bindingTypes) do v.scope = nil end end frame.okay, frame.cancel = resetView, resetView frame:SetScript("OnShow", frame.refresh) frame:SetScript("OnHide", function() if frame.resetOnHide then resetView() end end) T.AddSlashSuffix(function() frame:OpenPanel() end, "bind", "binding", "bindings") function T.ShowSliceBindingPanel(ringKey) frame:OpenPanel() bindSet.set(nil, subBindings, ringKey) frame.resetOnHide = true config.pulseDropdown(bindSet) end function EV:OPIE_PROFILE_SWITCHED() if frame:IsVisible() then frame.refresh() end end