local COMPAT, api, _, T = select(4,GetBuildInfo()), {}, ... local PC, RK, ORI, config = T.OPieCore, T.RingKeeper, OPie.UI, T.config local L, MODERN = T.L, COMPAT >= 8e4 local AB = assert(T.ActionBook:compatible(2,23), "A compatible version of ActionBook is required") local EV, TS, XU, CreateEdge = T.Evie, T.TenSettings, T.exUI, T.CreateEdge local GameTooltip = T.NotGameTooltip or GameTooltip local FULLNAME, SHORTNAME do function EV.PLAYER_LOGIN() local name, realm = UnitFullName("player") FULLNAME, SHORTNAME = name .. "-" .. realm, name end end local function prepEditBoxCancel(self) self.oldValue = self:GetText() if self.placeholder then self.placeholder:Hide() end end local function cancelEditBoxInput(self) local h = self:GetScript("OnEditFocusLost") self:SetText(self.oldValue or self:GetText()) self:SetScript("OnEditFocusLost", nil) self:ClearFocus() self:SetScript("OnEditFocusLost", h) if self.placeholder and self:GetText() == "" then self.placeholder:Show() end end local function prepEditBox(self, save) if self:IsMultiLine() then self:SetScript("OnEscapePressed", self.ClearFocus) else self:SetScript("OnEditFocusGained", prepEditBoxCancel) self:SetScript("OnEscapePressed", cancelEditBoxInput) self:SetScript("OnEnterPressed", self.ClearFocus) end self:SetScript("OnEditFocusLost", save) end local function addIconSlotTextures(tex, sz) local p = tex:GetParent() local bg = p:CreateTexture(nil, "BACKGROUND", nil, -1) bg:SetTexture("Interface/Buttons/UI-EmptySlot-Disabled") bg:SetPoint("CENTER", tex, "CENTER") bg:SetSize(1.5*sz, 1.5*sz) local edge = p:CreateTexture(nil, "OVERLAY", nil, -1) edge:SetTexture("Interface/Buttons/UI-Quickslot2") edge:SetSize(1.625*sz, 1.625*sz) edge:SetPoint("CENTER", tex, "CENTER", 0.25, -0.25) if MODERN then local m = p:CreateMaskTexture() m:SetTexture("Interface/FrameGeneral/UIFrameIconMask") m:SetAllPoints(tex) tex:AddMaskTexture(m) end return bg, edge end local function createIconButton(name, parent, id, skipSlotDecorations) local f = CreateFrame("CheckButton", name, parent, nil, id or 0) f:SetSize(32,32) f:SetNormalTexture("") f:SetHighlightTexture("Interface/Buttons/ButtonHilight-Square") f:GetHighlightTexture():SetBlendMode("ADD") f:SetCheckedTexture("Interface/Buttons/CheckButtonHilight") f:GetCheckedTexture():SetBlendMode("ADD") f:SetPushedTexture("Interface/Buttons/UI-Quickslot-Depress") f.tex = f:CreateTexture(nil, "ARTWORK") f.tex:SetAllPoints() if skipSlotDecorations ~= true then f.background, f.edge = addIconSlotTextures(f.tex, 32) end return f end local function PlayCheckboxSound(self) PlaySound(SOUNDKIT[self:GetChecked() and "IG_MAINMENU_OPTION_CHECKBOX_ON" or "IG_MAINMENU_OPTION_CHECKBOX_OFF"]) end local function SetCursor(tex) tex = type(tex) == "string" and GetFileIDFromPath(tex) or tex _G.SetCursor(type(tex) == "number" and tex > 0 and tex or tex and 132761) end local CallSetRing local function SaveRingVersion(name, liveData) local key = "RKRing#" .. name if not config.undo:search(key) then if liveData == true then liveData = RK:GetRingDescription(name) or false end config.undo:push(key, CallSetRing, name, liveData) end end function CallSetRing(msg, ...) if msg == "archive-unwind" then SaveRingVersion((...), true) end RK:SetRing(...) end local function CreateToggleButton(parent) local button = CreateFrame("CheckButton", nil, parent) button:SetSize(175, 30) button:SetNormalFontObject(GameFontHighlightMedium) button:SetPushedTextOffset(-1, -1) button:SetNormalTexture("Interface\\PVPFrame\\PvPMegaQueue") button:GetNormalTexture():SetTexCoord(0.00195313,0.58789063, 0.87304688,0.92773438) button:SetPushedTexture("Interface\\PVPFrame\\PvPMegaQueue") button:GetPushedTexture():SetTexCoord(0.00195313,0.58789063,0.92968750,0.98437500) button:SetHighlightTexture("Interface\\PVPFrame\\PvPMegaQueue") button:GetHighlightTexture():SetTexCoord(0.00195313,0.63867188, 0.70703125,0.76757813) button:SetCheckedTexture("Interface\\PVPFrame\\PvPMegaQueue") button:GetCheckedTexture():SetTexCoord(0.00195313,0.63867188, 0.76953125,0.83007813) for i=1,2 do local tex = i == 1 and button:GetHighlightTexture() or button:GetCheckedTexture() tex:ClearAllPoints() tex:SetPoint("TOPLEFT", button, "LEFT", 1.5, 12.3) tex:SetPoint("BOTTOMRIGHT", button, "RIGHT", -1.5, -12.3) tex:SetBlendMode("ADD") end return button end local function CreateButton(parent, width) local btn = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate") btn:SetWidth(width or 150) return btn end local function setIcon(self, path, ext) self:SetTexture(path or "Interface/Icons/Inv_Misc_QuestionMark") self:SetTexCoord(0,1,0,1) if not ext then return end if type(ext.iconR) == "number" and type(ext.iconG) == "number" and type(ext.iconB) == "number" then self:SetVertexColor(ext.iconR, ext.iconG, ext.iconB) end if type(ext.iconCoords) == "table" then self:SetTexCoord(unpack(ext.iconCoords)) elseif type(ext.iconCoords) == "function" or type(ext.iconCoords) == "userdata" then self:SetTexCoord(ext:iconCoords()) end end local function GetPositiveFileIDFromPath(path) local id = GetFileIDFromPath(path) return id and id > 0 and id or nil end local ringContainer, ringDetail, sliceDetail, newSlice, newRing, editorHost local panel = TS:CreateOptionsPanel(L"Custom Rings", "OPie") panel.desc:SetText(L"Customize OPie by modifying existing rings, or creating your own.") local ringDropDown = CreateFrame("Frame", "RKC_RingSelectionDropDown", panel, "UIDropDownMenuTemplate") ringDropDown:SetPoint("TOP", -70, -60) UIDropDownMenu_SetWidth(ringDropDown, 260) local btnNewRing = CreateButton(panel) btnNewRing:SetPoint("LEFT", ringDropDown, "RIGHT", -5, 3) btnNewRing:SetText(L"New Ring...") local dragBackdrop = CreateFrame("Frame") do dragBackdrop:Hide() dragBackdrop:SetFrameStrata("BACKGROUND") dragBackdrop:SetAllPoints() dragBackdrop:EnableMouse(true) dragBackdrop:SetScript("OnMouseDown", dragBackdrop.Hide) end newRing = CreateFrame("Frame") do newRing:SetSize(400, 115) newRing:Hide() local title = newRing:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") local toggle1, toggle2 = CreateToggleButton(newRing), CreateToggleButton(newRing) local name, snap = TS:CreateLineInputBox(newRing, true, 240), TS:CreateLineInputBox(newRing, true, 240) local nameLabel, snapLabel = newRing:CreateFontString(nil, "OVERLAY", "GameFontHighlight"), snap:CreateFontString(nil, "OVERLAY", "GameFontHighlight") local accept, cancel = CreateButton(newRing, 125), CreateButton(newRing, 125) local importNested = TS:CreateOptionsCheckButton(nil, newRing) local state = {selected=toggle1, buncount=0} title:SetPoint("TOP", 0, -3) toggle1:SetPoint("TOPLEFT", 20, -25) toggle2:SetPoint("TOPRIGHT", -20, -25) name:SetPoint("TOPRIGHT", -15, -62) nameLabel:SetPoint("TOPLEFT", newRing, "TOPLEFT", 15, -67) snap:SetPoint("TOPRIGHT", -15, -85) snapLabel:SetPoint("TOPLEFT", newRing, "TOPLEFT", 15, -90) accept:SetPoint("BOTTOMRIGHT", newRing, "BOTTOM", -2, 4) cancel:SetPoint("BOTTOMLEFT", newRing, "BOTTOM", 2, 4) toggle1:SetChecked(1) importNested:SetScript("OnClick", PlayCheckboxSound) importNested:SetPoint("TOPLEFT", snap, "BOTTOMLEFT", -9, -2) importNested:SetHitRectInsets(0, -222, 0, 0) importNested:SetScript("OnHide", function(self) self:SetChecked(nil) end) snap:Hide() local function updateSnap(snapText, speculativeNameCheck) if state.snap == snapText and not speculativeNameCheck then return end local bc, ring, bun = 0, RK:GetSnapshotRing(snapText) if speculativeNameCheck == true and not ring then return end if type(bun) == "table" then for _,v in pairs(bun) do if type(v) == "table" then bc = bc + 1 end end end state.snap, state.ring, state.bundle, state.buncount = snapText, ring, bun, bc if speculativeNameCheck == true then name:SetText("") snap:SetText(snapText) toggle2:Click() end if state.ring and name:GetText() == "" then snap:SetCursorPosition(0) name:SetText(state.ring.name or "") name:SetFocus() name:HighlightText() end snap:SetTextColor((ring and GameFontGreen or ChatFontNormal):GetTextColor()) importNested.Text:SetText((L"Import %s |4nested ring:nested rings;"):format(NORMAL_FONT_COLOR_CODE .. "|t" .. bc .. "|r")) return not not ring end local function validate() local nameText = name:GetText() or "" if toggle2:GetChecked() then updateSnap(snap:GetText() or "") elseif #nameText > 32 and nameText:match("^%s*oetohH7") then updateSnap(nameText, true) end local isSnapImport = toggle2:GetChecked() local snapOK = state.ring and true or not isSnapImport local hasBundledRings = isSnapImport and state.ring and state.buncount ~= 0 newRing:SetSize(400, hasBundledRings and 162 or isSnapImport and 140 or 115) snap:SetShown(isSnapImport) importNested:SetShown(hasBundledRings) accept:SetEnabled(type(nameText) == "string" and nameText:match("%S") and snapOK) if newRing:IsVisible() then TS:ShowFrameOverlay(panel, newRing) end end local function toggle(self) if self:GetChecked() then state.selected:SetChecked(nil) state.selected = self if self == toggle1 then snap:SetText("") end if newRing:IsVisible() then PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) validate(); (self == toggle1 and name or snap):SetFocus() end else self:SetChecked(1) end end local function navigate(self) if IsControlKeyDown() then (toggle1:GetChecked() and toggle2 or toggle1):Click() elseif self ~= snap and snap:IsShown() then snap:SetFocus() else name:SetFocus() end end local function submit(self) if accept:IsEnabled() then accept:Click() else navigate(self) end end cancel:SetScript("OnClick", function() newRing:Hide() end) accept:SetScript("OnClick", function() if state.selected == toggle1 then api.createRing(name:GetText(), {limit="PLAYER"}) newRing:Hide() elseif api.createRing(name:GetText(), state.ring, state.bundle, importNested:GetChecked()) then newRing:Hide() end end) toggle1:SetScript("OnClick", toggle) toggle2:SetScript("OnClick", toggle) for i=1,2 do local v = i == 1 and name or snap v:SetScript("OnTabPressed", navigate) v:SetScript("OnEnterPressed", submit) v:SetScript("OnTextChanged", validate) v:SetScript("OnEditFocusLost", EditBox_ClearHighlight) end btnNewRing:SetScript("OnClick", function() title:SetText(L"Create a New Ring") toggle1:SetText(L"Empty ring") toggle2:SetText(L"Import snapshot") local w1, w2 = 32+toggle1:GetFontString():GetStringWidth(), 32+toggle2:GetFontString():GetStringWidth() toggle1:SetWidth(math.max(w1, 350 - math.max(175, w2))) toggle2:SetWidth(math.max(w2, 350 - math.max(175, w1))) nameLabel:SetText(L"Ring name:") snapLabel:SetText(L"Snapshot:") accept:SetText(L"Add Ring") cancel:SetText(L"Cancel") snap:SetText("") snap.cachedText, snap.cachedValue = nil name:SetText("") toggle1:Click() accept:Disable() importNested:SetChecked(false) TS:ShowFrameOverlay(panel, newRing) name:SetFocus() end) end ringContainer = CreateFrame("Frame", nil, panel) do ringContainer:SetPoint("TOP", ringDropDown, "BOTTOM", 75, 0) ringContainer:SetPoint("BOTTOM", panel, 0, 6) ringContainer:SetPoint("LEFT", panel, 50, 0) ringContainer:SetPoint("RIGHT", panel, -10, 0) CreateEdge(ringContainer, {edgeFile="Interface/Tooltips/UI-Tooltip-Border", tile=true, edgeSize=14}, nil, 0x7f7f7f) local function UpdateOnShow(self) self:SetScript("OnUpdate", nil) api.refreshDisplay() end ringContainer:SetScript("OnHide", function(self) if self:IsShown() then self:SetScript("OnUpdate", UpdateOnShow) end end) do -- up/down arrow buttons: ringContainer.prev and ringContainer.next local prev, next = CreateFrame("Button", nil, ringContainer), CreateFrame("Button", nil, ringContainer) prev:SetSize(22, 22) next:SetSize(22, 22) next:SetPoint("TOPRIGHT", ringContainer, "TOPLEFT", 2, 0) prev:SetPoint("RIGHT", next, "LEFT", 4, 0) prev:SetNormalTexture("Interface/ChatFrame/UI-ChatIcon-ScrollUp-Up") prev:SetPushedTexture("Interface/ChatFrame/UI-ChatIcon-ScrollUp-Down") prev:SetDisabledTexture("Interface/ChatFrame/UI-ChatIcon-ScrollUp-Disabled") prev:SetHighlightTexture("Interface/Buttons/UI-Common-MouseHilight") next:SetNormalTexture("Interface/ChatFrame/UI-ChatIcon-ScrollDown-Up") next:SetPushedTexture("Interface/ChatFrame/UI-ChatIcon-ScrollDown-Down") next:SetDisabledTexture("Interface/ChatFrame/UI-ChatIcon-ScrollDown-Disabled") next:SetHighlightTexture("Interface/Buttons/UI-Common-MouseHilight") next:SetID(1) prev:SetID(-1) local function handler(self) api.scrollSliceList(self:GetID()) end next:SetScript("OnClick", handler) prev:SetScript("OnClick", handler) ringContainer.prev, ringContainer.next = prev, next local cap = CreateFrame("Frame", nil, ringContainer) cap:SetPoint("TOPLEFT", ringContainer, "TOPLEFT", -38, 0) cap:SetPoint("BOTTOMRIGHT", ringContainer, "BOTTOMLEFT", -1, 0) cap:SetScript("OnMouseWheel", function(_, delta) local b = delta == 1 and prev or next if b:IsEnabled() then b:Click() end end) end ringContainer.slices = {} do local function onClick(self) PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) api.selectSlice(self:GetID(), self:GetChecked()) end local function dragStart(self) if ringContainer.disableSliceDrag then return end PlaySound(832) self.source = api.resolveSliceOffset(self:GetID()) dragBackdrop:Show() SetCursor(self.tex:GetTexture()) end local function dragAbort(self) local src = self.source if src then SetCursor(nil) dragBackdrop:Hide() self.source = nil end return src end local function dragStop(self) local source = dragAbort(self) if ringContainer.disableSliceDrag then return end local x, y = GetCursorPosition() PlaySound(833) local scale, l, b, w, h = self:GetEffectiveScale(), self:GetRect() local dy, dx = math.floor(-(y / scale - b - h-1)/(h+2)), x / scale - l if dx < -2*w or dx > 2*w then return api.deleteSlice(source) end if dx < -w/2 or dx > 3*w/2 then return end local dest = self:GetID() + dy if not ringContainer.slices[dest+1] or not ringContainer.slices[dest+1]:IsShown() then return end dest = api.resolveSliceOffset(dest) if dest ~= source then api.moveSlice(source, dest) end end for i=0,11 do local ico = createIconButton(nil, ringContainer, i) ico:SetPoint("TOP", ringContainer.prev, "BOTTOMRIGHT", -2, -34*i) ico:SetScript("OnClick", onClick) ico:RegisterForDrag("LeftButton") ico:SetScript("OnDragStart", dragStart) ico:SetScript("OnDragStop", dragStop) ico:SetScript("OnHide", dragAbort) ico.check = ico:CreateTexture(nil, "OVERLAY") ico.check:SetSize(8,8) ico.check:SetPoint("BOTTOMRIGHT", -1, 1) ico.check:SetTexture("Interface/FriendsFrame/StatusIcon-Online") ico.auto = ico:CreateTexture(nil, "OVERLAY", nil, 4) ico.auto:SetAllPoints() ico.auto:SetTexture("Interface/Buttons/UI-AutoCastableOverlay") ico.auto:SetTexCoord(14/64, 49/64, 14/64, 49/64) ringContainer.slices[i+1] = ico end end ringContainer.newSlice = createIconButton(nil, ringContainer, nil, true) do local b = ringContainer.newSlice b:SetSize(24,24) b.tex:SetTexture("Interface/GuildBankFrame/UI-GuildBankFrame-NewTab") b:SetScript("OnClick", function(self) PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) config.ui.HideTooltip(self) if IsAltKeyDown() then self:SetChecked(not self:GetChecked()) TS:ShowPromptOverlay(panel, L"Custom slice", L"Input a slice action specification:", (L"Example: %s."):format(GREEN_FONT_COLOR_CODE .. '"item", 19019|r'), nil, api.addCustomSlice, 0.95) return end if newSlice:IsShown() then return api.closeActionPicker("add-new-slice-button") end if sliceDetail:IsShown() then api.selectSlice() for i=1,#ringContainer.slices do ringContainer.slices[i]:SetChecked(nil) end end ringDetail:Hide() api.endSliceRepick() newSlice:Show() end) b:SetScript("OnEnter", function(self) GameTooltip:SetOwner(self, "ANCHOR_NONE") GameTooltip:SetPoint("LEFT", self, "RIGHT", 2, 0) GameTooltip:AddLine(L"Add a new slice", 1, 1, 1) GameTooltip:Show() end) b:SetScript("OnLeave", config.ui.HideTooltip) b:SetPoint("TOP", ringContainer.slices[12], "BOTTOM", 0, -2) end end ringDetail = CreateFrame("Frame", nil, ringContainer) do ringDetail:SetAllPoints() TS:EscapeCallback(ringDetail, function() api.deselectRing() end) ringDetail.name = CreateFrame("EditBox", nil, ringDetail) do local e = ringDetail.name e:SetHeight(24) e:SetPoint("TOPLEFT", 5, -5) e:SetPoint("TOPRIGHT", -5, -5) e:SetTextInsets(2,2,2,2) e:SetFontObject(GameFontNormalLarge) e:SetAutoFocus(false) prepEditBox(e, function(self) api.setRingProperty("name", self:GetText()) end) local ht = e:CreateTexture(nil, "BACKGROUND", nil, -3) ht:SetColorTexture(1,1,1,0.08) ht:SetAllPoints() ht:Hide() local function hideHilight() ht:Hide() end e:SetScript("OnEnter", function(self) if not self:HasFocus() then ht:Show() end end) e:SetScript("OnLeave", hideHilight) e:HookScript("OnEditFocusGained", hideHilight) end local tex = ringDetail.name:CreateTexture() tex:SetHeight(1) tex:SetPoint("BOTTOMLEFT", 0, -2) tex:SetPoint("BOTTOMRIGHT", 0, -2) tex:SetColorTexture(1,0.82,0, 0.5) ringDetail.scope = CreateFrame("Frame", "RKC_RingScopeDropDown", ringDetail, "UIDropDownMenuTemplate") ringDetail.scope:SetPoint("TOPLEFT", 250, -37) UIDropDownMenu_SetWidth(ringDetail.scope, 250) ringDetail.scope.label = ringDetail.scope:CreateFontString(nil, "OVERLAY", "GameFontHighlight") ringDetail.scope.label:SetPoint("TOPLEFT", ringDetail, "TOPLEFT", 10, -47) ringDetail.scope.label:SetText(L"Make this ring available to:") ringDetail.binding = config.createBindingButton(ringDetail) ringDetail.bindingContainerFrame = panel ringDetail.binding:SetPoint("TOPLEFT", 267, -68) ringDetail.binding:SetWidth(265) function ringDetail:SetBinding(bind) return api.setRingProperty("hotkey", bind or nil) end function ringDetail:OnBindingAltClick() self:ToggleAlternateEditor(api.getRingProperty("hotkey")) end ringDetail.binding.label = ringDetail.scope:CreateFontString(nil, "OVERLAY", "GameFontHighlight") ringDetail.binding.label:SetPoint("TOPLEFT", ringDetail, "TOPLEFT", 10, -73) ringDetail.binding.label:SetText(L"Binding:") ringDetail.bindingQuarantine = TS:CreateOptionsCheckButton(nil, ringDetail) ringDetail.bindingQuarantine:SetHitRectInsets(0,0,0,0) ringDetail.bindingQuarantine:SetPoint("RIGHT", ringDetail.binding, "LEFT", 0, 0) ringDetail.bindingQuarantine:SetScript("OnClick", function(self) PlayCheckboxSound(self) api.setRingProperty("hotkey", api.getRingProperty("quarantineBind")) end) ringDetail.bindingQuarantine:SetScript("OnEnter", config.ui.ShowControlTooltip) ringDetail.bindingQuarantine:SetScript("OnLeave", config.ui.HideTooltip) ringDetail.bindingQuarantine.tooltipText = L"To enable the default binding for this ring, check this box or change the binding." do -- ringDetail.rotation local s, sliderLeftMargin, centerLine = TS:CreateOptionsSlider(ringDetail, nil, 250) s:SetPoint("TOPLEFT", 270-sliderLeftMargin, -95) s:SetMinMaxValues(0, 345) s:SetValueStep(15) s:SetObeyStepOnDrag(true) s:SetScript("OnValueChanged", function(_, value) api.setRingProperty("offset", value) end) s.lo:SetText("0°") s.hi:SetText("345°") s.text:SetPoint("LEFT", ringDetail, "TOPLEFT", 10, -95-centerLine) s.text:SetText(L"Rotation:") s.text:Show() ringDetail.rotation, s.label = s, s.text end ringDetail.opportunistCA = TS:CreateOptionsCheckButton(nil, ringDetail) ringDetail.opportunistCA:SetPoint("TOPLEFT", 266, -118) ringDetail.opportunistCA:SetMotionScriptsWhileDisabled(1) ringDetail.opportunistCA.Text:SetText(L"Pre-select a quick action slice") ringDetail.opportunistCA:SetScript("OnEnter", config.ui.ShowControlTooltip) ringDetail.opportunistCA:SetScript("OnLeave", config.ui.HideTooltip) ringDetail.opportunistCA:SetScript("OnClick", function(self) PlayCheckboxSound(self) api.setRingProperty("noOpportunisticCA", (not self:GetChecked()) or nil) api.setRingProperty("noPersistentCA", (not self:GetChecked()) or nil) end) ringDetail.hiddenRing = TS:CreateOptionsCheckButton(nil, ringDetail) ringDetail.hiddenRing:SetPoint("TOPLEFT", ringDetail.opportunistCA, "BOTTOMLEFT", 0, 2) ringDetail.hiddenRing.Text:SetText(L"Hide this ring") ringDetail.hiddenRing:SetScript("OnClick", function(self) PlayCheckboxSound(self) api.setRingProperty("internal", self:GetChecked() and true or nil) end) ringDetail.embedRing = TS:CreateOptionsCheckButton(nil, ringDetail) ringDetail.embedRing:SetPoint("TOPLEFT", ringDetail.hiddenRing, "BOTTOMLEFT", 0, 2) ringDetail.embedRing.Text:SetText(L"Embed into other rings by default") ringDetail.embedRing:SetScript("OnClick", function(self) PlayCheckboxSound(self) api.setRingProperty("embed", self:GetChecked() and true or nil) end) ringDetail.firstOnOpen = TS:CreateOptionsCheckButton(nil, ringDetail) do local f = ringDetail.firstOnOpen f:SetPoint("TOPLEFT", ringDetail.embedRing, "BOTTOMLEFT", 0, 2) f:SetMotionScriptsWhileDisabled(1) f.Text:SetText(L"Use first slice when opened") f:SetScript("OnClick", function(self) PlayCheckboxSound(self) self.quarantineMark:Hide() api.setRingProperty("onOpen", self:GetChecked() and 1 or nil) end) f.quarantineMark = f:CreateTexture(nil, "ARTWORK") f.quarantineMark:SetAllPoints() f.quarantineMark:SetTexture(f:GetCheckedTexture():GetTexture()) f.quarantineMark:SetDesaturated(true) f.quarantineMark:SetVertexColor(1, 0.95, 0.85, 0.65) end ringDetail.optionsLabel = ringDetail:CreateFontString(nil, "OVERLAY", "GameFontHighlight") ringDetail.optionsLabel:SetPoint("TOPLEFT", ringDetail, "TOPLEFT", 10, -125) ringDetail.optionsLabel:SetText(L"Options:") ringDetail.editBindings = CreateButton(ringDetail, 210) ringDetail.editBindings:SetPoint("TOPLEFT", ringDetail, "TOPLEFT", 292, -214) ringDetail.editBindings:SetText(L"Customize bindings") ringDetail.editBindings:SetScript("OnClick", function() PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) api.showExternalEditor("slice-binding") end) ringDetail.editOptions = CreateButton(ringDetail, 210) ringDetail.editOptions:SetPoint("TOPLEFT", ringDetail.editBindings, "BOTTOMLEFT", 0, -2) ringDetail.editOptions:SetText(L"Customize options") ringDetail.editOptions:SetScript("OnClick", function() PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) api.showExternalEditor("opie-options") end) ringDetail.shareLabel = ringDetail:CreateFontString(nil, "OVERLAY", "GameFontHighlight") ringDetail.shareLabel:SetPoint("TOPLEFT", ringDetail, "TOPLEFT", 10, -265) ringDetail.shareLabel:SetText(L"Snapshot:") ringDetail.shareLabel2 = ringDetail:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmallLeft") ringDetail.shareLabel2:SetPoint("TOPLEFT", ringDetail, "TOPLEFT", 270, -265) ringDetail.shareLabel2:SetWidth(275) ringDetail.export = CreateButton(ringDetail) ringDetail.export:SetPoint("TOP", ringDetail.shareLabel2, "BOTTOM", 0, -4) ringDetail.export:SetText(L"Share ring") ringDetail.export:SetScript("OnClick", function(self) PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) api.exportRing(self.nested:IsShown() and self.nested:GetChecked() and true) end) ringDetail.export.nested = TS:CreateOptionsCheckButton(nil, ringDetail.export) do local f = ringDetail.export.nested f:SetPoint("TOPLEFT", ringDetail.shareLabel2, "BOTTOMLEFT", -4, -1) f.Text:SetText(L"Include nested rings") local function moveExportButton(self) ringDetail.export:SetPoint("TOP", ringDetail.shareLabel2, "BOTTOM", 0, self:IsVisible() and -22 or -4) end f:SetScript("OnClick", PlayCheckboxSound) f:SetScript("OnShow", moveExportButton) f:SetScript("OnHide", moveExportButton) end local exportBg, scroll = CreateFrame("Frame", nil, ringDetail) CreateEdge(exportBg, {edgeFile="Interface/Tooltips/UI-Tooltip-Border", bgFile="Interface/DialogFrame/UI-DialogBox-Background-Dark", tile=true, edgeSize=16, tileSize=16, insets={left=4,right=4,bottom=4,top=4}}, 0xb2000000, 0xb2b2b2) exportBg:SetSize(265, 124) exportBg:Hide() exportBg:SetPoint("TOPLEFT", ringDetail.shareLabel2, "BOTTOMLEFT", -2, -2) ringDetail.exportFrame, ringDetail.exportInput, scroll = exportBg, config.ui.multilineInput("RKC_ExportInput", exportBg, 235) scroll:SetPoint("TOPLEFT", 5, -4) scroll:SetPoint("BOTTOMRIGHT", -26, 4) ringDetail.exportInput:SetFontObject(GameFontHighlightSmall) ringDetail.exportInput:SetScript("OnEscapePressed", function() exportBg:Hide() ringDetail.export:Show() end) ringDetail.exportInput:SetScript("OnChar", function(self) local text = self:GetText() if text ~= "" and text ~= self.text then self:SetText(self.text or "") self:SetCursorPosition(0) self:HighlightText() end end) ringDetail.exportInput:SetScript("OnTextSet", function(self) self.text = self:GetText() end) exportBg:SetScript("OnHide", function(self) self:Hide() ringDetail.export:Show() ringDetail.shareLabel2:SetText(L"Take a snapshot of this ring to share it with others.") end) exportBg:SetScript("OnShow", function() ringDetail.export:Hide() ringDetail.shareLabel2:SetText((L"Import snapshots by clicking %s above."):format(NORMAL_FONT_COLOR_CODE .. L"New Ring..." .. "|r")) end) ringDetail.remove = CreateButton(ringDetail) ringDetail.remove:SetPoint("BOTTOMRIGHT", -10, 10) ringDetail.remove:SetText(L"Delete ring") ringDetail.remove:SetScript("OnClick", function() PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) api.deleteRing() end) ringDetail.restore = CreateButton(ringDetail) ringDetail.restore:SetPoint("RIGHT", ringDetail.remove, "LEFT", -20, 0) ringDetail.restore:SetText(L"Restore default") ringDetail.restore:SetScript("OnClick", function() PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) api.restoreDefault() end) ringDetail:Hide() end sliceDetail = CreateFrame("Frame", nil, ringContainer) do sliceDetail:SetAllPoints() sliceDetail.desc = sliceDetail:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") sliceDetail.desc:SetPoint("TOPLEFT", 7, -9) sliceDetail.desc:SetPoint("TOPRIGHT", -7, -7) sliceDetail.desc:SetJustifyH("LEFT") TS:EscapeCallback(sliceDetail, function() api.selectSlice() end) local oy = 37 sliceDetail.skipSpecs = CreateFrame("Frame", "RKC_SkipSpecDropdown", sliceDetail, "UIDropDownMenuTemplate") do local s = sliceDetail.skipSpecs s:SetPoint("TOPLEFT", 250, -oy) UIDropDownMenu_SetWidth(s, 250) oy = oy + 31 s.label = s:CreateFontString(nil, "OVERLAY", "GameFontHighlight") s.label:SetPoint("TOPLEFT", sliceDetail, "TOPLEFT", 10, -47) s.label:SetText(L"Show this slice for:") end sliceDetail.showConditional = TS:CreateLineInputBox(sliceDetail, true, 260) do local c = sliceDetail.showConditional c:SetPoint("TOPLEFT", 274, -oy) oy = oy + 23 c.label = c:CreateFontString(nil, "OVERLAY", "GameFontHighlight") c.label:SetPoint("TOPLEFT", sliceDetail, "TOPLEFT", 10, -73) c.label:SetText(L"Visibility conditional:") prepEditBox(c, function(self) api.setSliceProperty("show", self:GetText()) end) c:SetScript("OnEnter", function(self) GameTooltip:SetOwner(self, "ANCHOR_TOP") GameTooltip:AddLine(L"Conditional Visibility") GameTooltip:AddLine((L"If this macro conditional evaluates to %s, or if none of its clauses apply, this slice will be hidden."):format(GREEN_FONT_COLOR_CODE .. "hide" .. "|r"), HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b, 1) GameTooltip:AddLine((L"You may use extended macro conditionals; see %s for details."):format("|cff33DDFFhttps://townlong-yak.com/addons/opie/extended-conditionals|r"), HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b, 1) GameTooltip:AddLine((L"Example: %s."):format(GREEN_FONT_COLOR_CODE .. "[nocombat][mod]|r"), NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) GameTooltip:AddLine((L"Example: %s."):format(GREEN_FONT_COLOR_CODE .. "[combat,@target,noexists] hide; show|r"), NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) GameTooltip:Show() end) c:SetScript("OnLeave", config.ui.HideTooltip) end sliceDetail.color = TS:CreateLineInputBox(sliceDetail, true, 85) do local c = sliceDetail.color c:SetPoint("TOPLEFT", 274, -oy) oy = oy + 23 c:SetTextInsets(22, 0, 0, 0) c:SetMaxBytes(7) prepEditBox(c, function(self) local r,g,b = self:GetText():match("(%x%x)(%x%x)(%x%x)") if self:GetText() == "" then api.setSliceProperty("color") elseif not r then if self.oldValue == "" then self.placeholder:Show() end self:SetText(self.oldValue) else api.setSliceProperty("color", tonumber(r,16)/255, tonumber(g,16)/255, tonumber(b,16)/255) end end) c.label = c:CreateFontString(nil, "OVERLAY", "GameFontHighlight") c.label:SetPoint("TOPLEFT", sliceDetail, "TOPLEFT", 10, -96) c.label:SetText(L"Color:") c.placeholder = c:CreateFontString(nil, "OVERLAY", "GameFontHighlight") c.placeholder:SetPoint("LEFT", 18, 0) c.placeholder:SetText("|cffa0a0a0" .. L"(default)") c.button = CreateFrame("Button", nil, c) local b = sliceDetail.color.button b:SetSize(14, 14) b:SetPoint("LEFT") b.bg = sliceDetail.color.button:CreateTexture(nil, "BACKGROUND") b.bg:SetSize(12, 12) b.bg:SetPoint("CENTER") b.bg:SetColorTexture(1,1,1) b.bg:SetSnapToPixelGrid(false) b.bg:SetTexelSnappingBias(0) b:SetNormalTexture("Interface/ChatFrame/ChatFrameColorSwatch") b:SetScript("OnEnter", function(self) self.bg:SetVertexColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) end) b:SetScript("OnLeave", function(self) self.bg:SetVertexColor(1, 1, 1) end) b:SetScript("OnShow", b:GetScript("OnLeave")) local ctex = b:GetNormalTexture() ctex:SetSnapToPixelGrid(false) ctex:SetTexelSnappingBias(0) local function update(v) if ColorPickerFrame:IsShown() or v then return end api.setSliceProperty("color", ColorPickerFrame:GetColorRGB()) end b:SetScript("OnClick", function() local cp = ColorPickerFrame cp.previousValues, cp.hasOpacity, cp.func, cp.cancelFunc = true cp:SetColorRGB(ctex:GetVertexColor()) cp:Show() cp.func, cp.cancelFunc = update, update end) local ceil = math.ceil function c:SetColor(r,g,b, custom) if r and g and b and custom then c:SetText(("%02X%02X%02X"):format(ceil((r or 0)*255),ceil((g or 0)*255),ceil((b or 0)*255))) c.placeholder:Hide() else c:SetText("") c.placeholder:Show() end ctex:SetVertexColor(r or 0,g or 0,b or 0) end end sliceDetail.icon = CreateFrame("Button", nil, sliceDetail) do local f = sliceDetail.icon f:SetHitRectInsets(0,-280,0,0) f:SetSize(18, 18) f:SetPoint("TOPLEFT", 270, -oy-2) oy = oy + 23 f:SetHighlightTexture("Interface/Buttons/ButtonHilight-Square") f:SetNormalFontObject(GameFontHighlight) f:SetHighlightFontObject(GameFontGreen) f:SetPushedTextOffset(3/4, -3/4) f:SetText(" ") f:GetFontString():ClearAllPoints() f:GetFontString():SetPoint("LEFT", f, "RIGHT", 4, 0) f.icon = f:CreateTexture() f.icon:SetAllPoints() f.label = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight") f.label:SetPoint("TOPLEFT", sliceDetail, "TOPLEFT", 10, -119) f.label:SetText(L"Icon:") local frame = CreateFrame("Frame", nil, f) local ICONGRID_ROWS, ICONGRID_COLUMNS, ICONGRID_CELL_WIDTH, ICONGRID_CELL_HEIGHT = 7, 14, 36, 36 CreateEdge(frame, {bgFile = "Interface/ChatFrame/ChatFrameBackground", edgeFile = "Interface/DialogFrame/UI-DialogBox-Border", tile = true, tileSize = 32, edgeSize = 32, insets = { left = 11, right = 11, top = 12, bottom = 10 }}, 0xd8000000) frame:SetSize(42+ICONGRID_COLUMNS*ICONGRID_CELL_WIDTH, 20+ICONGRID_CELL_HEIGHT*ICONGRID_ROWS) frame:SetPoint("TOPLEFT", f, "TOPLEFT", -268, -18) frame:EnableMouse(1) frame:SetToplevel(1) frame:Hide() f:SetScript("OnClick", function() frame:SetShown(not frame:IsShown()) PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) end) frame:SetScript("OnHide", frame.Hide) do local ed = TS:CreateLineInputBox(frame, false, 280) local hint = ed:CreateFontString(nil, "OVERLAY", "GameFontHighlight") hint:SetPoint("CENTER") hint:SetText("|cffa0a0a0" .. L"(enter an icon name or path here)") local bg = ed:CreateTexture(nil, "BACKGROUND", nil, -8) bg:SetColorTexture(0,0,0,0.95) bg:SetPoint("TOPLEFT", -3, 3) bg:SetPoint("BOTTOMRIGHT", 3, -3) ed:SetPoint("BOTTOM", 0, 8) ed:SetFrameLevel(ed:GetFrameLevel()+5) ed:SetScript("OnEditFocusGained", function() hint:Hide() end) ed:SetScript("OnEditFocusLost", function(self) hint:SetShown(not self:GetText():match("%S")) end) ed:SetScript("OnEnterPressed", function(self) local text = self:GetText() if text:match("%S") then local path = GetPositiveFileIDFromPath(text) path = path or GetPositiveFileIDFromPath("Interface\\Icons\\" .. text) api.setSliceProperty("icon", path or text) end self:SetText("") self:ClearFocus() end) ed:SetScript("OnEscapePressed", function(self) self:SetText("") self:ClearFocus() end) frame.textInput, frame.textInputHint = ed, hint TS:EscapeCallback(frame, "TAB", function(_, key) if key == "TAB" then ed:SetFocus() else frame:Hide() end end) end local icons, selectedIcon = {} local function onClick(self) if selectedIcon then selectedIcon:SetChecked(nil) end PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) api.setSliceProperty("icon", self:GetChecked() and self.tex:GetTexture() or nil) selectedIcon = self:GetChecked() and self or nil end for i=0,(ICONGRID_COLUMNS*ICONGRID_ROWS)-1 do local j = createIconButton(nil, frame, i) j:SetPoint("TOPLEFT", 12 + (i % ICONGRID_COLUMNS)*ICONGRID_CELL_WIDTH, -12 - ICONGRID_CELL_HEIGHT*math.floor(i / ICONGRID_COLUMNS)) j:SetScript("OnClick", onClick) icons[i] = j end local icontex, initTexture = {}, nil local slider = CreateFrame("Slider", "RKC_IconSelectionSlider", frame, "UIPanelScrollBarTrimTemplate") slider:SetPoint("TOPRIGHT",-11, -26) slider:SetPoint("BOTTOMRIGHT", -11, 25) slider:SetValueStep(ICONGRID_COLUMNS) slider:SetObeyStepOnDrag(true) slider.scrollStep = ICONGRID_COLUMNS*4 slider.Up, slider.Down = RKC_IconSelectionSliderScrollUpButton, RKC_IconSelectionSliderScrollDownButton slider:SetScript("OnValueChanged", function(self, value) self.Up:SetEnabled(value > 1) self.Down:SetEnabled(icontex[value + #icons + 1] ~= nil) for i=0,#icons do local ico, tex = icons[i].tex, i == 0 and value == 1 and (initTexture or "Interface/Icons/INV_Misc_QuestionMark") or icontex[i+value-1] icons[i]:SetShown(not not tex) if tex then ico:SetTexture(tex) local tex = ico:GetTexture() icons[i]:SetChecked(f.selection == tex) selectedIcon = f.selection == tex and icons[i] or selectedIcon end end end) local function FixLooseIcons(f, t) local c = #t f(t) for i=c+1,#t do local e = t[i] if type(e) == "string" and not GetFileIDFromPath(e) then local c1 = e:gsub("%.$", "") local c2 = "Interface/Icons/" .. c1 local c3 = "Interface/Icons/" .. e t[i] = GetFileIDFromPath(c1) and c1 or GetFileIDFromPath(c2) and c2 or GetFileIDFromPath(c3) and c3 or e end end end frame:SetScript("OnShow", function(self) self:SetFrameLevel(math.min(9990, sliceDetail.icon:GetFrameLevel()+200)) icontex = GetMacroIcons() FixLooseIcons(GetLooseMacroIcons, icontex) GetMacroItemIcons(icontex) FixLooseIcons(GetLooseMacroItemIcons, icontex) slider:SetMinMaxValues(1, #icontex-#icons+16) if slider:GetValue() == 1 then slider:GetScript("OnValueChanged")(slider, slider:GetValue()) else slider:SetValue(1) end end) frame:SetScript("OnMouseWheel", function(_, delta) slider:SetValue(slider:GetValue()-delta*15) end) function f:SetIcon(ico, forced, ext) setIcon(self.icon, forced or ico, ext) initTexture = self.icon:GetTexture() self.selection = forced self:SetText(forced and L"Customized icon" or L"Based on slice action") if frame:IsShown() then slider:GetScript("OnValueChanged")(slider, slider:GetValue()) end end function f:HidePanel() frame:Hide() end end sliceDetail.fastClick = TS:CreateOptionsCheckButton(nil, sliceDetail) do local e = sliceDetail.fastClick e:SetHitRectInsets(0, -200, 4, 4) e:SetMotionScriptsWhileDisabled(1) e:SetPoint("TOPLEFT", 266, -oy) e:SetScript("OnClick", function(self) PlayCheckboxSound(self) return api.setSliceProperty("fastClick", self:GetChecked() and true or nil) end) e:SetScript("OnEnter", config.ui.ShowControlTooltip) e:SetScript("OnLeave", config.ui.HideTooltip) e.Text:SetText(L"Allow as quick action") e.label = sliceDetail:CreateFontString(nil, "OVERLAY", "GameFontHighlight") e.label:SetPoint("TOPLEFT", sliceDetail, "TOPLEFT", 10, -142) e.label:SetText(L"Options:") oy = oy + 21 end sliceDetail.collectionDrop = CreateFrame("Frame", "RKC_SliceOptions_Collection", sliceDetail, "UIDropDownMenuTemplate") do local w = sliceDetail.collectionDrop w:SetPoint("TOPLEFT", 250, -oy) UIDropDownMenu_SetWidth(w, 250) w.label = w:CreateFontString(nil, "OVERLAY", "GameFontHighlight") w.label:SetText(L"Display as:") w.label:SetPoint("LEFT", -240, 0) local modes = { false, "cycle", "shuffle", "random", "reset", "jump", [false]=L"Remember last rotation", cycle=L"Advance rotation after use", shuffle=L"Randomize rotation after use", random=L"Randomize rotation on display", reset=L"Reset rotation on display", jump=L"Display a jump slice", } function w:set(opt) if opt == "default" then w.rotationMode, w.embed = nil, nil elseif opt == "embed" then w.embed, w.rotationMode = true, nil else w.rotationMode, w.embed = opt, nil if opt == nil then w.embed = false end end api.setSliceProperty("rotationMode", opt, w.embed) w:text() end function w:text() local text, mode = "", modes[self.rotationMode or false] if self.embed == nil and self.rotationMode == nil then text = L"Not customized" elseif self.embed then text = L"Embed slices in this ring" elseif self.rotationMode == "jump" then text = mode elseif mode then text = (NORMAL_FONT_COLOR_CODE .. L"Nested ring: %s"):format("|r" .. mode) end UIDropDownMenu_SetText(self, text) end function w:initialize() local info = {minWidth=self:GetWidth()-40, func=w.set} local isNotEmbed, isDefault = not self.embed, self.embed == nil and self.rotationMode == nil info.text, info.arg1, info.checked = L"Not customized", "default", isDefault UIDropDownMenu_AddButton(info) UIDropDownMenu_AddSeparator() info.text, info.isTitle, info.notCheckable = L"Display as a nested ring", true, true UIDropDownMenu_AddButton(info) info.isTitle, info.disabled, info.notCheckable = nil for i=1,#modes do local v = modes[i] info.arg1, info.text = v or nil, modes[v] info.checked = isNotEmbed and self.rotationMode == (v or nil) and not isDefault if v == "jump" then UIDropDownMenu_AddSeparator() end UIDropDownMenu_AddButton(info) end UIDropDownMenu_AddSeparator() info.arg1, info.text = "embed", L"Embed slices in this ring" info.checked = not isNotEmbed UIDropDownMenu_AddButton(info) end end do -- .editorContainer local f; f, editorHost = AB:CreateEditorHost(sliceDetail) f:SetPoint("TOPLEFT", sliceDetail.fastClick.label, "BOTTOMLEFT", 0, -10) f:SetPoint("BOTTOMRIGHT", -10, 36) f.optionsColumnOffset = 256 function f:OnActionChanged(ed) return editorHost:IsCurrentEditor(ed) and api.setSliceAction() end function f:SaveAction() -- DEPRECATED [2303/Y8] api.setSliceAction() end function f:SetVerticalOffset(ofsY) f:SetPoint("TOPLEFT", sliceDetail.fastClick.label, "BOTTOMLEFT", 0, -6-ofsY) end sliceDetail.editorContainer = f end sliceDetail.remove = CreateButton(sliceDetail) sliceDetail.remove:SetPoint("BOTTOMRIGHT", -10, 10) sliceDetail.remove:SetText(L"Delete slice") sliceDetail.remove:SetScript("OnClick", function() return api.deleteSlice() end) sliceDetail.repick = CreateButton(sliceDetail) sliceDetail.repick:SetPoint("TOPRIGHT", sliceDetail.remove, "TOPLEFT", -20, 0) sliceDetail.repick:SetText(L"Change action") sliceDetail.repick:SetScript("OnClick", function() PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) return api.beginSliceRepick() end) end newSlice = CreateFrame("Frame", nil, ringContainer) do newSlice:SetAllPoints() newSlice:Hide() local NUM_VISIBLE_CATS, NUM_VISIBLE_ACTION_ROWS = 22, 12 newSlice.slider = XU:Create("ScrollBar", nil, newSlice) do local s = newSlice.slider s:SetPoint("TOPLEFT", 162, -3) s:SetPoint("BOTTOMLEFT", 162, 3) s:SetMinMaxValues(0, 20) s:SetValueStep(1) s:SetWindowRange(NUM_VISIBLE_CATS) s:SetStepsPerPage(NUM_VISIBLE_CATS-2) local cap = CreateFrame("Frame", nil, newSlice) cap:SetPoint("TOPLEFT") cap:SetPoint("BOTTOMRIGHT", s, "BOTTOMRIGHT") cap:SetScript("OnMouseWheel", function(_, delta) s:Step(-5*delta, true) end) end local cats, actions, searchCat, selectCategory, selectedCategory, selectedCategoryId = {}, {} local performSearch do local function matchAction(q, ...) local _, aname = AB:GetActionDescription(...) if type(aname) ~= "string" then return end aname = aname:match("|") and aname:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):gsub("|T.-|t", ""):lower() or aname:lower() return not not aname:match(q) end function performSearch(query, inCurrentCategory) searchCat = selectedCategory if not inCurrentCategory then searchCat = AB:GetCategoryContents(1) for i=2,AB:GetNumCategories() do searchCat = AB:GetCategoryContents(i, searchCat) end end searchCat:filter(matchAction, query:lower()) selectCategory(-1) end end do -- newSlice.search local s = TS:CreateLineInputBox(newSlice, true, 153) s:SetPoint("TOPLEFT", 7, -1) s:SetTextInsets(16, 0, 0, 0) local i = s:CreateTexture(nil, "OVERLAY") i:SetSize(14, 14) i:SetPoint("LEFT", 0, -1) i:SetTexture("Interface/Common/UI-Searchbox-Icon") local l, tip = s:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall"), CreateFrame("GameTooltip", "RKC_SearchTip", newSlice, "GameTooltipTemplate") l:SetPoint("LEFT", 16, 0) l:SetText(L"Search") s:SetScript("OnEditFocusGained", function(s) l:Hide() i:SetVertexColor(0.90, 0.90, 0.90) tip:SetFrameStrata("TOOLTIP") tip:SetOwner(s, "ANCHOR_BOTTOM") tip:AddLine((L"Press %s to search"):format(HIGHLIGHT_FONT_COLOR_CODE .. GetBindingText("ENTER") .. "|r")) tip:AddLine((L"%s to search within current results"):format(HIGHLIGHT_FONT_COLOR_CODE .. GetBindingText("CTRL-ENTER") .. "|r"), nil, nil, nil, true) tip:AddLine((L"%s to cancel"):format(HIGHLIGHT_FONT_COLOR_CODE .. GetBindingText("ESCAPE") .. "|r"), true) tip:Show() end) s:SetScript("OnEditFocusLost", function(s) l:SetShown(not s:GetText():match("%S")) i:SetVertexColor(0.75, 0.75, 0.75) tip:Hide() end) s:SetScript("OnEnterPressed", function(s) s:ClearFocus() if s:GetText():match("%S") then performSearch(s:GetText(), IsControlKeyDown() and selectedCategory) end end) newSlice.search, s.ico, s.label = s, i, l end local catbg = newSlice:CreateTexture(nil, "BACKGROUND") catbg:SetPoint("TOPLEFT", 2, -2) catbg:SetPoint("RIGHT", newSlice, "RIGHT", -2, 0) catbg:SetPoint("BOTTOM", 0, 2) catbg:SetColorTexture(0,0,0, 0.65) local function onCatClick(self) PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) selectCategory(self:GetID()) end local catContainer = CreateFrame("Frame", nil, newSlice) catContainer:SetClipsChildren(true) catContainer:SetSize(159, NUM_VISIBLE_CATS*20) catContainer:SetPoint("TOPLEFT", 2, -22) local catOrigin = CreateFrame("Frame", nil, catContainer) catOrigin:Hide() catOrigin:SetSize(159, 1) catOrigin:SetPoint("TOPLEFT") for i=1, NUM_VISIBLE_CATS+1 do local b = CreateFrame("Button", nil, catContainer) b:SetSize(159, 20) b:SetNormalTexture("Interface/AchievementFrame/UI-Achievement-Category-Background") b:SetHighlightTexture("Interface/AchievementFrame/UI-Achievement-Category-Highlight") b:GetNormalTexture():SetTexCoord(7/256, 162/256, 5/32, 24/32) b:GetHighlightTexture():SetTexCoord(7/256, 163/256, 5/32, 24/32) b:GetNormalTexture():SetVertexColor(0.6, 0.6, 0.6) b:SetNormalFontObject(GameFontHighlight) b:SetHighlightFontObject(GameFontHighlight) b:SetPushedTextOffset(0,0) b:SetText(" ") b:GetFontString():SetPoint("CENTER", 0, 1) b:SetScript("OnClick", onCatClick) cats[i] = b b:SetPoint("TOPLEFT", catOrigin, 0, 20-20*i) end newSlice.desc = newSlice:CreateFontString(nil, "OVERLAY", "GameFontHighlight") newSlice.desc:SetPoint("TOPLEFT", newSlice.slider, "TOPRIGHT", 2, -6) newSlice.desc:SetPoint("RIGHT", -24, 0) newSlice.desc:SetHeight(26) newSlice.desc:SetJustifyV("TOP") newSlice.desc:SetJustifyH("CENTER") newSlice.desc:SetText(L"Select an action by double clicking.") newSlice.close = CreateFrame("Button", "RKC_CloseNewSliceBrowser", newSlice, "UIPanelCloseButton") newSlice.close:SetPoint("TOPRIGHT", 3, 4) newSlice.close:SetSize(30, 30) newSlice.close:SetFrameLevel(newSlice:GetFrameLevel()+120) newSlice.close:SetScript("OnClick", function() PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) api.closeActionPicker("close-picker-button") end) TS:EscapeCallback(newSlice.close, function() api.closeActionPicker() end) local b = newSlice.close:CreateTexture(nil, "BACKGROUND") if MODERN then b:SetAtlas("UI-Frame-TopCornerRight") b:SetTexCoord(9/33, 1, 0, 23/33) else b:SetTexture("Interface/FrameGeneral/UI-Frame") b:SetTexCoord(90/128, 114/128, 1/128, 24/128) end b:SetPoint("TOPLEFT", 4, -5) b:SetPoint("BOTTOMRIGHT", -5, 4) b:SetVertexColor(0.6,0.6,0.6) newSlice.slider2 = XU:Create("ScrollBar", nil, newSlice) do local s = newSlice.slider2 s:SetPoint("TOPRIGHT", -2, COMPAT > 11403 and -26 or -22) s:SetPoint("BOTTOMRIGHT", -2, 2) s:SetMinMaxValues(0, 20) s:SetValueStep(1) s:SetStepsPerPage(NUM_VISIBLE_ACTION_ROWS-2) s:SetWindowRange(NUM_VISIBLE_ACTION_ROWS) local cap = CreateFrame("Frame", nil, newSlice) cap:SetPoint("TOPRIGHT") cap:SetPoint("BOTTOMLEFT", newSlice.slider, "BOTTOMRIGHT") cap:SetScript("OnMouseWheel", function(_, delta) s:Step(-4*delta, true) end) end local function onClick(self) PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON) api.addSlice(nil, selectedCategory(self:GetID())) end local function onDragStart(self) if newSlice.disableDrag then return end PlaySound(832) dragBackdrop:Show() SetCursor(self.ico:GetTexture()) self.dragActive = true end local function onDragAbort(self) if self.dragActive then self.dragActive = nil SetCursor(nil) dragBackdrop:Hide() end end local function onDragStop(self) onDragAbort(self) if newSlice.disableDrag then return end PlaySound(833) local e, x, y = ringContainer.slices[1], GetCursorPosition() if not e:GetLeft() then e = ringContainer.prev end local scale, l, b, w, h = e:GetEffectiveScale(), e:GetRect() local dy, dx = math.floor(-(y / scale - b - h-1)/(h+2)+0.5), x / scale - l if dx < -w/2 or dx > 3*w/2 then return end if dy < -1 or dy > (#ringContainer.slices+1) then return end api.addSlice(dy, selectedCategory(self:GetID())) end local function onEnter(self) GameTooltip_SetDefaultAnchor(GameTooltip, self) if type(self.tipFunc) == "function" then securecall(self.tipFunc, GameTooltip, self.tipFuncArg) else GameTooltip:AddLine(self.name:GetText()) end GameTooltip:Show() end local actionsContainer = CreateFrame("Frame", nil, newSlice) actionsContainer:SetClipsChildren(true) actionsContainer:SetPoint("TOPLEFT", newSlice.desc, "BOTTOMLEFT", 0, 8) actionsContainer:SetSize(344, 36*NUM_VISIBLE_ACTION_ROWS-1) local actionsOrigin = CreateFrame("Frame", nil, actionsContainer) actionsOrigin:SetSize(1,1) actionsOrigin:SetPoint("TOPLEFT") actionsOrigin:Hide() for i=1,NUM_VISIBLE_ACTION_ROWS*2+2 do local f = CreateFrame("Button", nil, actionsContainer) f:SetSize(170, 34) f:SetPoint("TOPLEFT", actionsOrigin, "TOPLEFT", 172*(1 - i % 2), -math.floor((i-1)/2)*36) f:RegisterForDrag("LeftButton") actions[i] = f f:SetScript("OnDragStart", onDragStart) f:SetScript("OnDragStop", onDragStop) f:SetScript("OnDoubleClick", onClick) f:SetScript("OnEnter", onEnter) f:SetScript("OnHide", onDragAbort) f:SetScript("OnLeave", config.ui.HideTooltip) f.ico = f:CreateTexture(nil, "ARTWORK") f.ico:SetSize(32,32) f.ico:SetPoint("LEFT", 1, 0) addIconSlotTextures(f.ico, 32) f.name = f:CreateFontString(nil, "OVERLAY", "GameFontNormal") f.name:SetHeight(12) f.name:SetPoint("TOPLEFT", f.ico, "TOPRIGHT", 3, -2) f.name:SetPoint("RIGHT", -2, 0) f.name:SetJustifyH("LEFT") f.sub = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") f.sub:SetPoint("TOPLEFT", f.name, "BOTTOMLEFT", 0, -2) f.sub:SetPoint("RIGHT", -2, 0) f.sub:SetJustifyH("LEFT") f:SetHighlightTexture("Interface/Buttons/ButtonHilight-Square") f:GetHighlightTexture():SetAllPoints(f.ico) end local function syncActions() local sv = newSlice.slider2:GetValue() local base = math.floor(sv)*2 actionsOrigin:SetPoint("TOPLEFT", 0, 36*(sv%1)) for i=1,#actions do local e, id = actions[i], i + base if id <= #selectedCategory then local stype, sname, sicon, extico, tipfunc, tiparg = AB:GetActionListDescription(selectedCategory(id)) pcall(setIcon, e.ico, sicon, extico) e.tipFunc, e.tipFuncArg = tipfunc, tiparg e.name:SetText(sname) e.sub:SetText(stype) e:SetID(id) e:Show() else e:Hide() end end end local function syncCats(_, base) local fr = base % 1 base = base - fr catOrigin:SetPoint("TOPLEFT", 0, fr*20) for i=1,#cats do local e, category, id = cats[i], AB:GetCategoryInfo(i+base), i+base e:SetShown(not not category) e:SetID(id) e[id == selectedCategoryId and "LockHighlight" or "UnlockHighlight"](e) e:SetText(category) end if selectedCategoryId == -1 then newSlice.search.ico:SetVertexColor(0.3, 1, 0) else newSlice.search.ico:SetVertexColor(0.75, 0.75, 0.75) end end function selectCategory(id) selectedCategoryId, selectedCategory = id, id == -1 and searchCat or AB:GetCategoryContents(id) if id ~= -1 then newSlice.search:SetText("") newSlice.search.label:Show() end syncCats(newSlice.slider, newSlice.slider:GetValue()) local xv = math.max(0, math.ceil((#selectedCategory - #actions + 2)/2)) newSlice.slider2:SetMinMaxValues(0, xv) newSlice.slider2:SetValue(0) syncActions() newSlice.search:ClearFocus() end newSlice.slider:SetScript("OnValueChanged", syncCats) newSlice.slider2:SetScript("OnValueChanged", syncActions) newSlice:SetScript("OnShow", function(self) selectCategory(1) self.slider:SetMinMaxValues(0, math.max(0, AB:GetNumCategories() - #cats)) self.slider:SetValue(0) end) end local PLAYER_CLASS, PLAYER_CLASS_UC = UnitClass("player") local PLAYER_CLASS_COLOR_HEX = RAID_CLASS_COLORS[PLAYER_CLASS_UC].colorStr:sub(3) local function getSliceInfo(slice) return securecall(AB.GetActionDescription, AB, slice) end local function getSliceColor(slice, sicon) local c, r,g,b = true, (type(slice.c) == "string" and slice.c or ""):match("(%x%x)(%x%x)(%x%x)") if ORI and not r then c, r,g,b = false, ORI:GetTexColor(slice.icon or sicon or "Interface\\Icons\\INV_Misc_QuestionMark") elseif r then r,g,b = tonumber(r,16)/255, tonumber(g,16)/255, tonumber(b,16)/255 end return r,g,b, c end local function isCollectionSlice(...) local actType = select(7, AB:GetActionDescription(...)) if actType ~= nil then return actType == "collection" end local aid = AB:GetActionSlot(...) if aid then return AB:GetSlotImplementation(aid) == "collection" end end local function dropKeys(t, k, ...) if k == nil then return end t[k] = nil return dropKeys(t, ...) end local decodeConstantList do local stringEscapes = {a="\a",b="\b",f="\f",n="\n",r="\r",t="\t",v="\v",["\\"]="\\",["'"]="'",['"']='"'} local function decodeStringEscape(w, f, s) local v = stringEscapes[f] if v then return v .. s end v = f >= "0" and f <= "9" and tonumber(w) return v and (v < 256 and string.char(v) or error("Invalid escape sequence")) or w end function decodeConstantList(text, start) local rv, ve, ps, w, c, pe = nil, nil, text:match("^%s*()(([%dtfn\"'%[])[^,%s\\\"']*)%s*()", start) if c == "'" or c == '"' then repeat w, pe = text:match('(\\*)' .. c .. '()', pe) until pe == nil or #w % 2 == 0 if pe then rv, ve = text:sub(ps+1,pe-2):gsub('||','|'):gsub('\\((.)(%d?%d?))', decodeStringEscape), pe end elseif c == '[' then w, rv, ve = text:match('^%[(=*)%[(.-)%]%1%]()', ps) if rv then rv = rv:gsub('||', '|') end elseif w == "false" or w == "true" then ve, rv = pe, w == "true" elseif w == "nil" or tonumber(w) then ve, rv = pe, tonumber(w) end if ve then local ns = text:match("^%s*,()", ve) if ns then return rv, decodeConstantList(text, ns) elseif text:match("^%s*$", ve) then return rv end end error("Invalid encoding at position " .. tostring(start)) end end local function genBundledRingName(mainNew, mainOld, nestName, usedNames, seq) if type(mainOld) == "string" and type(nestName) == "string" and nestName:sub(1,#mainOld) == mainOld then nestName = nestName:sub(#mainOld + 1 + #nestName:match("^%s*:?%s*", 1+#mainOld)) end local cand = mainNew .. ": " .. (nestName or seq) if usedNames[cand] then cand = cand .. "/" .. seq while usedNames[cand] do cand = ("%s: %s [%04x%04x]"):format(mainNew, nestName or seq, math.random(2^16)-1, math.random(2^16)-1) end end usedNames[cand] = seq return cand end local function setImportedRingProps(name, data) data.name, data.limit = name, data.limit == "PLAYER" and FULLNAME or (type(data.limit) == "string" and data.limit:match("^[A-Z]+$") or nil) end local function updateImportedRingContents(data, ringNameMap) for i=1,#data do local e = data[i] local s = ringNameMap[e and e[1] == "ring" and e[2] or nil] if s then e[2] = s end end end local ringNameMap, ringOrderMap, ringTypeMap, ringNames, currentRing, currentRingName, sliceBaseIndex, currentSliceIndex, repickSlice = {}, {}, {}, {} local typePrefix = { MINE="|cff25bdff|TInterface/FriendsFrame/UI-Toast-FriendOnlineIcon:14:14:0:1:32:32:8:24:8:24:30:190:255|t ", PERSONAL="|cffd659ff|TInterface/FriendsFrame/UI-Toast-FriendOnlineIcon:14:14:0:1:32:32:8:24:8:24:180:0:255|t ", HORDE=MODERN and "|cffff3000|A:QuestPortraitIcon-Horde-small:18:18:-1:-2|a" or "|cffff3000|A:poi-horde:16:16:-2:0|a", ALLIANCE=MODERN and "|cff00a0ff|A:QuestPortraitIcon-Alliance-small:20:18:-2:0|a" or "|cff00a0ff|A:poi-alliance:17:20:-2:0|a", } do for k, v in pairs(CLASS_ICON_TCOORDS) do local cc = RAID_CLASS_COLORS[k] typePrefix[k] = ("|cff%s|TInterface/GLUES/CHARACTERCREATE/UI-CharacterCreate-Classes:16:16:0:0:256:256:%d:%d:%d:%d|t "):format(cc and cc.colorStr:sub(3) or "a0ff00", v[1]*256+6,v[2]*256-6,v[3]*256+6,v[4]*256-6) end end local function sortNames(a,b) local oa, ob, na, nb, ta, tb = ringOrderMap[a] or 5, ringOrderMap[b] or 5, ringNameMap[a] or "", ringNameMap[b] or "", ringTypeMap[a] or "", ringTypeMap[b] or "" return oa < ob or (oa == ob and ta < tb) or (oa == ob and ta == tb and na < nb) or false end local function ringDropDown_EntryFormat(k) return (typePrefix[ringTypeMap[k]] or "") .. (ringNameMap[k] or "?"), currentRingName == k end function ringDropDown:initialize(level, nameList) local playerName, playerServer = UnitFullName("player") local playerFullName = playerName .. "-" .. playerServer local info = {func=api.selectRing, minWidth=level == 1 and (self:GetWidth()-40) or nil} if level == 1 then ringNames = {hidden={}, other={}} for name, dname, active, _slices, internal, limit in RK:GetManagedRings() do table.insert(active and (internal and ringNames.hidden or ringNames) or ringNames.other, name) local isFactionLimit = (limit == "Alliance" or limit == "Horde") and limit:upper() or nil local rtype = type(limit) ~= "string" and "GLOBAL" or limit == playerFullName and "MINE" or isFactionLimit or limit:match("[^A-Z]") and "PERSONAL" or limit ringNameMap[name], ringOrderMap[name], ringTypeMap[name] = dname, (not active and (rtype == "PERSONAL" and 12 or 10)) or isFactionLimit and 4 or (limit and (limit:match("[^A-Z]") and 0 or 2)), rtype end table.sort(ringNames, sortNames) table.sort(ringNames.hidden, sortNames) table.sort(ringNames.other, sortNames) if #ringNames == 0 and #ringNames.hidden == 0 and #ringNames.other == 0 then btnNewRing:Click() return end elseif nameList then XU:Create("ScrollableDropDownList", 2, nameList, ringDropDown_EntryFormat, api.selectRing) return end local hasHidden = ringNames.hidden and #ringNames.hidden > 0 local hasOther = ringNames.other and #ringNames.other > 0 XU:Create("ScrollableDropDownList", 1, ringNames, ringDropDown_EntryFormat, api.selectRing, hasHidden or hasOther) info.hasArrow, info.notCheckable, info.padding, info.fontObject = 1, 1, 32, GameFontNormalSmall info.text, info.func, info.checked = nil if hasHidden then info.menuList, info.text = ringNames.hidden, L"Hidden rings" UIDropDownMenu_AddButton(info, level) end if hasOther then info.menuList, info.text = ringNames.other, L"Inactive rings" UIDropDownMenu_AddButton(info, level) end end function api.createRing(name, data, bundle, importNested) local name = name:match("^%s*(.-)%s*$") if name == "" then return false end local iname = RK:GenFreeRingName(name) local mapRings, reservedINames, usedNames, nr = {}, importNested and {[iname]=1}, {[name]=true}, 2 if bundle then for k,v in pairs(bundle) do if v == 0 then mapRings[k] = iname elseif type(v) == "table" and importNested then setImportedRingProps(genBundledRingName(name, data.name, v.name, usedNames, nr), v) local n = RK:GenFreeRingName(v.name, reservedINames) mapRings[k], reservedINames[n], nr = n, nr, nr + 1 end end if importNested then for k,v in pairs(bundle) do if type(v) == "table" then updateImportedRingContents(v, mapRings) SaveRingVersion(mapRings[k], false) api.saveRing(mapRings[k], v) end end end end setImportedRingProps(name, data) updateImportedRingContents(data, mapRings) SaveRingVersion(iname, false) api.saveRing(iname, data) api:selectRing(iname) return true end function api.selectRing(_, name) CloseDropDownMenus() ringDetail:Hide() api.hideSliceDetail() newSlice:Hide() ringContainer.newSlice:SetChecked(nil) local desc = RK:GetRingDescription(name) currentRing, currentRingName, repickSlice = nil if not desc then return end RK:SoftSync(name) UIDropDownMenu_SetText(ringDropDown, desc.name or name) ringDetail.rotation:SetValue(desc.offset or 0) ringDetail.name:SetText(desc.name or name) ringDetail.hiddenRing:SetChecked(desc.internal) ringDetail.embedRing:SetChecked(desc.embed) currentRing, currentRingName, sliceBaseIndex, currentSliceIndex = desc, name, 1 api.refreshDisplay() ringDetail:Show() ringDetail.scope:text() ringDetail.export.nested:SetChecked(true) api.updateRingLine(true) ringContainer:Show() end function api.hideSliceDetail() sliceDetail:Hide() editorHost:Clear() end function api.updateRingLine(scanForNestedRings) ringContainer.prev:SetEnabled(sliceBaseIndex > 1) ringContainer.next:Disable() local onOpen, lastWidget = currentRing.onOpen for i=sliceBaseIndex,#currentRing do local e = ringContainer.slices[i-sliceBaseIndex+1] if not e then ringContainer.next:Enable() break end local _, _, sicon, icoext = getSliceInfo(currentRing[i]) pcall(setIcon, e.tex, currentRing[i].icon or sicon, icoext) e.check:SetShown(RK:IsRingSliceActive(currentRingName, i)) e.auto:SetShown(onOpen == i) e:SetChecked(currentSliceIndex == i) e:Show() lastWidget = e end ringContainer.newSlice:SetPoint("TOP", lastWidget or ringContainer.slices[1], lastWidget and "BOTTOM" or "TOP", 0, -2) for i=#currentRing-sliceBaseIndex+2,#ringContainer.slices do ringContainer.slices[i]:Hide() end if scanForNestedRings then local hasNestedCustomRings = false for i=1,#currentRing do local e = currentRing[i] if e[1] == "ring" and e[2] ~= currentRingName and select(4,RK:GetRingInfo(e[2])) then hasNestedCustomRings = true break end end ringDetail.export.nested:SetShown(hasNestedCustomRings) end end function api.scrollSliceList(dir) sliceBaseIndex = math.max(1,sliceBaseIndex + dir) api.updateRingLine() end function api.resolveSliceOffset(id) return sliceBaseIndex + id end function sliceDetail.skipSpecs:toggle(id) self = sliceDetail.skipSpecs local v, c = self.val:gsub("/" .. id .. "/", "/") if c == 0 then v = "/" .. id .. v end self.val = v api.setSliceProperty("skipSpecs") self:text() end function sliceDetail.skipSpecs:SetValue(skip) self.val = type(skip) == "string" and skip ~= "" and ("/" .. skip .. "/") or "/" self:text() end function sliceDetail.skipSpecs:GetValue() return self.val:match("^/(.+)/$") end function sliceDetail.skipSpecs:text() if not MODERN then UIDropDownMenu_DisableDropDown(self) return UIDropDownMenu_SetText(self, L"All characters") end local text, u, skipSpecs = "", GetNumSpecializations(), self.val for i=1, u do local id, name = GetSpecializationInfo(i) if not skipSpecs:match("/" .. id .. "/") then text, u = text .. (text == "" and "" or ", ") .. name, u - 1 end end if u == 0 then text = (L"All %s specializations"):format("|cff" .. PLAYER_CLASS_COLOR_HEX .. PLAYER_CLASS .. "|r") elseif text == "" then text = (L"No %s specializations"):format("|cff" .. PLAYER_CLASS_COLOR_HEX .. PLAYER_CLASS .. "|r") else text = (L"Only %s"):format("|cff" .. PLAYER_CLASS_COLOR_HEX .. text .. "|r") end UIDropDownMenu_SetText(self, text) end function sliceDetail.skipSpecs:initialize() local info = {func=self.toggle, isNotRadio=true, minWidth=self:GetWidth()-40, keepShownOnClick=true} local skip = self.val or "" for i=1, GetNumSpecializations() do local id, name, _, icon = GetSpecializationInfo(i) info.text, info.arg1, info.checked = "|T" .. icon .. ":16:16:0:0:64:64:4:60:4:60|t " .. name, id, not skip:match("/" .. id .. "/") UIDropDownMenu_AddButton(info) end end function ringDetail.scope:initialize() local luFaction, lFaction = UnitFactionGroup("player") local info = {func=self.set, minWidth=self:GetWidth()-40} info.text, info.checked = L"All characters", currentRing.limit == nil UIDropDownMenu_AddButton(info) info.text, info.checked, info.arg1 = (L"All %s characters"):format("|cff" .. (luFaction == "Horde" and "ff3000" or "00a0ff") .. lFaction .. "|r"), currentRing.limit == luFaction, luFaction UIDropDownMenu_AddButton(info) info.text, info.checked, info.arg1 = (L"All %s characters"):format("|cff" .. PLAYER_CLASS_COLOR_HEX .. PLAYER_CLASS .. "|r"), currentRing.limit == PLAYER_CLASS_UC, PLAYER_CLASS_UC UIDropDownMenu_AddButton(info) info.text, info.checked, info.arg1 = (L"Only %s"):format("|cff" .. PLAYER_CLASS_COLOR_HEX .. SHORTNAME .. "|r"), currentRing.limit == FULLNAME, FULLNAME UIDropDownMenu_AddButton(info) end function ringDetail.scope:set(arg1) api.setRingProperty("limit", arg1) end function ringDetail.scope:text() local limit = currentRing.limit local isFactionLimit = (limit == "Alliance" or limit == "Horde") UIDropDownMenu_SetText(self, type(limit) ~= "string" and L"All characters" or isFactionLimit and (L"All %s characters"):format((limit == "Horde" and "|cffff3000" or "|cff00a0ff") .. (limit == "Horde" and FACTION_HORDE or FACTION_ALLIANCE) .. "|r") or limit:match("[^A-Z]") and (L"Only %s"):format("|cff" .. (limit == FULLNAME and PLAYER_CLASS_COLOR_HEX .. SHORTNAME or ("d659ff" .. limit)) .. "|r") or RAID_CLASS_COLORS[limit] and (L"All %s characters"):format("|cff" .. RAID_CLASS_COLORS[limit].colorStr:sub(3) .. (UnitSex("player") == 3 and LOCALIZED_CLASS_NAMES_FEMALE or LOCALIZED_CLASS_NAMES_MALE)[limit] .. "|r") ) end function api.getRingProperty(key) if key == "hotkey" then if PC:GetRingInfo(currentRingName) then local skey, _, over = PC:GetRingBinding(currentRingName) if over then return skey end end if currentRing.hotkey and not PC:GetOption("UseDefaultBindings", currentRingName) then return currentRing.hotkey, "|cffa0a0a0" elseif currentRing.quarantineBind and not currentRing.hotkey then return currentRing.quarantineBind, "|cffa0a0a0" end end return currentRing[key] end function api.setRingProperty(name, value) if not currentRing then return end currentRing[name] = value if name == "limit" then ringDetail.scope:text() ringOrderMap[currentRingName] = value ~= nil and (value:match("[^A-Z]") and 0 or 2) or nil elseif name == "hotkey" then currentRing.quarantineBind = nil ringDetail.bindingQuarantine:Hide() ringDetail.binding:SetBindingText(value) if PC:GetRingInfo(currentRingName) then config.undo:saveActiveProfile() PC:SetRingBinding(currentRingName, value) end elseif name == "internal" then local source, dest = value and ringNames or ringNames.hidden, value and ringNames.hidden or ringNames for i=1,#source do if source[i] == currentRingName then table.remove(source, i) break end end table.insert(dest, currentRingName) elseif name == "onOpen" then currentRing.quarantineOnOpen = nil api.updateRingLine() end api.saveRing(currentRingName, currentRing) end function api.setSliceAction() if currentRing and currentSliceIndex then api.setSliceProperty("*") end end function api.setSliceProperty(prop, ...) local slice = assert(currentRing[currentSliceIndex], "Setting a slice property on an unknown slice") if prop == "color" then local r, g, b = ... slice.c = r and ("%02x%02x%02x"):format(r*255, g*255, b*255) or nil elseif prop == "*" then if editorHost:GetAction(slice) then api.updateSliceDisplay(currentSliceIndex, slice) end elseif prop == "skipSpecs" or prop == "show" then local ss = sliceDetail.skipSpecs:GetValue() local sh = sliceDetail.showConditional:GetText() slice.show = (ss or sh ~= "") and ((ss and ("[spec:" .. ss .. "] hide;") or "") .. sh) or nil elseif prop == "rotationMode" then if ... == "default" then slice.embed, slice.rotationMode = nil, nil elseif ... == "embed" then slice.embed, slice.rotationMode = true, nil else slice.rotationMode, slice.embed = ..., false end else slice[prop] = (...) end api.saveRing(currentRingName, currentRing) if prop == "icon" or prop == "color" then local _, _, ico, icoext = getSliceInfo(currentRing[currentSliceIndex]) if prop ~= "color" then sliceDetail.icon:SetIcon(ico, slice.icon, icoext) end sliceDetail.color:SetColor(getSliceColor(slice, ico)) end api.updateRingLine() end function api.updateSliceOptions(slice) local extraY, isCollection = 0, securecall(isCollectionSlice, slice) local fc, cd = sliceDetail.fastClick, sliceDetail.collectionDrop fc:SetChecked(not not slice.fastClick) if PC:GetOption("CenterAction", currentRingName) or PC:GetOption("MotionAction", currentRingName) then fc:SetEnabled(true) fc.tooltipText = nil fc.Text:SetVertexColor(1, 1, 1) else fc.tooltipText = (L"You must enable the %s option for this ring in OPie options to use quick actions."):format( "|cffffffff" .. L"Quick action at ring center" .. "|r|cff909090 / |r|cffffffff" .. L"Quick action if mouse remains still" .. "|r") fc:SetChecked(false) fc:SetEnabled(false) fc.Text:SetVertexColor(0.6, 0.6, 0.6) end cd:SetShown(isCollection) if isCollection then cd:SetPoint("TOPLEFT", fc, "BOTTOMLEFT", -16, 1) extraY = 34 cd.embed, cd.rotationMode = slice.embed, slice.rotationMode cd:text() end sliceDetail.editorContainer:SetVerticalOffset(extraY) end function api.selectSlice(offset, select) if not select then -- This can trigger save-on-hide logic, which in turn forces a -- (redundant) sliceDetail update. api.hideSliceDetail() newSlice:Hide() api.endSliceRepick() currentSliceIndex = nil ringDetail:Show() api.updateRingLine() return end ringDetail:Hide() newSlice:Hide() api.hideSliceDetail() ringContainer.newSlice:SetChecked(nil) local old, id = ringContainer.slices[(currentSliceIndex or 0) + 1 - sliceBaseIndex], sliceBaseIndex + offset local desc = currentRing[id] if old then old:SetChecked(nil) end currentSliceIndex = nil if not desc then return ringDetail:Show() end api.updateSliceDisplay(id, desc) api.endSliceRepick() sliceDetail:Show() currentSliceIndex = id end function api.updateSliceDisplay(_id, desc) local stype, sname, sicon, icoext = getSliceInfo(desc) if sname ~= "" then sliceDetail.desc:SetFormattedText("%s: |cffffffff%s|r", stype or "?", sname or "?") else sliceDetail.desc:SetText(stype or "?") end local skipSpecs, showConditional = (desc.show or ""):match("^%[spec:([%d/]+)%] hide;(.*)") sliceDetail.icon:HidePanel() sliceDetail.icon:SetIcon(sicon, desc.icon, icoext) sliceDetail.color:SetColor(getSliceColor(desc, sicon)) sliceDetail.skipSpecs:SetValue(skipSpecs) sliceDetail.showConditional:SetText(showConditional or desc.show or "") api.updateSliceOptions(desc) editorHost:SetAction(desc) end function api.moveSlice(source, dest) if not (currentRing and currentRing[source] and currentRing[dest]) then return end table.insert(currentRing, dest, table.remove(currentRing, source)) if currentSliceIndex == source then currentSliceIndex = dest end api.saveRing(currentRingName, currentRing) api.updateRingLine() end function api.deleteSlice(id) if id == nil then id = currentSliceIndex end if id and currentRing and currentRing[id] then if id == currentSliceIndex then api.hideSliceDetail() currentSliceIndex = nil ringDetail:Show() end table.remove(currentRing, id) if sliceBaseIndex == id and sliceBaseIndex > 1 then sliceBaseIndex = sliceBaseIndex - 1 end if id == currentRing.onOpen then currentRing.onOpen = nil elseif currentRing.onOpen and id < currentRing.onOpen then currentRing.onOpen = currentRing.onOpen - 1 end api.saveRing(currentRingName, currentRing) api.updateRingLine(true) end end function api.beginSliceRepick() repickSlice = currentRing[currentSliceIndex] api.hideSliceDetail() ringContainer.disableSliceDrag, newSlice.disableDrag = true, true newSlice:Show() end function api.endSliceRepick() ringContainer.disableSliceDrag, newSlice.disableDrag = nil, nil repickSlice = nil end function api.finishSliceRepick() for i=1,#currentRing do if currentRing[i] == repickSlice then api.endSliceRepick() api.selectSlice(i-sliceBaseIndex, true) api.updateRingLine() return true end end end function api.deleteRing() if currentRing then ringContainer:Hide() config.undo:saveActiveProfile() api.saveRing(currentRingName, false) api.deselectRing() end end function api.deselectRing() ringContainer:Hide() currentRing, currentRingName, ringNames = nil UIDropDownMenu_SetText(ringDropDown, L"Select a ring to modify") end function api.restoreDefault() if currentRingName then local _, _, isDefaultAvailable, isDefaultOverriden = RK:GetRingInfo(currentRingName) if isDefaultAvailable and isDefaultOverriden then SaveRingVersion(currentRingName, true) RK:RestoreDefaults(currentRingName) end api.selectRing(nil, currentRingName) end end function api.addSlice(pos, ...) local wasRepick if pos == nil and repickSlice then local otid = repickSlice[1] for k in pairs(repickSlice) do if type(k) == "number" then repickSlice[k] = nil end end dropKeys(repickSlice, AB:GetActionOptions(otid)) for i=1,select("#", ...),2 do repickSlice[i], repickSlice[i+1] = select(i, ...) end wasRepick = true else pos = math.max(1, math.min(#currentRing+1, pos and (pos + sliceBaseIndex) or (#currentRing+1))) table.insert(currentRing, pos, {sliceToken=AB:CreateToken(), ...}) sliceBaseIndex = math.min(pos, math.max(1 + pos - #ringContainer.slices, sliceBaseIndex)) end api.saveRing(currentRingName, currentRing) api.updateRingLine(true) if wasRepick then api.finishSliceRepick() end end local function resolveCustomSliceAdd(ok, ...) if ok and type((...)) == "string" and AB:GetActionDescription(...) then api.addSlice(nil, ...) return true end return false end function api.addCustomSlice(_editbox, text, attemptAccept) if not attemptAccept then return true end return resolveCustomSliceAdd(pcall(decodeConstantList, text, 1)) end function api.closeActionPicker(source) if source == "add-new-slice-button" and repickSlice then api.endSliceRepick() currentSliceIndex = nil api.updateRingLine() ringContainer.newSlice:SetChecked(true) return elseif repickSlice and api.finishSliceRepick() then return end newSlice:Hide() ringDetail:Show() api.updateRingLine() ringContainer.newSlice:SetChecked(false) end function api.saveRing(name, data) SaveRingVersion(name, true) if data ~= nil then if type(data) == "table" then data.save = true end RK:SetRing(name, data) end ringDetail.exportFrame:Hide() end function api.refreshDisplay() if currentRing and currentRing[currentSliceIndex] then api.updateSliceOptions(currentRing[currentSliceIndex]) end if currentRing then ringDetail.binding:SetBindingText(api.getRingProperty("hotkey")) ringDetail.exportFrame:GetScript("OnHide")(ringDetail.exportFrame) local caOptionNames = "|cffffffff" .. L"Quick action at ring center" .. "|r|cff909090 / |r|cffffffff" .. L"Quick action if mouse remains still" .. "|r" local noCA = not PC:GetOption("CenterAction", currentRingName) and (L"You must enable the %s option for this ring in OPie options to use quick actions."):format(caOptionNames) or nil ringDetail.opportunistCA.tooltipText = noCA ringDetail.opportunistCA:SetEnabled(not noCA) ringDetail.opportunistCA:SetChecked(not noCA and not currentRing.noOpportunisticCA) ringDetail.opportunistCA.Text:SetVertexColor(noCA and 0.6 or 1,noCA and 0.6 or 1,noCA and 0.6 or 1) ringDetail.bindingQuarantine:SetShown(not not currentRing.quarantineBind) ringDetail.bindingQuarantine:SetChecked(nil) ringDetail.firstOnOpen:SetChecked(currentRing.onOpen == 1) ringDetail.firstOnOpen.quarantineMark:SetShown(currentRing.quarantineOnOpen == 1) end end function api.exportRing(includeNestedRings) local input = ringDetail.exportInput ringDetail.export:Hide() ringDetail.exportFrame:Show() input:SetText(RK:GetRingSnapshot(currentRingName, includeNestedRings)) input:SetCursorPosition(0) input:HighlightText() input:SetFocus() end function api.showExternalEditor(which) if which == "slice-binding" then T.ShowSliceBindingPanel(currentRingName) elseif which == "opie-options" then T.ShowOPieOptionsPanel(currentRingName) end end ringDetail:SetScript("OnShow", function() local _,_, isDefaultAvailable, isDefaultOverriden = RK:GetRingInfo(currentRingName) ringDetail.restore:SetText(isDefaultAvailable and L"Restore default" or L"Undo changes") ringDetail.restore:SetShown(isDefaultAvailable and isDefaultOverriden) end) local function resetView() currentRingName, currentRing, currentSliceIndex, ringNames = nil ringContainer:Hide() end function panel:refresh() local oRingName, oBaseIndex, oSliceIndex = currentRingName, sliceBaseIndex, currentSliceIndex local oSliceToken = currentRing and currentRing[currentSliceIndex] and currentRing[currentSliceIndex].sliceToken currentRingName, currentRing, currentSliceIndex, ringNames = nil ringContainer:Hide() ringDetail:Hide() api.hideSliceDetail() newSlice:Hide() if oRingName and RK:GetRingInfo(oRingName) then api.selectRing(nil, oRingName) for i=1, oSliceToken and type(currentRing) == "table" and #currentRing or 0 do local s = currentRing[i] if s and s.sliceToken == oSliceToken then api.selectSlice(i-sliceBaseIndex, true) break end end local cbi = currentSliceIndex and (currentSliceIndex - oSliceIndex + oBaseIndex) or oBaseIndex if currentRing and cbi > 0 and cbi < #currentRing and cbi ~= sliceBaseIndex then sliceBaseIndex = cbi api.updateRingLine() end end if not currentRing then UIDropDownMenu_SetText(ringDropDown, L"Select a ring to modify") end end function panel:default() for key in RK:GetManagedRings() do local _, _, isDefault, isOverriden = RK:GetRingInfo(key) if isDefault and isOverriden then SaveRingVersion(key, true) end end for key in RK:GetDeletedRings() do SaveRingVersion(key, false) end RK:RestoreDefaults() panel:refresh() end function panel:okay() ringContainer:Hide() resetView() end panel.cancel = resetView panel:SetScript("OnShow", config.checkSVState) SLASH_OPIE_CUSTOM_RINGS1 = "/rk" function SlashCmdList.OPIE_CUSTOM_RINGS() panel:OpenPanel() end T.AddSlashSuffix(SlashCmdList.OPIE_CUSTOM_RINGS, "custom", "rings")