--[[ MainPanel.lua @Author : DengSir (tdaddon@163.com) @Link : https://dengsir.github.io ]] local ns = select(2, ...) local Addon = ns.Addon local UI = ns.UI local L = ns.L local Director = ns.Director local GUI = LibStub('tdGUI-1.0') local Class = LibStub('LibClass-2.0') local STATUS_NONE = 0 local STATUS_ADD = 1 local STATUS_EDIT = 2 local STATUS_LABELS = { [STATUS_ADD] = L['Create script'], [STATUS_EDIT] = L['Edit script'], } local Module = Addon:NewModule('UI.MainPanel', 'AceEvent-3.0') function Module:OnInitialize() local function UpdateLayout() self:UpdateLayout() end local function UpdateSaveButton() self:UpdateSaveButton() end local MainPanel = GUI:GetClass('BasicPanel'):New(UIParent) do MainPanel.SetTitle = function(p, t) return p:SetText('tdBattlePetScript - ' .. t) end MainPanel:Hide() MainPanel:SetSize(550, 450) MainPanel:SetPoint('CENTER') MainPanel:SetMovable(true) MainPanel:SetResizable(true) MainPanel:SetMinResize(550, 350) MainPanel:SetMaxResize(900, 700) MainPanel:ShowPortrait() MainPanel:SetFrameStrata('DIALOG') MainPanel:SetTitle(L['Script editor']) MainPanel:SetPortrait(ns.ICON) MainPanel:RegisterConfig(Addon.db.profile.position) MainPanel:SetAttribute('UIPanelLayout-enabled', true) MainPanel:SetAttribute('UIPanelLayout-whileDead', true) MainPanel:SetAttribute('UIPanelLayout-area', 'left') MainPanel:SetAttribute('UIPanelLayout-pushable', 1) end local Inset = CreateFrame('Frame', nil, MainPanel, 'InsetFrameTemplate') do Inset:SetPoint('TOPLEFT', 4, -60) Inset:SetPoint('BOTTOMRIGHT', -6, 26) Inset.Bg:SetDrawLayer('BACKGROUND') end local ScriptList = GUI:GetClass('ListView'):New(Inset) do ScriptList:SetPoint('TOPLEFT', 0, 0) ScriptList:SetPoint('BOTTOMLEFT', 0, -1) ScriptList:SetWidth(200) ScriptList:SetItemHeight(30) ScriptList:SetPadding(5) ScriptList:SetSelectMode('RADIO') ScriptList:SetItemClass(Addon:GetClass('ScriptItem')) ScriptList:SetCallback('OnItemFormatting', function(ScriptList, button, d) if d.type == 'plugin' then button:SetText(d.value:GetPluginTitle()) button:SetTexture(d.value:GetPluginIcon()) button:ShowIcon() button:SetSelectable(false) button:SetType(d.type) elseif d.type == 'script' then button:SetText(d.value:GetName()) button:HideIcon() button:SetSelectable(true) button:SetType(d.type) end end) ScriptList:SetCallback('OnSelectChanged', function(ScriptList, index, d) local script = d.value self:OpenScript(script:GetPlugin(), script:GetKey(), script:GetName()) end) ScriptList:SetCallback('OnItemEnter', function(ScriptList, button, d) if d.type == 'script' then UI.OpenScriptTooltip(d.value, button, 'ANCHOR_RIGHT') else end end) ScriptList:SetCallback('OnItemLeave', function() GameTooltip:Hide() end) ScriptList:SetScript('OnShow', function() self:UpdateLayout() self:UpdateScriptList() end) ScriptList:SetScript('OnHide', UpdateLayout) end local Content = CreateFrame('Frame', nil, Inset) do Content:SetPoint('TOPLEFT', ScriptList, 'TOPRIGHT', 2, 0) Content:SetPoint('BOTTOMRIGHT') end local Banner = CreateFrame('Frame', nil, Content) do Banner:SetPoint('BOTTOMLEFT', Content, 'TOPLEFT') Banner:SetPoint('BOTTOMRIGHT', Content, 'TOPRIGHT', -30, 0) Banner:SetHeight(35) end local ShareButton = CreateFrame('Button', nil, MainPanel) do ShareButton:SetSize(26, 26) ShareButton:SetPoint('LEFT', Banner, 'RIGHT') ShareButton:SetNormalTexture([[Interface\CHATFRAME\UI-ChatIcon-Chat-Up]]) ShareButton:SetPushedTexture([[Interface\CHATFRAME\UI-ChatIcon-Chat-Down]]) ShareButton:SetHighlightTexture([[Interface\CHATFRAME\UI-ChatIcon-BlinkHilight]], 'ADD') ShareButton:SetScript('OnClick', function() self:OnShareButtonClick() end) end local HelpIcon = CreateFrame('Button', nil, Banner) do HelpIcon:SetSize(28, 28) HelpIcon:SetPoint('LEFT', 5, 0) local Border = HelpIcon:CreateTexture(nil, 'ARTWORK') do Border:SetTexture([[Interface\PetBattles\PetBattleHUD]]) Border:SetPoint('TOPLEFT', 0, 1) Border:SetPoint('BOTTOMRIGHT', 1, 0) Border:SetTexCoord(0.884765625, 0.943359375, 0.681640625, 0.798828125) end local Highlight = HelpIcon:CreateTexture(nil, 'HIGHLIGHT') do Highlight:SetPoint('TOPLEFT', -1, 1) Highlight:SetPoint('BOTTOMRIGHT', 2, -2) Highlight:SetBlendMode('ADD') Highlight:SetTexture([[Interface\Minimap\UI-Minimap-ZoomButton-Highlight]]) end local Icon = HelpIcon:CreateTexture(nil, 'BORDER') do Icon:SetMask([[Interface\Minimap\UI-Minimap-Background]]) Icon:SetSize(28, 28) Icon:SetAllPoints(true) end HelpIcon:SetScript('OnEnter', function(HelpIcon) UI.OpenScriptTooltip(self.script, HelpIcon, 'ANCHOR_BOTTOMRIGHT') end) HelpIcon:SetScript('OnLeave', function() GameTooltip:Hide() end) function HelpIcon:SetTexture(texture) return Icon:SetTexture(texture) end end local ExtraButton = CreateFrame('CheckButton', nil, Banner) do ExtraButton:SetSize(26, 26) ExtraButton:SetPoint('RIGHT') ExtraButton:SetNormalTexture([[Interface\CHATFRAME\UI-ChatIcon-ScrollDown-Up]]) ExtraButton:SetPushedTexture([[Interface\CHATFRAME\UI-ChatIcon-ScrollDown-Down]]) ExtraButton:SetHighlightTexture([[Interface\CHATFRAME\UI-ChatIcon-BlinkHilight]], 'ADD') ExtraButton:SetScript('OnClick', function(ExtraButton) if ExtraButton:GetChecked() then ExtraButton:SetNormalTexture([[Interface\CHATFRAME\UI-ChatIcon-ScrollUp-Up]]) ExtraButton:SetPushedTexture([[Interface\CHATFRAME\UI-ChatIcon-ScrollUp-Down]]) PlaySound(SOUNDKIT and SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF or 'igMainMenuOptionCheckBoxOff') else ExtraButton:SetNormalTexture([[Interface\CHATFRAME\UI-ChatIcon-ScrollDown-Up]]) ExtraButton:SetPushedTexture([[Interface\CHATFRAME\UI-ChatIcon-ScrollDown-Down]]) PlaySound(SOUNDKIT and SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON or 'igMainMenuOptionCheckBoxOn') end self.ExtraFrame:SetShown(ExtraButton:GetChecked()) end) ExtraButton:SetScript('OnEnter', function(ExtraButton) GameTooltip:SetOwner(ExtraButton, 'ANCHOR_BOTTOMLEFT') GameTooltip:SetText(L.SCRIPT_EDITOR_LABEL_TOGGLE_EXTRA) GameTooltip:Show() end) ExtraButton:SetScript('OnLeave', function() GameTooltip:Hide() end) end local Name = Banner:CreateFontString(nil, 'ARTWORK', 'GameFontNormalOutline') do Name:SetHeight(30) Name:SetJustifyH('CENTER') Name:SetJustifyV('MIDDLE') Name:SetPoint('LEFT', HelpIcon, 'RIGHT', 2, 0) Name:SetPoint('RIGHT', ExtraButton, 'LEFT', -2, 0) end local SaveButton = CreateFrame('Button', nil, Content, 'UIPanelButtonTemplate') do SaveButton:SetPoint('BOTTOMRIGHT', MainPanel, 'BOTTOMRIGHT', -10, 4) SaveButton:SetSize(80, 22) SaveButton:SetText(SAVE) SaveButton:SetScript('OnClick', function() self:OnSaveButtonClick() end) end local DebugButton = CreateFrame('Button', nil, Content, 'UIPanelButtonTemplate') do DebugButton:SetPoint('RIGHT', SaveButton, 'LEFT') DebugButton:SetSize(80, 22) DebugButton:SetText(L['Run']) DebugButton:SetScript('OnClick', function() self:Run() end) end local DeleteButton = CreateFrame('Button', nil, Content, 'UIPanelButtonTemplate') do DeleteButton:SetPoint('BOTTOMLEFT', MainPanel, 'BOTTOMLEFT', 8, 4) DeleteButton:SetSize(80, 22) DeleteButton:SetText(DELETE) DeleteButton:SetScript('OnClick', function() self:OnDeleteButtonClick() end) end local ExtraFrame = CreateFrame('Frame', nil, Content) do ExtraFrame:Hide() ExtraFrame:SetPoint('TOPLEFT') ExtraFrame:SetPoint('TOPRIGHT') ExtraFrame:SetHeight(40) ExtraFrame:EnableMouse(true) ExtraFrame:SetScript('OnShow', UpdateLayout) ExtraFrame:SetScript('OnHide', UpdateLayout) end local function MakeBox(className, parent, labelText) local class = Class:IsClass(className) and className or GUI:GetClass(className) local box = class:New(parent, true) do local label = box:CreateFontString(nil, 'ARTWORK', 'GameFontNormalSmall') do label:SetPoint('BOTTOMLEFT', box, 'TOPLEFT') label:SetText(labelText) end end return box end local NameBox = MakeBox('InputBox', ExtraFrame, L['Script name']) do NameBox:SetPoint('TOPLEFT', 10, -25) NameBox:SetPoint('TOPRIGHT', -10, -25) NameBox:SetHeight(22) NameBox:SetCallback('OnTextChanged', UpdateSaveButton) end local ScriptEditor = CreateFrame('Frame', nil, Content) do ScriptEditor:SetPoint('TOPLEFT') ScriptEditor:SetPoint('BOTTOMRIGHT') end local ScriptBox = MakeBox(Addon:GetClass('ScriptEditor'), ScriptEditor, L['Script']) do ScriptBox:SetPoint('TOPLEFT', 10, -25) ScriptBox:SetPoint('BOTTOMRIGHT', -10, 10) ScriptBox:SetCallback('OnTextChanged', function(ScriptBox, userInput) ScriptBox:OnTextChanged(userInput) self.BugFrame:Hide() self:UpdateSaveButton() end) end local BugFrame = CreateFrame('Frame', nil, Content, 'BackdropTemplate') do BugFrame:Hide() BugFrame:SetPoint('BOTTOMLEFT', 8, 6) BugFrame:SetPoint('BOTTOMRIGHT', -8, 6) BugFrame:SetHeight(32) BugFrame:SetBackdrop{ edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]], tile = true, edgeSize = 16, } BugFrame:SetBackdropBorderColor(0.5, 0.5, 0.5) local Bg = BugFrame:CreateTexture(nil, 'BACKGROUND') do Bg:SetTexture([[Interface\FrameGeneral\UI-Background-Marble]]) Bg:SetPoint('TOPLEFT', 3, -3) Bg:SetPoint('BOTTOMRIGHT', -3, 3) Bg:SetTexCoord(0, 1, 0, 0.2) end local Icon = BugFrame:CreateTexture(nil, 'ARTWORK') do Icon:SetSize(26, 26) Icon:SetTexture([[Interface\HELPFRAME\HelpIcon-Bug]]) Icon:SetPoint('LEFT', 10, 0) end local Text = BugFrame:CreateFontString(nil, 'ARTWORK', 'GameFontRed') do Text:SetPoint('LEFT', Icon, 'RIGHT', 5, 0) Text:SetPoint('RIGHT', -5, 0) Text:SetJustifyH('LEFT') Text:SetWordWrap(false) end BugFrame.Text = Text BugFrame.Icon = Icon BugFrame:SetScript('OnEnter', function(BugFrame) if not BugFrame.err then return end GameTooltip:SetOwner(BugFrame, 'ANCHOR_TOP') GameTooltip:SetText('Bug') GameTooltip:AddLine(BugFrame.message, 1, 1, 1, true) GameTooltip:AddLine(BugFrame.err, 1, 0, 0, true) GameTooltip:Show() end) BugFrame:SetScript('OnLeave', GameTooltip_Hide) BugFrame:SetScript('OnShow', UpdateLayout) BugFrame:SetScript('OnHide', UpdateLayout) end local EditBoxGroup = GUI:GetClass('EditBoxGroup'):New() do EditBoxGroup:RegisterEditBox(NameBox) EditBoxGroup:RegisterEditBox(ScriptBox.EditBox) end local BlockDialog = GUI:GetClass('BlockDialog'):New(MainPanel) do BlockDialog:SetPoint('TOPLEFT', 3, -22) BlockDialog:SetPoint('BOTTOMRIGHT', -3, 3) BlockDialog:SetFrameLevel(MainPanel:GetFrameLevel() + 100) end self.Banner = Banner self.BlockDialog = BlockDialog self.BugFrame = BugFrame self.CollapseButton = CollapseButton self.Content = Content self.Content = Content self.DebugButton = DebugButton self.DeleteButton = DeleteButton self.EditBoxGroup = EditBoxGroup self.ExtraFrame = ExtraFrame self.HelpIcon = HelpIcon self.MainPanel = MainPanel self.Name = Name self.NameBox = NameBox self.SaveButton = SaveButton self.ScriptBox = ScriptBox self.ScriptEditor = ScriptEditor self.ScriptList = ScriptList self.ShareButton = ShareButton self:Bind('UpdateLayout', MainPanel) self:Bind('UpdateStatus', Content) self:Bind('UpdateSaveButton', SaveButton) end function Module:OnEnable() self:OnFontChanged() self:RegisterEvent('PET_BATTLE_ACTION_SELECTED') self:RegisterEvent('PET_BATTLE_OPENING_START', 'UpdateDebugButton') self:RegisterEvent('PET_BATTLE_CLOSE', 'UpdateDebugButton') self:RegisterEvent('PET_BATTLE_PET_ROUND_PLAYBACK_COMPLETE', 'UpdateDebugButton') self:RegisterMessage('PET_BATTLE_SCRIPT_SCRIPT_LIST_UPDATE', 'UpdateScriptList') self:RegisterMessage('PET_BATTLE_SCRIPT_RESET_FRAMES') self:RegisterMessage('PET_BATTLE_SCRIPT_SETTING_CHANGED_editorFontFace', 'OnFontChanged') self:RegisterMessage('PET_BATTLE_SCRIPT_SETTING_CHANGED_editorFontSize', 'OnFontChanged') end function Module:OnFontChanged() self.ScriptBox:SetFont(Addon:GetSetting('editorFontFace'), Addon:GetSetting('editorFontSize')) end function Module:PET_BATTLE_ACTION_SELECTED() self.DebugButton:Disable() end function Module:PET_BATTLE_SCRIPT_RESET_FRAMES() self.MainPanel:RestorePosition() self.MainPanel:RestoreSize() end function Module:UpdateScriptList() if not self.ScriptList:IsShown() then return end local list = {} do for _, plugin in Addon:IteratePlugins() do tinsert(list, { type = 'plugin', value = plugin }) local scripts = {} do for _, script in plugin:IterateScripts() do tinsert(scripts, script) end sort(scripts, function(a, b) return a:GetName() < b:GetName() end) end for _, script in ipairs(scripts) do tinsert(list, { type = 'script', value = script }) end end end self.ScriptList:SetItemList(list) self.ScriptList:Refresh() end function Module:Message(flag, message, err) self.BugFrame.Icon:SetTexture(flag and [[Interface\DialogFrame\UI-Dialog-Icon-AlertOther]] or [[Interface\DialogFrame\UI-Dialog-Icon-AlertNew]]) self.BugFrame.Text:SetFontObject(flag and 'GameFontNormalSmall' or 'GameFontRedSmall') self.BugFrame.Text:SetText(err and message .. ': ' .. err or message) self.BugFrame:Show() self.BugFrame.err = err self.BugFrame.message = message end function Module:Clear() self.isDialog = false self.script = nil self.plugin = nil self.key = nil self.status = STATUS_NONE end function Module:OpenScript(plugin, key, defaultName) local script = plugin:GetScript(key) if script then self.script = script self.status = STATUS_EDIT else self.script = Addon:GetClass('Script'):New({}, plugin, key) self.status = STATUS_ADD end self.plugin = plugin self.key = key self.defaultName = self.script:GetName() or defaultName self:UpdateLayout() self:UpdateSaveButton() self:UpdateStatus() self:UpdateScript() self.Content:Show() end function Module:OpenScriptDialog(...) self:OpenScript(...) self:ShowDialog() end function Module:ShowDialog() self.isDialog = true self.ScriptList:Hide() self.MainPanel:RestorePosition() self.MainPanel:RestoreSize() self.MainPanel:SetMovable(true) self.MainPanel:SetResizable(true) self.MainPanel:SetFrameStrata('DIALOG') self.MainPanel:SetTitle(L['Script editor']) if self.MainPanel:IsShown() then self:HidePanel() end self.MainPanel:Show() end function Module:ShowPanel() self:Clear() self.ScriptList:Show() self.Content:Hide() self.MainPanel:SetSize(550, 450) self.MainPanel:SetMovable(false) self.MainPanel:SetResizable(false) self.MainPanel:SetFrameStrata('MEDIUM') self.MainPanel:SetTitle(L['Script manager']) if self.MainPanel:IsShown() then self:HidePanel() end self.MainPanel:SetAttribute('UIPanelLayout-defined', true) ShowUIPanel(self.MainPanel) end function Module:TogglePanel() if self.MainPanel:IsShown() then self:HidePanel() else self:ShowPanel() end end function Module:HidePanel() HideUIPanel(self.MainPanel) end function Module:UpdateLayout() if not self.MainPanel:IsVisible() then return end if self.ScriptList:IsShown() then self.Content:SetPoint('TOPLEFT', self.ScriptList, 'TOPRIGHT', 2, 0) self.MainPanel:SetMinResize(550, 350) self.MainPanel:SetMaxResize(900, 700) self.MainPanel:ShowPortrait() else self.Content:SetPoint('TOPLEFT') self.MainPanel:SetMinResize(350, 350) self.MainPanel:SetMaxResize(700, 700) self.MainPanel:HidePortrait() end if self.ExtraFrame:IsShown() then self.ScriptEditor:SetPoint('TOPLEFT', self.ExtraFrame, 'BOTTOMLEFT') else self.ScriptEditor:SetPoint('TOPLEFT') end if self.BugFrame:IsShown() then self.ScriptEditor:SetPoint('BOTTOMRIGHT', 0, 30) else self.ScriptEditor:SetPoint('BOTTOMRIGHT') end end function Module:UpdateScript() self.BugFrame:Hide() self.BlockDialog:Hide() self.HelpIcon:SetTexture(self.plugin:GetPluginIcon()) self.NameBox:SetText(self.defaultName) self.ScriptBox:SetText(self.script:GetCode() or '') end function Module:UpdateDebugButton() self.DebugButton:SetShown(C_PetBattles.IsInBattle()) self.DebugButton:SetEnabled(C_PetBattles.IsSkipAvailable() or C_PetBattles.ShouldShowPetSelect()) end function Module:UpdateSaveButton() self.SaveButton:SetEnabled(self:IsCanSave()) end function Module:UpdateStatus() self.Name:SetFormattedText('%s: |cff00ff00%s|r-|cffffffff%s|r', STATUS_LABELS[self.status], self.plugin:GetPluginTitle(), self.defaultName) self.DeleteButton:SetShown(self.status == STATUS_EDIT) self.DebugButton:SetShown(C_PetBattles.IsInBattle()) end function Module:IsCanSave() if not self:GetEditBoxText(self.ScriptBox) then return false end return self.script:GetCode() ~= self:GetEditBoxText(self.ScriptBox) or self.script:GetName() ~= self:GetEditBoxText(self.NameBox) end function Module:GetEditBoxText(editBox) local text = editBox:GetText():trim() return text ~= '' and text or nil end function Module:OnSaveButtonClick() local ok, err = self.script:SetCode(self.ScriptBox:GetText():trim()) if ok and not self.status ~= STATUS_EDIT then self.status = STATUS_EDIT self.plugin:AddScript(self.key, self.script) end self:Message(ok, ok and L['Save success'] or L['Found error'], err) if not ok then return end self.script:SetName(self:GetEditBoxText(self.NameBox)) self.EditBoxGroup:ClearFocus() self:UpdateSaveButton() self:UpdateStatus() end function Module:OnBeautyButtonClick() if not self.script:GetCode() or self:IsCanSave() then return end local code = Director:BeautyScript(self.script:GetScript()) self.ScriptBox:SetText(code) end function Module:OnDeleteButtonClick() self.EditBoxGroup:ClearFocus() self.BlockDialog:Open{ text = format(L.SCRIPT_EDITOR_DELETE_SCRIPT, self.plugin:GetPluginTitle(), self.script:GetName()), delay = Addon:GetSetting('noWaitDeleteScript') and 0 or 3, ctx = self.script, OnAccept = function(script) script:GetPlugin():RemoveScript(script:GetKey()) if self.isDialog then self:HidePanel() else self:ShowPanel() end end, } end function Module:Run() local script, err = Director:BuildScript(self:GetEditBoxText(self.ScriptBox)) if not script then self:Message(false, L['Found error'], err) else Director:Debug(script) end end function Module:OnShareButtonClick() GUI:ToggleMenu(self.ShareButton, { { text = L['Beauty script'], disabled = not self.script or not self.script:GetCode() or self:IsCanSave(), func = function() self:OnBeautyButtonClick() end }, { text = L['Import'], func = function() UI.Import.Frame:Show() self:HidePanel() end }, { text = L['Export'], disabled = self.status ~= STATUS_EDIT, func = function() self.BlockDialog:Open{ text = L['Export'], cancelHidden = true, acceptText = OKAY, editBox = true, editText = Addon:Export(self.script), editFocus = true, editHighlightAll = true, editFocusLost = 'ACCEPT', } end }, { text = L['Options'], func = function() Addon:OpenOptionFrame() end } }) end function Module:Bind(method, obj) GUI:Embed(obj, 'Refresh') local fn = self[method] obj.Update = function(...) return fn(self, ...) end self[method] = function() return obj:Refresh() end end