diff --git a/StdUi.lua b/StdUi.lua index e2fedb0..ef852ac 100644 --- a/StdUi.lua +++ b/StdUi.lua @@ -1,19 +1,24 @@ -local MAJOR, MINOR = 'StdUi', 2; +local MAJOR, MINOR = 'StdUi', 3; --- @class StdUi local StdUi = LibStub:NewLibrary(MAJOR, MINOR); if not StdUi then - return ; + return end -StdUi.moduleVersions = {}; +local TableInsert = tinsert; -StdUiInstances = {StdUi}; +StdUi.moduleVersions = {}; +if not StdUiInstances then + StdUiInstances = {StdUi}; +else + TableInsert(StdUiInstances, StdUi); +end function StdUi:NewInstance() local instance = CopyTable(self); instance:ResetConfig(); - tinsert(StdUiInstances, instance); + TableInsert(StdUiInstances, instance); return instance; end @@ -34,6 +39,7 @@ function StdUi:RegisterWidget(name, func) self[name] = func; return true; end + return false; end @@ -46,7 +52,7 @@ function StdUi:InitWidget(widget) for i = 1, #children do local child = children[i]; if child.isWidget then - tinsert(result, child); + TableInsert(result, child); end end @@ -78,7 +84,7 @@ StdUi.SetHighlightBorder = function(self) end if self.isDisabled then - return; + return end local hc = StdUi.config.highlight.color; @@ -94,11 +100,13 @@ StdUi.ResetHighlightBorder = function(self) end if self.isDisabled then - return; + return end local hc = self.origBackdropBorderColor; - self:SetBackdropBorderColor(unpack(hc)); + if hc then + self:SetBackdropBorderColor(unpack(hc)); + end end function StdUi:HookHoverBorder(object) @@ -148,6 +156,7 @@ function StdUi:ApplyDisabledBackdrop(frame, enabled) if frame.target then frame = frame.target; end + if enabled then self:ApplyBackdrop(frame, 'button', 'border'); self:SetTextColor(frame, 'normal'); @@ -205,9 +214,11 @@ function StdUi:MakeDraggable(frame, handle) handle:EnableMouse(true); handle:SetMovable(true); handle:RegisterForDrag('LeftButton'); + handle:SetScript('OnDragStart', function(self) frame.StartMoving(frame); end); + handle:SetScript('OnDragStop', function(self) frame.StopMovingOrSizing(frame); end); diff --git a/StdUiBuilder.lua b/StdUiBuilder.lua index de65a00..23d02a3 100644 --- a/StdUiBuilder.lua +++ b/StdUiBuilder.lua @@ -1,59 +1,15 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return ; + return end -local module, version = 'Builder', 5; +local module, version = 'Builder', 6; if not StdUi:UpgradeNeeded(module, version) then return -end ; - -local function __genOrderedIndex(t) - local orderedIndex = {}; - - for key in pairs(t) do - tinsert(orderedIndex, key) - end - - table.sort(orderedIndex, function(a, b) - if not t[a].order or not t[b].order then - return a < b; - end - return t[a].order < t[b].order; - end); - - return orderedIndex; end -local function orderedNext(t, state) - local key; - - if state == nil then - -- the first time, generate the index - t.__orderedIndex = __genOrderedIndex(t); - key = t.__orderedIndex[1]; - else - -- fetch the next value - for i = 1, table.getn(t.__orderedIndex) do - if t.__orderedIndex[i] == state then - key = t.__orderedIndex[i + 1]; - end - end - end - - if key then - return key, t[key]; - end - - -- no more value to return, cleanup - t.__orderedIndex = nil; - return -end - -local function orderedPairs(t) - return orderedNext, t, nil; -end +local util = StdUi.Util; local function setDatabaseValue(db, key, value) if key:find('.') then @@ -101,16 +57,18 @@ function StdUi:BuildElement(frame, row, info, dataKey, db) local genericChangeEvent = function(el, value) setDatabaseValue(el.dbReference, el.dataKey, value); - if info.onChange then - info.onChange(el, value); + if el.onChange then + el:onChange(value); end end local hasLabel = false; if info.type == 'checkbox' then element = self:Checkbox(frame, info.label); - elseif info.type == 'text' or info.type == 'editBox' then + elseif info.type == 'editBox' then element = self:EditBox(frame, nil, 20); + elseif info.type == 'multiLineBox' then + element = self:MultiLineBox(frame, 300, 20); elseif info.type == 'dropdown' then element = self:Dropdown(frame, 300, 20, info.options or {}, nil, info.multi or nil, info.assoc or false); elseif info.type == 'autocomplete' then @@ -131,12 +89,14 @@ function StdUi:BuildElement(frame, row, info, dataKey, db) if info.items then element:SetItems(info.items); end - elseif info.type == 'sliderWithBox' then + elseif info.type == 'slider' or info.type == 'sliderWithBox' then element = self:SliderWithBox(frame, nil, 32, 0, info.min or 0, info.max or 2); if info.precision then element:SetPrecision(info.precision); end + elseif info.type == 'color' then + element = self:ColorInput(frame, info.label, 100, 20, info.color); elseif info.type == 'button' then element = self:Button(frame, nil, 20, info.text or ''); @@ -144,47 +104,119 @@ function StdUi:BuildElement(frame, row, info, dataKey, db) element:SetScript('OnClick', info.onClick); end elseif info.type == 'header' then - element = StdUi:Header(frame, info.label); + element = self:Header(frame, info.label); + elseif info.type == 'label' then + element = self:Label(frame, info.label); + elseif info.type == 'texture' then + element = self:Texture(frame, info.width or 24, info.height or 24, info.texture); + elseif info.type == 'panel' then -- Containers + element = self:Panel(frame, 300, 20); + elseif info.type == 'scroll' then + element = self:ScrollFrame( + frame, + 300, + 20, + type(info.scrollChild) == 'table' and info.scrollChild or nil + ); + if type(info.scrollChild) == 'function' then + info.scrollChild(element); + end + elseif info.type == 'fauxScroll' then + element = self:FauxScrollFrame( + frame, + 300, + 20, + info.displayCount or 5, + info.lineHeight or 22, + type(info.scrollChild) == 'table' and info.scrollChild or nil + ); + if type(info.scrollChild) == 'function' then + info.scrollChild(element); + end + elseif info.type == 'tab' then + element = self:TabPanel( + frame, + 300, + 20, + info.tabs or {}, + info.vertical or false, + info.buttonWidth, + info.buttonHeight + ); elseif info.type == 'custom' then element = info.createFunction(frame, row, info, dataKey, db); end + if not element then + print('Could not build element with type: ', info.type); + end + + -- Widgets can have initialization code + if info.init then + info.init(element); + end + element.dbReference = db; element.dataKey = dataKey; + if info.onChange then + element.onChange = info.onChange; + end if element.hasLabel then hasLabel = true; end - local canHaveLabel = info.type ~= 'checkbox' and info.type ~= 'header'; + local canHaveLabel = info.type ~= 'checkbox' and + info.type ~= 'header' and + info.type ~= 'label' and + info.type ~= 'color'; + if info.label and canHaveLabel then self:AddLabel(frame, element, info.label); hasLabel = true; end - if info.initialValue and element.SetValue then - element:SetValue(info.initialValue); - end - - if info.initialValue and element.SetChecked then - element:SetChecked(info.initialValue); + if info.initialValue then + if element.SetChecked then + element:SetChecked(info.initialValue); + elseif element.SetColor then + element:SetColor(info.initialValue); + elseif element.SetValue then + element:SetValue(info.initialValue); + end end -- Setting onValueChanged disqualifies from any writes to database if info.onValueChanged then element.OnValueChanged = info.onValueChanged; elseif db then + local iVal = getDatabaseValue(db, dataKey); + if info.type == 'checkbox' then - element:SetChecked(getDatabaseValue(db, dataKey)) + element:SetChecked(iVal) + elseif element.SetColor then + element:SetColor(iVal); elseif element.SetValue then - element:SetValue(getDatabaseValue(db, dataKey)); + element:SetValue(iVal); end element.OnValueChanged = genericChangeEvent; end + -- Technically, every frame can be a container + if info.children then + self:BuildWindow(element, info.children); + self:EasyLayout(element, { padding = { top = 10 } }); + + element:SetScript('OnShow', function(of) + of:DoLayout(); + end); + end + row:AddElement(element, { column = info.column or 12, + fullSize = info.fullSize or false, + fullHeight = info.fullHeight or false, margin = info.layoutMargins or { top = (hasLabel and 20 or 0) } @@ -200,7 +232,7 @@ end function StdUi:BuildRow(frame, info, db) local row = frame:AddRow(); - for key, element in orderedPairs(info) do + for key, element in util.orderedPairs(info) do local dataKey = element.key or key or nil; local el = self:BuildElement(frame, row, element, dataKey, db); @@ -225,7 +257,7 @@ function StdUi:BuildWindow(frame, info) self:EasyLayout(frame, info.layoutConfig); - for i, row in orderedPairs(rows) do + for _, row in util.orderedPairs(rows) do self:BuildRow(frame, row, db); end diff --git a/StdUiConfig.lua b/StdUiConfig.lua index feb0de3..f5cc952 100644 --- a/StdUiConfig.lua +++ b/StdUiConfig.lua @@ -1,11 +1,15 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Config', 3; -if not StdUi:UpgradeNeeded(module, version) then return end; +local module, version = 'Config', 4; +if not StdUi:UpgradeNeeded(module, version) then + return +end + +local IsAddOnLoaded = IsAddOnLoaded; StdUi.config = {}; @@ -14,24 +18,25 @@ function StdUi:ResetConfig() local _, largeFontSize = GameFontNormalLarge:GetFont(); self.config = { - font = { - family = font, - size = fontSize, - titleSize = largeFontSize, - effect = 'NONE', - strata = 'OVERLAY', - color = { + font = { + family = font, + size = fontSize, + titleSize = largeFontSize, + effect = 'NONE', + strata = 'OVERLAY', + color = { normal = { r = 1, g = 1, b = 1, a = 1 }, disabled = { r = 0.55, g = 0.55, b = 0.55, a = 1 }, header = { r = 1, g = 0.9, b = 0, a = 1 }, } }, - backdrop = { + backdrop = { texture = [[Interface\Buttons\WHITE8X8]], panel = { r = 0.0588, g = 0.0588, b = 0, a = 0.8 }, slider = { r = 0.15, g = 0.15, b = 0.15, a = 1 }, + highlight = { r = 0.40, g = 0.40, b = 0, a = 0.5 }, button = { r = 0.20, g = 0.20, b = 0.20, a = 1 }, buttonDisabled = { r = 0.15, g = 0.15, b = 0.15, a = 1 }, @@ -43,12 +48,12 @@ function StdUi:ResetConfig() color = { r = 1, g = 0.9, b = 0, a = 0.5 }, }, - highlight = { + highlight = { color = { r = 1, g = 0.9, b = 0, a = 0.4 }, blank = { r = 0, g = 0, b = 0, a = 0 } }, - dialog = { + dialog = { width = 400, height = 100, button = { @@ -58,14 +63,14 @@ function StdUi:ResetConfig() } }, - tooltip = { + tooltip = { padding = 10 } }; if IsAddOnLoaded('ElvUI') then local eb = ElvUI[1].media.backdropfadecolor; - self.config.backdrop.panel = { r = eb[1],g = eb[2],b = eb[3],a = eb[4] }; + self.config.backdrop.panel = { r = eb[1], g = eb[2], b = eb[3], a = eb[4] }; end end StdUi:ResetConfig(); diff --git a/StdUiGrid.lua b/StdUiGrid.lua index 8eb0561..a16ce58 100644 --- a/StdUiGrid.lua +++ b/StdUiGrid.lua @@ -1,10 +1,10 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Grid', 2; +local module, version = 'Grid', 4; if not StdUi:UpgradeNeeded(module, version) then return end; --- Creates frame list that reuses frames and is based on array data @@ -15,7 +15,8 @@ if not StdUi:UpgradeNeeded(module, version) then return end; --- @param padding number --- @param oX number --- @param oY number -function StdUi:ObjectList(parent, itemsTable, create, update, data, padding, oX, oY) +--- @param limitFn function +function StdUi:ObjectList(parent, itemsTable, create, update, data, padding, oX, oY, limitFn) local this = self; oX = oX or 1; oY = oY or -1; @@ -58,6 +59,11 @@ function StdUi:ObjectList(parent, itemsTable, create, update, data, padding, oX, this:GlueBelow(itemFrame, itemsTable[i - 1], 0, -padding); totalHeight = totalHeight + padding; end + + if limitFn and limitFn(i, totalHeight, itemFrame:GetHeight()) then + break; + end + i = i + 1; end @@ -69,8 +75,8 @@ end --- @param create function --- @param update function --- @param data table ---- @param size number - size of each wi ---- @param padding number +--- @param paddingX number +--- @param paddingY number --- @param oX number --- @param oY number function StdUi:ObjectGrid(parent, itemsMatrix, create, update, data, paddingX, paddingY, oX, oY) diff --git a/StdUiLayout.lua b/StdUiLayout.lua index a6987fe..3ff8cc0 100644 --- a/StdUiLayout.lua +++ b/StdUiLayout.lua @@ -2,76 +2,87 @@ local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Layout', 2; -if not StdUi:UpgradeNeeded(module, version) then return end; +local module, version = 'Layout', 3; +if not StdUi:UpgradeNeeded(module, version) then + return +end + +local TableInsert = tinsert; +local TableRemove = tremove; +local pairs = pairs; +local MathMax = math.max; +local MathFloor = math.floor; local defaultLayoutConfig = { - gutter = 10, + gutter = 10, columns = 12, padding = { - top = 0, - right = 10, - left = 10 + top = 0, + right = 10, + left = 10, + bottom = 10, } }; local defaultRowConfig = { margin = { - top = 0, - right = 0, + top = 0, + right = 0, bottom = 15, - left = 0 + left = 0 } }; local defaultElementConfig = { margin = { - top = 0, - right = 0, + top = 0, + right = 0, bottom = 0, - left = 0 + left = 0 } }; - ----EasyLayoutRow ----@param parent Frame ----@param config table -function StdUi:EasyLayoutRow(parent, config) - ---@class EasyLayoutRow - local row = { - parent = parent, - config = self.Util.tableMerge(defaultRowConfig, config or {}), - elements = {} - }; - - function row:AddElement(frame, config) +local EasyLayoutRow = { + AddElement = function(self, frame, config) if not frame.layoutConfig then - frame.layoutConfig = StdUi.Util.tableMerge(defaultElementConfig , config or {}); + frame.layoutConfig = StdUi.Util.tableMerge(defaultElementConfig, config or {}); elseif config then - frame.layoutConfig = StdUi.Util.tableMerge(frame.layoutConfig , config or {}); + frame.layoutConfig = StdUi.Util.tableMerge(frame.layoutConfig, config or {}); end - tinsert(row.elements, frame); - end + TableInsert(self.elements, frame); + end, - function row:AddElements(...) - local r = {...}; - local cfg = tremove(r, #r); + AddElements = function(self, ...) + local r = { ... }; + local cfg = TableRemove(r, #r); if cfg.column == 'even' then - cfg.column = math.floor(self.parent.layout.columns / #r); + cfg.column = MathFloor(self.parent.layout.columns / #r); end for i = 1, #r do self:AddElement(r[i], StdUi.Util.tableMerge(defaultElementConfig, cfg)); end - end + end, - function row:DrawRow(parentWidth, yOffset) + GetColumnsTaken = function(self) + local columnsTaken = 0; + local l = self.parent.layout; + + for i = 1, #self.elements do + local lc = self.elements[i].layoutConfig; + local col = lc.column or l.columns; + columnsTaken = columnsTaken + col; + end + + return columnsTaken; + end, + + DrawRow = function(self, parentWidth, yOffset) yOffset = yOffset or 0; local l = self.parent.layout; local g = l.gutter; @@ -89,9 +100,29 @@ function StdUi:EasyLayoutRow(parent, config) frame:ClearAllPoints(); + -- Frame layout config local lc = frame.layoutConfig; local m = lc.margin; + -- take full size + if lc.fullSize then + StdUi:GlueAcross( + frame, + self.parent, + l.padding.left, + -l.padding.top, + -l.padding.right, + l.padding.bottom + ); + + if frame.DoLayout then + frame:DoLayout(); + end + + totalHeight = MathMax(totalHeight, frame:GetHeight() + m.bottom + m.top + rowMargin.top + rowMargin.bottom); + return totalHeight; + end + local col = lc.column or l.columns; local w = (parentWidth / (l.columns / col)) - 2 * g; @@ -105,57 +136,76 @@ function StdUi:EasyLayoutRow(parent, config) -- move it down by rowMargin and element margin frame:SetPoint('TOPLEFT', self.parent, 'TOPLEFT', x, yOffset - m.top - rowMargin.top); + if lc.fullHeight then + frame:SetPoint('BOTTOMLEFT', self.parent, 'BOTTOMLEFT', x, m.bottom + rowMargin.bottom); + end + --each element takes 1 gutter plus column * colWidth, while gutter is inclusive x = x + w + 2 * g; -- double the gutter because width subtracts gutter - totalHeight = math.max(totalHeight, frame:GetHeight() + m.bottom + m.top + rowMargin.top + rowMargin.bottom); + -- if that frame is container itself, do layout for it too + if frame.DoLayout then + frame:DoLayout(); + end + + totalHeight = MathMax(totalHeight, frame:GetHeight() + m.bottom + m.top + rowMargin.top + rowMargin.bottom); columnsTaken = columnsTaken + col; end return totalHeight; end +} - function row:GetColumnsTaken() - local columnsTaken = 0; - local l = self.parent.layout; - - for i = 1, #self.elements do - local lc = self.elements[i].layoutConfig; - local col = lc.column or l.columns; - columnsTaken = columnsTaken + col; - end +---EasyLayoutRow +---@param parent Frame +---@param config table +function StdUi:EasyLayoutRow(parent, config) + ---@class EasyLayoutRow + local row = { + parent = parent, + config = self.Util.tableMerge(defaultRowConfig, config or {}), + elements = {} + }; - return columnsTaken; + for k, v in pairs(EasyLayoutRow) do + row[k] = v; end return row; end -function StdUi:EasyLayout(parent, config) - local stdUi = self; - - parent.layout = self.Util.tableMerge(defaultLayoutConfig, config or {}); - +local EasyLayout = { ---@return EasyLayoutRow - function parent:AddRow(config) - if not self.rows then self.rows = {}; end + AddRow = function(self, config) + if not self.rows then + self.rows = {}; + end - local row = stdUi:EasyLayoutRow(self, config); - tinsert(self.rows, row); + local row = self.stdUi:EasyLayoutRow(self, config); + TableInsert(self.rows, row); return row; - end + end, - function parent:DoLayout() + DoLayout = function(self) local l = self.layout; local width = self:GetWidth() - l.padding.left - l.padding.right; local y = -l.padding.top; for i = 1, #self.rows do - local r = self.rows[i]; - y = y - r:DrawRow(width, y); + local row = self.rows[i]; + y = y - row:DrawRow(width, y); end end +}; + +function StdUi:EasyLayout(parent, config) + parent.stdUi = self; + parent.layout = self.Util.tableMerge(defaultLayoutConfig, config or {}); + + for k, v in pairs(EasyLayout) do + parent[k] = v; + end end StdUi:RegisterModule(module, version); \ No newline at end of file diff --git a/StdUiPosition.lua b/StdUiPosition.lua index 396be7d..83401e1 100644 --- a/StdUiPosition.lua +++ b/StdUiPosition.lua @@ -1,13 +1,15 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Position', 1; +local module, version = 'Position', 2; if not StdUi:UpgradeNeeded(module, version) then return end; -- Points +local Center = 'CENTER'; + local Top = 'TOP'; local Bottom = 'BOTTOM'; local Left = 'LEFT'; @@ -18,6 +20,20 @@ local TopRight = 'TOPRIGHT'; local BottomLeft = 'BOTTOMLEFT'; local BottomRight = 'BOTTOMRIGHT'; +StdUi.Anchors = { + Center = Center, + + Top = Top, + Bottom = Bottom, + Left = Left, + Right = Right, + + TopLeft = TopLeft, + TopRight = TopRight, + BottomLeft = BottomLeft, + BottomRight = BottomRight, +} + --- Glues object below referenced object function StdUi:GlueBelow(object, referencedObject, x, y, align) if align == Left then diff --git a/StdUiUtil.lua b/StdUiUtil.lua index 533eed8..81aecce 100644 --- a/StdUiUtil.lua +++ b/StdUiUtil.lua @@ -1,17 +1,23 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Util', 5; -if not StdUi:UpgradeNeeded(module, version) then return end; +local module, version = 'Util', 6; +if not StdUi:UpgradeNeeded(module, version) then + return +end + +local TableGetN = table.getn; +local TableInsert = tinsert; +local TableSort = table.sort; --- @param frame Frame function StdUi:MarkAsValid(frame, valid) if not valid then frame:SetBackdropBorderColor(1, 0, 0, 1); - frame.origBackdropBorderColor = {frame:GetBackdropBorderColor()}; + frame.origBackdropBorderColor = { frame:GetBackdropBorderColor() }; else frame:SetBackdropBorderColor( self.config.backdrop.border.r, @@ -19,199 +25,249 @@ function StdUi:MarkAsValid(frame, valid) self.config.backdrop.border.b, self.config.backdrop.border.a ); - frame.origBackdropBorderColor = {frame:GetBackdropBorderColor()}; + frame.origBackdropBorderColor = { frame:GetBackdropBorderColor() }; end end -StdUi.Util = {}; +StdUi.Util = { + --- @param self EditBox + editBoxValidator = function(self) + self.value = self:GetText(); ---- @param self EditBox -StdUi.Util.editBoxValidator = function(self) - self.value = self:GetText(); + StdUi:MarkAsValid(self, true); + return true; + end, - StdUi:MarkAsValid(self, true); - return true; -end - ---- @param self EditBox -StdUi.Util.moneyBoxValidator = function(self) - local text = self:GetText(); - text = text:trim(); - local total, gold, silver, copper, isValid = StdUi.Util.parseMoney(text); + --- @param self EditBox + moneyBoxValidator = function(self) + local text = self:GetText(); + text = text:trim(); + local total, gold, silver, copper, isValid = StdUi.Util.parseMoney(text); - if not isValid or total == 0 then - StdUi:MarkAsValid(self, false); - return false; - end + if not isValid or total == 0 then + StdUi:MarkAsValid(self, false); + return false; + end - self:SetText(StdUi.Util.formatMoney(total)); - self.value = total; + self:SetText(StdUi.Util.formatMoney(total)); + self.value = total; - StdUi:MarkAsValid(self, true); - return true; -end + StdUi:MarkAsValid(self, true); + return true; + end, ---- @param self EditBox -StdUi.Util.numericBoxValidator = function(self) - local text = self:GetText(); - text = text:trim(); + --- @param self EditBox + numericBoxValidator = function(self) + local text = self:GetText(); + text = text:trim(); - local value = tonumber(text); + local value = tonumber(text); - if value == nil then - StdUi:MarkAsValid(self, false); - return false; - end + if value == nil then + StdUi:MarkAsValid(self, false); + return false; + end - if self.maxValue and self.maxValue < value then - StdUi:MarkAsValid(self, false); - return false; - end + if self.maxValue and self.maxValue < value then + StdUi:MarkAsValid(self, false); + return false; + end - if self.minValue and self.minValue > value then - StdUi:MarkAsValid(self, false); - return false; - end + if self.minValue and self.minValue > value then + StdUi:MarkAsValid(self, false); + return false; + end - self.value = value; + self.value = value; - StdUi:MarkAsValid(self, true); + StdUi:MarkAsValid(self, true); - return true; -end + return true; + end, -StdUi.Util.spellValidator = function(self) - local text = self:GetText(); - text = text:trim(); - local name, _, icon, _, _, _, spellId = GetSpellInfo(text); + --- @param self EditBox + spellValidator = function(self) + local text = self:GetText(); + text = text:trim(); + local name, _, icon, _, _, _, spellId = GetSpellInfo(text); - if not name then - StdUi:MarkAsValid(self, false); - return false; - end + if not name then + StdUi:MarkAsValid(self, false); + return false; + end - self:SetText(name); - self.value = spellId; - self.icon:SetTexture(icon); + self:SetText(name); + self.value = spellId; + self.icon:SetTexture(icon); + + StdUi:MarkAsValid(self, true); + return true; + end, + + parseMoney = function(text) + text = StdUi.Util.stripColors(text); + local total = 0; + local cFound, _, copper = string.find(text, '(%d+)c$'); + if cFound then + text = string.gsub(text, '(%d+)c$', ''); + text = text:trim(); + total = tonumber(copper); + end - StdUi:MarkAsValid(self, true); - return true; -end + local sFound, _, silver = string.find(text, '(%d+)s$'); + if sFound then + text = string.gsub(text, '(%d+)s$', ''); + text = text:trim(); + total = total + tonumber(silver) * 100; + end -StdUi.Util.parseMoney = function(text) - text = StdUi.Util.stripColors(text); - local total = 0; - local cFound, _, copper = string.find(text, '(%d+)c$'); - if cFound then - text = string.gsub(text, '(%d+)c$', ''); - text = text:trim(); - total = tonumber(copper); - end + local gFound, _, gold = string.find(text, '(%d+)g$'); + if gFound then + text = string.gsub(text, '(%d+)g$', ''); + text = text:trim(); + total = total + tonumber(gold) * 100 * 100; + end - local sFound, _, silver = string.find(text, '(%d+)s$'); - if sFound then - text = string.gsub(text, '(%d+)s$', ''); - text = text:trim(); - total = total + tonumber(silver) * 100; - end + local left = tonumber(text:len()); + local isValid = (text:len() == 0 and total > 0); - local gFound, _, gold = string.find(text, '(%d+)g$'); - if gFound then - text = string.gsub(text, '(%d+)g$', ''); - text = text:trim(); - total = total + tonumber(gold) * 100 * 100; - end + return total, gold, silver, copper, isValid; + end, - local left = tonumber(text:len()); - local isValid = (text:len() == 0 and total > 0); + formatMoney = function(money) + if type(money) ~= 'number' then + return money; + end - return total, gold, silver, copper, isValid; -end + money = tonumber(money); + local goldColor = '|cfffff209'; + local silverColor = '|cff7b7b7a'; + local copperColor = '|cffac7248'; -StdUi.Util.formatMoney = function(money) - if type(money) ~= 'number' then - return money; - end + local gold = floor(money / COPPER_PER_GOLD); + local silver = floor((money - (gold * COPPER_PER_GOLD)) / COPPER_PER_SILVER); + local copper = floor(money % COPPER_PER_SILVER); - money = tonumber(money); - local goldColor = '|cfffff209'; - local silverColor = '|cff7b7b7a'; - local copperColor = '|cffac7248'; + local output = ''; - local gold = floor(money / COPPER_PER_GOLD); - local silver = floor((money - (gold * COPPER_PER_GOLD)) / COPPER_PER_SILVER); - local copper = floor(money % COPPER_PER_SILVER); + if gold > 0 then + output = format('%s%i%s ', goldColor, gold, '|rg') + end - local output = ''; + if gold > 0 or silver > 0 then + output = format('%s%s%02i%s ', output, silverColor, silver, '|rs') + end - if gold > 0 then - output = format('%s%i%s ', goldColor, gold, '|rg') - end + output = format('%s%s%02i%s ', output, copperColor, copper, '|rc') - if gold > 0 or silver > 0 then - output = format('%s%s%02i%s ', output, silverColor, silver, '|rs') - end + return output:trim(); + end, - output = format('%s%s%02i%s ', output, copperColor, copper, '|rc') + stripColors = function(text) + text = string.gsub(text, '|c%x%x%x%x%x%x%x%x', ''); + text = string.gsub(text, '|r', ''); + return text; + end, - return output:trim(); -end + WrapTextInColor = function(text, r, g, b, a) + local hex = string.format( + '%02x%02x%02x%02x', + Clamp(a * 255, 0, 255), + Clamp(r * 255, 0, 255), + Clamp(g * 255, 0, 255), + Clamp(b * 255, 0, 255) + ); -StdUi.Util.stripColors = function(text) - text = string.gsub(text, '|c%x%x%x%x%x%x%x%x', ''); - text = string.gsub(text, '|r', ''); - return text; -end + return WrapTextInColorCode(text, hex); + end, -StdUi.Util.WrapTextInColor = function(text, r, g, b, a) - local hex = string.format( - '%02x%02x%02x%02x', - Clamp(a * 255, 0, 255), - Clamp(r * 255, 0, 255), - Clamp(g * 255, 0, 255), - Clamp(b * 255, 0, 255) - ); + tableCount = function(tab) + local n = #tab; - return WrapTextInColorCode(text, hex); -end + if n == 0 then + for _ in pairs(tab) do + n = n + 1; + end + end -StdUi.Util.tableCount = function(tab) - local n = #tab; + return n; + end, + + tableMerge = function(default, new) + local result = {}; + for k, v in pairs(default) do + if type(v) == 'table' then + if new[k] then + result[k] = StdUi.Util.tableMerge(v, new[k]); + else + result[k] = v; + end + else + result[k] = new[k] or default[k]; + end + end - if (n == 0) then - for _ in pairs(tab) do - n = n + 1; + for k, v in pairs(new) do + if not result[k] then + result[k] = v; + end end - end - return n; -end + return result; + end, -StdUi.Util.tableMerge = function(default, new) - local result = {}; - for k, v in pairs(default) do - if type(v) == 'table' then - if new[k] then - result[k] = StdUi.Util.tableMerge(v, new[k]); - else - result[k] = v; + stringSplit = function(separator, input, limit) + return { strsplit(separator, input, limit) }; + end, + + --- Ordered pairs + + __genOrderedIndex = function(t) + local orderedIndex = {}; + + for key in pairs(t) do + TableInsert(orderedIndex, key) + end + + TableSort(orderedIndex, function(a, b) + if not t[a].order or not t[b].order then + return a < b; end + + return t[a].order < t[b].order; + end); + + return orderedIndex; + end, + + orderedNext = function(t, state) + local key; + + if state == nil then + -- the first time, generate the index + t.__orderedIndex = StdUi.Util.__genOrderedIndex(t); + key = t.__orderedIndex[1]; else - result[k] = new[k] or default[k]; + -- fetch the next value + for i = 1, TableGetN(t.__orderedIndex) do + if t.__orderedIndex[i] == state then + key = t.__orderedIndex[i + 1]; + end + end end - end - for k, v in pairs(new) do - if not result[k] then - result[k] = v; + if key then + return key, t[key]; end - end - return result; -end + -- no more value to return, cleanup + t.__orderedIndex = nil; + return + end, -StdUi.Util.stringSplit = function(separator, input, limit) - return { strsplit(separator, input, limit) }; -end + orderedPairs = function(t) + return StdUi.Util.orderedNext, t, nil; + end +}; StdUi:RegisterModule(module, version); \ No newline at end of file diff --git a/widgets/Autocomplete.lua b/widgets/Autocomplete.lua index 07e4c92..23408c9 100644 --- a/widgets/Autocomplete.lua +++ b/widgets/Autocomplete.lua @@ -1,13 +1,15 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Autocomplete', 1; +local module, version = 'Autocomplete', 2; if not StdUi:UpgradeNeeded(module, version) then return end; -StdUi.Util.autocompleteTransformer = function(ac, value) +local TableInsert = tinsert; + +StdUi.Util.autocompleteTransformer = function(_, value) return value; end @@ -16,7 +18,7 @@ StdUi.Util.autocompleteValidator = function(self) return true; end -StdUi.Util.autocompleteItemTransformer = function(ac, value) +StdUi.Util.autocompleteItemTransformer = function(_, value) if not value or value == '' then return value; end @@ -55,37 +57,14 @@ StdUi.Util.autocompleteItemValidator = function(ac) end end ---- Very similar to dropdown except it has the ability to create new records and filters results ---- @return EditBox -function StdUi:Autocomplete(parent, width, height, text, validator, transformer, items) - local this = self; - transformer = transformer or StdUi.Util.autocompleteTransformer; - validator = validator or StdUi.Util.autocompleteValidator; - - local autocomplete = self:EditBox(parent, width, height, text, validator); - autocomplete.transformer = transformer; - autocomplete.items = items; - autocomplete.filteredItems = {}; - autocomplete.selectedItem = nil; - autocomplete.itemLimit = 8; - autocomplete.itemTable = {}; - - autocomplete.dropdown = self:Panel(parent, width, 20); - autocomplete.dropdown:SetPoint('TOPLEFT', autocomplete, 'BOTTOMLEFT', 0, 0); - autocomplete.dropdown:SetPoint('TOPRIGHT', autocomplete, 'BOTTOMRIGHT', 0, 0); - autocomplete.dropdown:Hide(); - autocomplete.dropdown:SetFrameLevel(autocomplete:GetFrameLevel() + 10); - - function self:GetFilteredItems() - return self.filteredItems; - end - - autocomplete.buttonCreate = function(panel, i) +local AutocompleteMethods = { + --- Private methods + buttonCreate = function(panel) local optionButton; - optionButton = this:HighlightButton(panel, panel:GetWidth(), 20, ''); + optionButton = StdUi:HighlightButton(panel, panel:GetWidth(), 20, ''); optionButton.text:SetJustifyH('LEFT'); - optionButton.autocomplete = autocomplete; + optionButton.autocomplete = panel.autocomplete; optionButton:SetFrameLevel(panel:GetFrameLevel() + 2); optionButton:SetScript('OnClick', function(b) @@ -99,47 +78,68 @@ function StdUi:Autocomplete(parent, width, height, text, validator, transformer, end); return optionButton; - end + end, - autocomplete.buttonUpdate = function(panel, optionButton, data) + buttonUpdate = function(panel, optionButton, data) optionButton.boundItem = data; optionButton.value = data.value; optionButton:SetWidth(panel:GetWidth()); optionButton:SetText(data.text); - end + end, + + filterItems = function(ac, search, itemsToSearch) + local result = {}; + + for _, item in pairs(itemsToSearch) do + local valueString = tostring(item.value); + if + item.text:lower():find(search:lower(), nil, true) or + valueString:lower():find(search:lower(), nil, true) + then + TableInsert(result, item); + end + + if #result >= ac.itemLimit then + break; + end + end - function autocomplete:SetItems(newItems) + return result; + end, + + --- Public methods + SetItems = function(self, newItems) self.items = newItems; self:RenderItems(); self.dropdown:Hide(); - end + end, - function autocomplete:RenderItems() + RenderItems = function(self) local dropdownHeight = 20 * #self.filteredItems; self.dropdown:SetHeight(dropdownHeight); - this:ObjectList( - autocomplete.dropdown, - autocomplete.itemTable, + self.stdUi:ObjectList( + self.dropdown, + self.itemTable, self.buttonCreate, self.buttonUpdate, self.filteredItems ); - end + end, - function autocomplete:ValueToText(value) + ValueToText = function(self, value) return self.transformer(value) - end + end, - function autocomplete:SetValue(value, text) + SetValue = function(self, value, t) self.value = value; - self:SetText(text or self:ValueToText(value) or ''); + self:SetText(t or self:ValueToText(value) or ''); self:Validate(); self.button:Hide(); - end + end, - function autocomplete:Validate() + Validate = function(self) self.isValidated = true; self.isValid = self:validator(); @@ -149,38 +149,20 @@ function StdUi:Autocomplete(parent, width, height, text, validator, transformer, end end self.isValidated = false; - end; - - autocomplete.filterItems = function(ac, search, itemsToSearch) - local result = {}; - - for _, item in pairs(itemsToSearch) do - local valueString = tostring(item.value); - if - item.text:lower():find(search:lower(), nil, true) or - valueString:lower():find(search:lower(), nil, true) - then - tinsert(result, item); - end - - if #result >= ac.itemLimit then - break; - end - end + end, +}; - return result; - end - - autocomplete:SetScript('OnEditFocusLost', function(s) +local AutocompleteEvents = { + OnEditFocusLost = function(s) s.dropdown:Hide(); - end) + end, - autocomplete:SetScript('OnEnterPressed', function(s) + OnEnterPressed = function(s) s.dropdown:Hide(); s:Validate(); - end) + end, - autocomplete:SetScript('OnTextChanged', function(ac, isUserInput) + OnTextChanged = function(ac, isUserInput) local plainText = StdUi.Util.stripColors(ac:GetText()); ac.selectedItem = nil; @@ -202,7 +184,41 @@ function StdUi:Autocomplete(parent, width, height, text, validator, transformer, ac.dropdown:Show(); end end - end); + end +} + +--- Very similar to dropdown except it has the ability to create new records and filters results +--- @return EditBox +function StdUi:Autocomplete(parent, width, height, text, validator, transformer, items) + transformer = transformer or StdUi.Util.autocompleteTransformer; + validator = validator or StdUi.Util.autocompleteValidator; + + local autocomplete = self:EditBox(parent, width, height, text, validator); + ---@type StdUi + autocomplete.stdUi = self; + autocomplete.transformer = transformer; + autocomplete.items = items; + autocomplete.filteredItems = {}; + autocomplete.selectedItem = nil; + autocomplete.itemLimit = 8; + autocomplete.itemTable = {}; + + autocomplete.dropdown = self:Panel(parent, width, 20); + autocomplete.dropdown:SetPoint('TOPLEFT', autocomplete, 'BOTTOMLEFT', 0, 0); + autocomplete.dropdown:SetPoint('TOPRIGHT', autocomplete, 'BOTTOMRIGHT', 0, 0); + autocomplete.dropdown:Hide(); + autocomplete.dropdown:SetFrameLevel(autocomplete:GetFrameLevel() + 10); + + -- keep back reference + autocomplete.dropdown.autocomplete = autocomplete; + + for k, v in pairs(AutocompleteMethods) do + autocomplete[k] = v; + end + + for k, v in pairs(AutocompleteEvents) do + autocomplete:SetScript(k, v); + end return autocomplete; end diff --git a/widgets/Basic.lua b/widgets/Basic.lua index e193f55..8ad4800 100644 --- a/widgets/Basic.lua +++ b/widgets/Basic.lua @@ -1,10 +1,10 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Basic', 2; +local module, version = 'Basic', 3; if not StdUi:UpgradeNeeded(module, version) then return end; function StdUi:Frame(parent, width, height, inherits) diff --git a/widgets/Button.lua b/widgets/Button.lua index ba1b00a..08a6a4f 100644 --- a/widgets/Button.lua +++ b/widgets/Button.lua @@ -1,10 +1,10 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Button', 3; +local module, version = 'Button', 4; if not StdUi:UpgradeNeeded(module, version) then return end; local SquareButtonCoords = { @@ -15,9 +15,31 @@ local SquareButtonCoords = { DELETE = { 0.01562500, 0.20312500, 0.01562500, 0.20312500}; }; +local SquareButtonMethods = { + SetIconDisabled = function(self, texture, iconWidth, iconHeight) + self.iconDisabled = self.stdUi:Texture(self, iconWidth, iconHeight, texture); + self.iconDisabled:SetDesaturated(true); + self.iconDisabled:SetPoint('CENTER', 0, 0); + + self:SetDisabledTexture(self.iconDisabled); + end, + + SetIcon = function(self, texture, iconWidth, iconHeight, alsoDisabled) + self.icon = self.stdUi:Texture(self, iconWidth, iconHeight, texture); + self.icon:SetPoint('CENTER', 0, 0); + + self:SetNormalTexture(self.icon); + + if alsoDisabled then + self:SetIconDisabled(texture, iconWidth, iconHeight); + end + end +}; + function StdUi:SquareButton(parent, width, height, icon) - local this = self; local button = CreateFrame('Button', nil, parent); + button.stdUi = self; + self:InitWidget(button); self:SetObjSize(button, width, height); @@ -25,26 +47,10 @@ function StdUi:SquareButton(parent, width, height, icon) self:HookDisabledBackdrop(button); self:HookHoverBorder(button); - function button:SetIconDisabled(texture, width, height) - button.iconDisabled = this:Texture(button, width, height, texture); - button.iconDisabled:SetDesaturated(true); - button.iconDisabled:SetPoint('CENTER', 0, 0); - - button:SetDisabledTexture(button.iconDisabled); + for k, v in pairs(SquareButtonMethods) do + button[k] = v; end - function button:SetIcon(texture, width, height, alsoDisabled) - button.icon = this:Texture(button, width, height, texture); - button.icon:SetPoint('CENTER', 0, 0); - - button:SetNormalTexture(button.icon); - - if alsoDisabled then - button:SetIconDisabled(texture, width, height); - end - end - - local coords = SquareButtonCoords[icon]; if coords then button:SetIcon([[Interface\Buttons\SquareButtonTextures]], 16, 16, true); diff --git a/widgets/Checkbox.lua b/widgets/Checkbox.lua index 6fc8748..8761ab4 100644 --- a/widgets/Checkbox.lua +++ b/widgets/Checkbox.lua @@ -1,47 +1,24 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Checkbox', 3; -if not StdUi:UpgradeNeeded(module, version) then return end; ----@return CheckButton -function StdUi:Checkbox(parent, text, width, height) - local checkbox = CreateFrame('Button', nil, parent); - checkbox:EnableMouse(true); - self:SetObjSize(checkbox, width, height or 20); - self:InitWidget(checkbox); - - checkbox.target = self:Panel(checkbox, 16, 16); - checkbox.target:SetPoint('LEFT', 0, 0); - - checkbox.value = true; - checkbox.isChecked = false; - - checkbox.text = self:Label(checkbox, text); - checkbox.text:SetPoint('LEFT', checkbox.target, 'RIGHT', 5, 0); - checkbox.text:SetPoint('RIGHT', checkbox, 'RIGHT', -5, 0); - checkbox.target.text = checkbox.text; -- reference for disabled - - checkbox.checkedTexture = self:Texture(checkbox.target, nil, nil, [[Interface\Buttons\UI-CheckBox-Check]]); - checkbox.checkedTexture:SetAllPoints(); - checkbox.checkedTexture:Hide(); - - checkbox.disabledCheckedTexture = self:Texture(checkbox.target, nil, nil, - [[Interface\Buttons\UI-CheckBox-Check-Disabled]]); - checkbox.disabledCheckedTexture:SetAllPoints(); - checkbox.disabledCheckedTexture:Hide(); +local module, version = 'Checkbox', 4; +if not StdUi:UpgradeNeeded(module, version) then + return +end - function checkbox:GetChecked() - return self.isChecked; - end +---------------------------------------------------- +--- Checkbox +---------------------------------------------------- +local CheckboxMethods = { --- Set checkbox state --- --- @param flag boolean --- @param internal boolean - indicates to not run OnValueChanged - function checkbox:SetChecked(flag, internal) + SetChecked = function(self, flag, internal) self.isChecked = flag; if not internal and self.OnValueChanged then @@ -51,7 +28,7 @@ function StdUi:Checkbox(parent, text, width, height) if not flag then self.checkedTexture:Hide(); self.disabledCheckedTexture:Hide(); - return; + return end if self.isDisabled then @@ -61,37 +38,82 @@ function StdUi:Checkbox(parent, text, width, height) self.checkedTexture:Show(); self.disabledCheckedTexture:Hide(); end - end + end, - function checkbox:SetText(text) - self.text:SetText(text); - end + GetChecked = function(self) + return self.isChecked; + end, + + SetText = function(self, t) + self.text:SetText(t); + end, - function checkbox:SetValue(value) + SetValue = function(self, value) self.value = value; - end + end, - function checkbox:GetValue() + GetValue = function(self) if self:GetChecked() then return self.value; else return nil; end - end + end, - function checkbox:Disable() + Disable = function(self) self.isDisabled = true; self:SetChecked(self.isChecked); - end + end, - function checkbox:Enable() + Enable = function(self) self.isDisabled = false; self:SetChecked(self.isChecked); - end + end, - function checkbox:AutoWidth() + AutoWidth = function(self) self:SetWidth(self.target:GetWidth() + 15 + self.text:GetWidth()); end +}; + +local CheckboxEvents = { + OnClick = function(self) + if not self.isDisabled then + self:SetChecked(not self:GetChecked()); + end + end +} + +---@return CheckButton +function StdUi:Checkbox(parent, text, width, height) + local checkbox = CreateFrame('Button', nil, parent); + checkbox.stdUi = self; + checkbox:EnableMouse(true); + self:SetObjSize(checkbox, width, height or 20); + self:InitWidget(checkbox); + + checkbox.target = self:Panel(checkbox, 16, 16); + checkbox.target:SetPoint('LEFT', 0, 0); + + checkbox.value = true; + checkbox.isChecked = false; + + checkbox.text = self:Label(checkbox, text); + checkbox.text:SetPoint('LEFT', checkbox.target, 'RIGHT', 5, 0); + checkbox.text:SetPoint('RIGHT', checkbox, 'RIGHT', -5, 0); + checkbox.target.text = checkbox.text; -- reference for disabled + + checkbox.checkedTexture = self:Texture(checkbox.target, nil, nil, [[Interface\Buttons\UI-CheckBox-Check]]); + checkbox.checkedTexture:SetAllPoints(); + checkbox.checkedTexture:Hide(); + + checkbox.disabledCheckedTexture = self:Texture(checkbox.target, nil, nil, + [[Interface\Buttons\UI-CheckBox-Check-Disabled]]); + checkbox.disabledCheckedTexture:SetAllPoints(); + checkbox.disabledCheckedTexture:Hide(); + + for k, v in pairs(CheckboxMethods) do + checkbox[k] = v; + end self:ApplyBackdrop(checkbox.target); self:HookDisabledBackdrop(checkbox); @@ -101,15 +123,17 @@ function StdUi:Checkbox(parent, text, width, height) checkbox:AutoWidth(); end - checkbox:SetScript('OnClick', function(frame) - if not frame.isDisabled then - frame:SetChecked(not frame:GetChecked()); - end - end); + for k, v in pairs(CheckboxEvents) do + checkbox:SetScript(k, v); + end return checkbox; end +---------------------------------------------------- +--- IconCheckbox +---------------------------------------------------- + function StdUi:IconCheckbox(parent, icon, text, width, height, iconSize) iconSize = iconSize or 16 local checkbox = self:Checkbox(parent, text, width, height); @@ -123,6 +147,18 @@ function StdUi:IconCheckbox(parent, icon, text, width, height, iconSize) return checkbox; end +---------------------------------------------------- +--- Radio +---------------------------------------------------- + +local RadioEvents = { + OnClick = function(self) + if not self.isDisabled then + self:SetChecked(true); + end + end +}; + ---@return CheckButton function StdUi:Radio(parent, text, groupName, width, height) local radio = self:Checkbox(parent, text, width, height); @@ -138,20 +174,19 @@ function StdUi:Radio(parent, text, groupName, width, height) radio.disabledCheckedTexture:Hide(); radio.disabledCheckedTexture:SetTexCoord(0.75, 1, 0, 1); - radio:SetScript('OnClick', function(frame) - if not frame.isDisabled then - frame:SetChecked(true); - end - end); + for k, v in pairs(RadioEvents) do + radio:SetScript(k, v); + end if groupName then - self:AddToRadioGroup(groupName, radio); + self:AddToRadioGroup(radio, groupName); end return radio; end StdUi.radioGroups = {}; +StdUi.radioGroupValues = {}; ---@return CheckButton[] function StdUi:RadioGroup(groupName) @@ -159,6 +194,10 @@ function StdUi:RadioGroup(groupName) self.radioGroups[groupName] = {}; end + if not self.radioGroupValues[groupName] then + self.radioGroupValues[groupName] = {}; + end + return self.radioGroups[groupName]; end @@ -186,47 +225,62 @@ function StdUi:SetRadioGroupValue(groupName, value) return nil; end -function StdUi:OnRadioGroupValueChanged(groupName, callback) - local group = self:RadioGroup(groupName); - - local function changed(radio, flag, value) - radio.notified = true; +local radioGroupOnValueChanged = function(radio) + radio.notified = true; + local group = radio.radioGroup; + local groupName = radio.radioGroupName; - -- We must get all notifications from group - for i = 1, #group do - if not group[i].notified then - return; - end + -- We must get all notifications from group + for i = 1, #group do + if not group[i].notified then + return end + end - callback(self:GetRadioGroupValue(groupName), groupName); + local newValue = radio.stdUi:GetRadioGroupValue(groupName); + if radio.stdUi.radioGroupValues[groupName] ~= newValue then + radio.OnValueChangedCallback(newValue, groupName); + end + radio.stdUi.radioGroupValues[groupName] = newValue; - for i = 1, #group do - group[i].notified = false; - end + for i = 1, #group do + group[i].notified = false; end +end + +function StdUi:OnRadioGroupValueChanged(groupName, callback) + local group = self:RadioGroup(groupName); for i = 1, #group do local radio = group[i]; - radio.OnValueChanged = changed; + radio.OnValueChangedCallback = callback; + radio.OnValueChanged = radioGroupOnValueChanged; end return nil; end -function StdUi:AddToRadioGroup(groupName, frame) - local group = self:RadioGroup(groupName); - tinsert(group, frame); - frame.radioGroup = group; - - frame:HookScript('OnClick', function(radio) +local RadioGroupEvents = { + OnClick = function(radio) for i = 1, #radio.radioGroup do local otherRadio = radio.radioGroup[i]; + if otherRadio ~= radio then otherRadio:SetChecked(false); end end - end); + end +}; + +function StdUi:AddToRadioGroup(radio, groupName) + local group = self:RadioGroup(groupName); + tinsert(group, radio); + radio.radioGroup = group; + radio.radioGroupName = groupName; + + for k, v in pairs(RadioGroupEvents) do + radio:HookScript(k, v); + end end StdUi:RegisterModule(module, version); \ No newline at end of file diff --git a/widgets/ColorPicker.lua b/widgets/ColorPicker.lua index 94e1f6c..00724b5 100644 --- a/widgets/ColorPicker.lua +++ b/widgets/ColorPicker.lua @@ -1,11 +1,91 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); -if not StdUi or StdUi.ColorPickerFrame then - return ; +if not StdUi then + return end -local module, version = 'ColorPicker', 1; -if not StdUi:UpgradeNeeded(module, version) then return end; +local module, version = 'ColorPicker', 3; +if not StdUi:UpgradeNeeded(module, version) then + return +end + +local ColorPickerMethods = { + SetColorRGBA = function(self, r, g, b, a) + self:SetColorAlpha(a); + self:SetColorRGB(r, g, b); + + self.newTexture:SetVertexColor(r, g, b, a); + end, + + GetColorRGBA = function(self) + local r, g, b = self:GetColorRGB(); + return r, g, b, self:GetColorAlpha(); + end, + + SetColor = function(self, c) + self:SetColorAlpha(c.a or 1); + self:SetColorRGB(c.r, c.g, c.b); + + self.newTexture:SetVertexColor(c.r, c.g, c.b, c.a or 1); + end, + + GetColor = function(self) + local r, g, b = self:GetColorRGB(); + return { r = r, g = g, b = b, a = self:GetColorAlpha() }; + end, + + SetColorAlpha = function(self, a, fromSlider) + a = Clamp(a, 0, 1); + + if not fromSlider then + self.alphaSlider:SetValue(100 - a * 100); + end + + self.aEdit:SetValue(Round(a * 100)); + self.aEdit:Validate(); + self:SetColorRGB(self:GetColorRGB()); + end, + + GetColorAlpha = function(self) + local a = Clamp(tonumber(self.aEdit:GetValue()) or 100, 0, 100); + return a / 100; + end +}; + +local ColorPickerEvents = { + OnColorSelect = function(self) + -- Ensure custom fields are updated. + local r, g, b, a = self:GetColorRGBA(); + + if not self.skipTextUpdate then + self.rEdit:SetValue(r * 255); + self.gEdit:SetValue(g * 255); + self.bEdit:SetValue(b * 255); + self.aEdit:SetValue(100 * a); + + self.rEdit:Validate(); + self.gEdit:Validate(); + self.bEdit:Validate(); + self.aEdit:Validate(); + end + + self.newTexture:SetVertexColor(r, g, b, a); + self.alphaTexture:SetGradientAlpha('VERTICAL', 1, 1, 1, 0, r, g, b, 1); + end +}; + +local function OnColorPickerValueChanged(self) + local cpf = self:GetParent(); + local r = tonumber(cpf.rEdit:GetValue() or 255) / 255; + local g = tonumber(cpf.gEdit:GetValue() or 255) / 255; + local b = tonumber(cpf.bEdit:GetValue() or 255) / 255; + local a = tonumber(cpf.aEdit:GetValue() or 100) / 100; + + cpf.skipTextUpdate = true; + cpf:SetColorRGB(r, g, b); + cpf.alphaSlider:SetValue(100 - a * 100); + cpf.skipTextUpdate = false; +end --- alphaSliderTexture = [[Interface\AddOns\YourAddon\Libs\StdUi\media\Checkers.tga]] function StdUi:ColorPicker(parent, alphaSliderTexture) @@ -51,12 +131,11 @@ function StdUi:ColorPicker(parent, alphaSliderTexture) --cpf.alphaTexture:SetGradientAlpha('VERTICAL', 0, 0, 0, 1, 1, 1, 1, 1); cpf.alphaThumbTexture = self:Texture(cpf.alphaSlider, barWidth, thumbWidth, - [[Interface\Buttons\UI-ColorPicker-Buttons]]); + [[Interface\Buttons\UI-ColorPicker-Buttons]]); cpf.alphaThumbTexture:SetTexCoord(0.275, 1, 0.875, 0); cpf.alphaThumbTexture:SetDrawLayer('ARTWORK', 2); cpf.alphaSlider:SetThumbTexture(cpf.alphaThumbTexture); - cpf.newTexture = self:Texture(cpf, 32, 32, [[Interface\Buttons\WHITE8X8]]); cpf.oldTexture = self:Texture(cpf, 32, 32, [[Interface\Buttons\WHITE8X8]]); cpf.newTexture:SetDrawLayer('ARTWORK', 5); @@ -98,33 +177,8 @@ function StdUi:ColorPicker(parent, alphaSliderTexture) --- Methods ---------------------------------------------------- - function cpf:SetColorRGBA(r, g, b, a) - self:SetColorAlpha(a); - self:SetColorRGB(r, g, b); - - self.newTexture:SetVertexColor(r, g, b, a); - end - - function cpf:GetColorRGBA() - local r, g, b = self:GetColorRGB(); - return r, g, b, self:GetColorAlpha(); - end - - function cpf:SetColorAlpha(a, fromSlider) - a = Clamp(a, 0, 1); - - if not fromSlider then - self.alphaSlider:SetValue(100 - a * 100); - end - - self.aEdit:SetValue(Round(a * 100)); - self.aEdit:Validate(); - self:SetColorRGB(self:GetColorRGB()); - end - - function cpf:GetColorAlpha() - local a = Clamp(tonumber(self.aEdit:GetValue()) or 100, 0, 100); - return a / 100; + for k, v in pairs(ColorPickerMethods) do + cpf[k] = v; end ---------------------------------------------------- @@ -135,49 +189,37 @@ function StdUi:ColorPicker(parent, alphaSliderTexture) cpf:SetColorAlpha((100 - slider:GetValue()) / 100, true); end); - cpf:SetScript('OnColorSelect', function(self) - -- Ensure custom fields are updated. - local r, g, b, a = self:GetColorRGBA(); - - if not self.skipTextUpdate then - self.rEdit:SetValue(r * 255); - self.gEdit:SetValue(g * 255); - self.bEdit:SetValue(b * 255); - self.aEdit:SetValue(100 * a); - - self.rEdit:Validate(); - self.gEdit:Validate(); - self.bEdit:Validate(); - self.aEdit:Validate(); - end + for k, v in pairs(ColorPickerEvents) do + cpf:SetScript(k, v); + end - self.newTexture:SetVertexColor(r, g, b, a); - self.alphaTexture:SetGradientAlpha('VERTICAL', 1, 1, 1, 0, r, g, b, 1); - end); + cpf.rEdit.OnValueChanged = OnColorPickerValueChanged; + cpf.gEdit.OnValueChanged = OnColorPickerValueChanged; + cpf.bEdit.OnValueChanged = OnColorPickerValueChanged; + cpf.aEdit.OnValueChanged = OnColorPickerValueChanged; - local function OnValueChanged() - local r = tonumber(cpf.rEdit:GetValue() or 255) / 255; - local g = tonumber(cpf.gEdit:GetValue() or 255) / 255; - local b = tonumber(cpf.bEdit:GetValue() or 255) / 255; - local a = tonumber(cpf.aEdit:GetValue() or 100) / 100; + return cpf; +end - cpf.skipTextUpdate = true; - cpf:SetColorRGB(r, g, b); - cpf.alphaSlider:SetValue(100 - a * 100); - cpf.skipTextUpdate = false; +local ColorPickerFrameOkCallback = function(self) + local cpf = self:GetParent(); + if cpf.okCallback then + cpf.okCallback(cpf); end + cpf:Hide(); +end - cpf.rEdit.OnValueChanged = OnValueChanged; - cpf.gEdit.OnValueChanged = OnValueChanged; - cpf.bEdit.OnValueChanged = OnValueChanged; - cpf.aEdit.OnValueChanged = OnValueChanged; +local ColorPickerFrameCancelCallback = function(self) + local cpf = self:GetParent(); + if cpf.cancelCallback then + cpf.cancelCallback(cpf); + end - return cpf; + cpf:Hide(); end -- placeholder -StdUi.colorPickerFrame = nil; function StdUi:ColorPickerFrame(r, g, b, a, okCallback, cancelCallback, alphaSliderTexture) local colorPickerFrame = self.colorPickerFrame; if not colorPickerFrame then @@ -186,19 +228,11 @@ function StdUi:ColorPickerFrame(r, g, b, a, okCallback, cancelCallback, alphaSli self.colorPickerFrame = colorPickerFrame; end - colorPickerFrame.okButton:SetScript('OnClick', function (self) - if okCallback then - okCallback(colorPickerFrame); - end - colorPickerFrame:Hide(); - end); + colorPickerFrame.okCallback = okCallback; + colorPickerFrame.cancelCallback = cancelCallback; - colorPickerFrame.cancelButton:SetScript('OnClick', function (self) - if cancelCallback then - cancelCallback(colorPickerFrame); - end - colorPickerFrame:Hide(); - end); + colorPickerFrame.okButton:SetScript('OnClick', ColorPickerFrameOkCallback); + colorPickerFrame.cancelButton:SetScript('OnClick', ColorPickerFrameCancelCallback); colorPickerFrame:SetColorRGBA(r or 1, g or 1, b or 1, a or 1); colorPickerFrame.oldTexture:SetVertexColor(r or 1, g or 1, b or 1, a or 1); @@ -208,10 +242,51 @@ function StdUi:ColorPickerFrame(r, g, b, a, okCallback, cancelCallback, alphaSli colorPickerFrame:Show(); end -function StdUi:ColorInput(parent, label, width, height, r, g, b, a) - local this = self; +local ColorInputMethods = { + SetColor = function(self, c) + if type(c) == 'table' then + self.color.r = c.r; + self.color.g = c.g; + self.color.b = c.b; + self.color.a = c.a or 1; + end + + self.target:SetBackdropColor(c.r, c.g, c.b, c.a or 1); + if self.OnValueChanged then + self:OnValueChanged(c); + end + end, + + GetColor = function (self, type) + if type == 'hex' then + elseif type == 'rgba' then + return self.color.r, self.color.g, self.color.b, self.color.a + else + -- object + return self.color; + end + end +}; + +local ColorInputOkCallback = function(cpf) + self:SetColor(cpf:GetColor()); +end + +local ColorInputEvents = { + OnClick = function(self) + self.stdUi:ColorPickerFrame( + self.color.r, + self.color.g, + self.color.b, + self.color.a, + ColorInputOkCallback + ); + end +}; +function StdUi:ColorInput(parent, label, width, height, color) local button = CreateFrame('Button', nil, parent); + button.stdUi = self; button:EnableMouse(true); self:SetObjSize(button, width, height or 20); self:InitWidget(button); @@ -223,52 +298,18 @@ function StdUi:ColorInput(parent, label, width, height, r, g, b, a) button.text:SetPoint('LEFT', button.target, 'RIGHT', 5, 0); button.text:SetPoint('RIGHT', button, 'RIGHT', -5, 0); - button.color = {}; + button.color = {r = 1, g = 1, b = 1, a = 1}; - function button:SetColor(r, g, b, a) - if type(r) == 'table' then - self.color.r = r.r; - self.color.g = r.g; - self.color.b = r.b; - self.color.a = r.a; - elseif type(r) == 'string' then - - else - self.color = { - r = r, g = g, b = b, a = a, - }; - end - - self.target:SetBackdropColor(r, g, b, a); - if self.OnValueChanged then - self:OnValueChanged(r, g, b, a); - end + for k, v in pairs(ColorInputMethods) do + button[k] = v; end - function button:GetColor(type) - if type == 'hex' then - elseif type == 'rgba' then - return self.color.r, self.color.g, self.color.b, self.color.a - else - -- object - return self.color; - end + for k, v in pairs(ColorInputEvents) do + button:SetScript(k, v); end - button:SetScript('OnClick', function(btn) - StdUi:ColorPickerFrame( - btn.color.r, - btn.color.g, - btn.color.b, - btn.color.a, - function(cpf) - btn:SetColor(cpf:GetColorRGBA()); - end - ); - end); - - if r then - button:SetColor(r, g, b, a); + if color then + button:SetColor(color); end return button; diff --git a/widgets/ContextMenu.lua b/widgets/ContextMenu.lua index dfc38db..7c844da 100644 --- a/widgets/ContextMenu.lua +++ b/widgets/ContextMenu.lua @@ -1,21 +1,60 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'ContextMenu', 2; -if not StdUi:UpgradeNeeded(module, version) then return end; +local module, version = 'ContextMenu', 3; +if not StdUi:UpgradeNeeded(module, version) then + return +end + +--- ContextMenuItem Events + +local ContextMenuItemOnEnter = function(itemFrame, button) + itemFrame.parentContext:CloseSubMenus(); + + itemFrame.childContext:ClearAllPoints(); + itemFrame.childContext:SetPoint('TOPLEFT', itemFrame, 'TOPRIGHT', 0, 0); + itemFrame.childContext:Show(); +end + +local ContextMenuItemOnMouseUp = function(itemFrame, button) + if button == 'LeftButton' and itemFrame.contextMenuData.callback then + itemFrame.contextMenuData.callback(itemFrame, itemFrame.parentContext) + end +end + +--- ContextMenuEvents + +local ContextMenuOnMouseUp = function(self, button) + if button == 'RightButton' then + local uiScale = UIParent:GetScale(); + local cursorX, cursorY = GetCursorPosition(); + + cursorX = cursorX / uiScale; + cursorY = cursorY / uiScale; + + self:ClearAllPoints(); + + if self:IsShown() then + self:Hide(); + else + self:SetPoint('TOPLEFT', nil, 'BOTTOMLEFT', cursorX, cursorY); + self:Show(); + end + end +end ---@type ContextMenu StdUi.ContextMenuMethods = { - CloseMenu = function(self) + CloseMenu = function(self) self:CloseSubMenus(); self:Hide(); end, - CloseSubMenus = function(self) + CloseSubMenus = function(self) for i = 1, #self.optionFrames do local optionFrame = self.optionFrames[i]; if optionFrame.childContext then @@ -24,28 +63,10 @@ StdUi.ContextMenuMethods = { end end, - HookRightClick = function(self) + HookRightClick = function(self) local parent = self:GetParent(); if parent then - parent:HookScript('OnMouseUp', function(par, button) - - if button == 'RightButton' then - local uiScale = UIParent:GetScale(); - local cursorX, cursorY = GetCursorPosition(); - - cursorX = cursorX / uiScale; - cursorY = cursorY / uiScale; - - self:ClearAllPoints(); - - if self:IsShown() then - self:Hide(); - else - self:SetPoint('TOPLEFT', nil, 'BOTTOMLEFT', cursorX, cursorY); - self:Show(); - end - end - end); + parent:HookScript('OnMouseUp', ContextMenuOnMouseUp); end end, @@ -53,7 +74,7 @@ StdUi.ContextMenuMethods = { end, - CreateItem = function(parent, data, i) + CreateItem = function(parent, data, i) local itemFrame; if data.title then @@ -75,6 +96,8 @@ StdUi.ContextMenuMethods = { itemFrame = parent.stdUi:HighlightButton(parent, nil, 20); end + itemFrame.contextMenuData = data; + if not data.isSeparator then itemFrame.text:SetJustifyH('LEFT'); end @@ -89,13 +112,7 @@ StdUi.ContextMenuMethods = { -- this will keep propagating mainContext thru all children itemFrame.mainContext = parent.mainContext; - itemFrame:HookScript('OnEnter', function(itemFrame, button) - parent:CloseSubMenus(); - - itemFrame.childContext:ClearAllPoints(); - itemFrame.childContext:SetPoint('TOPLEFT', itemFrame, 'TOPRIGHT', 0, 0); - itemFrame.childContext:Show(); - end); + itemFrame:HookScript('OnEnter', ContextMenuItemOnEnter); end if data.events then @@ -105,11 +122,7 @@ StdUi.ContextMenuMethods = { end if data.callback then - itemFrame:SetScript('OnMouseUp', function(frame, button) - if button == 'LeftButton' then - data.callback(frame, frame.parentContext) - end - end) + itemFrame:SetScript('OnMouseUp', ContextMenuItemOnMouseUp) end if data.custom then @@ -121,7 +134,7 @@ StdUi.ContextMenuMethods = { return itemFrame; end, - UpdateItem = function(parent, itemFrame, data, i) + UpdateItem = function(parent, itemFrame, data, i) local padding = parent.padding; if data.title then @@ -143,7 +156,7 @@ StdUi.ContextMenuMethods = { itemFrame:SetWidth(itemFrame:GetWidth() + 16); end - if (parent:GetWidth() - padding * 2) < itemFrame:GetWidth() then + if (parent:GetWidth() - padding * 2) < itemFrame:GetWidth() then parent:SetWidth(itemFrame:GetWidth() + padding * 2); end @@ -155,7 +168,7 @@ StdUi.ContextMenuMethods = { end end, - DrawOptions = function(self, options) + DrawOptions = function(self, options) if not self.optionFrames then self.optionFrames = {}; end @@ -174,14 +187,14 @@ StdUi.ContextMenuMethods = { self:SetHeight(totalHeight + self.padding); end, - StartHideCounter = function(self) + StartHideCounter = function(self) if self.timer then self.timer:Cancel(); end self.timer = C_Timer:NewTimer(3, self.TimerCallback); end, - StopHideCounter = function() + StopHideCounter = function() end }; @@ -204,12 +217,12 @@ function StdUi:ContextMenu(parent, options, stopHook, level) panel:SetFrameStrata('FULLSCREEN_DIALOG'); - for methodName, method in pairs(self.ContextMenuMethods) do - panel[methodName] = method; + for k, v in pairs(self.ContextMenuMethods) do + panel[k] = v; end - for eventName, eventHandler in pairs(self.ContextMenuEvents) do - panel:SetScript(eventName, eventHandler); + for k, v in pairs(self.ContextMenuEvents) do + panel:SetScript(k, v); end panel:DrawOptions(options); diff --git a/widgets/Dropdown.lua b/widgets/Dropdown.lua index 1066fae..5091e7c 100644 --- a/widgets/Dropdown.lua +++ b/widgets/Dropdown.lua @@ -1,46 +1,62 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Dropdown', 3; +local module, version = 'Dropdown', 4; if not StdUi:UpgradeNeeded(module, version) then return end; +local TableInsert = tinsert; + -- reference to all other dropdowns to close them when new one opens -local dropdowns = {}; +local dropdowns = StdUiDropdowns or {}; +StdUiDropdowns = dropdowns; ---- Creates a single level dropdown menu ---- local options = { ---- {text = 'some text', value = 10}, ---- {text = 'some text2', value = 11}, ---- {text = 'some text3', value = 12}, ---- } -function StdUi:Dropdown(parent, width, height, options, value, multi, assoc) - local this = self; - local dropdown = self:Button(parent, width, height, ''); - dropdown.text:SetJustifyH('LEFT'); - -- make it shorter because of arrow - dropdown.text:ClearAllPoints(); - self:GlueAcross(dropdown.text, dropdown, 2, -2, -16, 2); - - local dropTex = self:Texture(dropdown, 15, 15, [[Interface\Buttons\SquareButtonTextures]]); - dropTex:SetTexCoord(0.45312500, 0.64062500, 0.20312500, 0.01562500); - self:GlueRight(dropTex, dropdown, -2, 0, true); +local DropdownItemOnClick = function(self) + self.dropdown:SetValue(self.value, self:GetText()); + self.dropdown.optsFrame:Hide(); +end - local optsFrame = self:FauxScrollFrame(dropdown, dropdown:GetWidth(), 200, 10, 20); - optsFrame:Hide(); - self:GlueBelow(optsFrame, dropdown, 0, 0, 'LEFT'); - dropdown:SetFrameLevel(optsFrame:GetFrameLevel() + 1); +local DropdownItemOnValueChanged = function(checkbox, isChecked) + checkbox.dropdown:ToggleValue(checkbox.value, isChecked); +end - dropdown.multi = multi; - dropdown.assoc = assoc; +local DropdownMethods = { + buttonCreate = function(parent) + local dropdown = parent.dropdown; + local optionButton; - dropdown.optsFrame = optsFrame; - dropdown.dropTex = dropTex; - dropdown.options = options; + if dropdown.multi then + optionButton = dropdown.stdUi:Checkbox(parent, '', parent:GetWidth(), 20); + else + optionButton = dropdown.stdUi:HighlightButton(parent, parent:GetWidth(), 20, ''); + optionButton.text:SetJustifyH('LEFT'); + end - function dropdown:ShowOptions() + optionButton.dropdown = dropdown; + optionButton:SetFrameLevel(parent:GetFrameLevel() + 2); + if not dropdown.multi then + optionButton:SetScript('OnClick', DropdownItemOnClick); + else + optionButton.OnValueChanged = DropdownItemOnValueChanged; + end + + return optionButton; + end, + + buttonUpdate = function(parent, itemFrame, data) + itemFrame:SetWidth(parent:GetWidth()); + itemFrame:SetText(data.text); + + if itemFrame.dropdown.multi then + itemFrame:SetValue(data.value); + else + itemFrame.value = data.value; + end + end, + + ShowOptions = function(self) for i = 1, #dropdowns do dropdowns[i]:HideOptions(); end @@ -49,86 +65,55 @@ function StdUi:Dropdown(parent, width, height, options, value, multi, assoc) self.optsFrame:Show(); self.optsFrame:Update(); self:RepaintOptions(); - end + end, - function dropdown:HideOptions() + HideOptions = function(self) self.optsFrame:Hide(); - end + end, - function dropdown:ToggleOptions() + ToggleOptions = function(self) if self.optsFrame:IsShown() then self:HideOptions(); else self:ShowOptions(); end - end + end, - function dropdown:SetPlaceholder(placeholderText) + SetPlaceholder = function(self, placeholderText) if self:GetText() == '' or self:GetText() == self.placeholder then self:SetText(placeholderText); end self.placeholder = placeholderText; - end - - dropdown.buttonCreate = function(parent, i) - local optionButton; - if dropdown.multi then - optionButton = this:Checkbox(parent, '', parent:GetWidth(), 20); - else - optionButton = this:HighlightButton(parent, parent:GetWidth(), 20, ''); - optionButton.text:SetJustifyH('LEFT'); - end - - optionButton.dropdown = dropdown; - optionButton:SetFrameLevel(parent:GetFrameLevel() + 2); - if not dropdown.multi then - optionButton:SetScript('OnClick', function(self) - self.dropdown:SetValue(self.value, self:GetText()); - self.dropdown.optsFrame:Hide(); - end); - else - optionButton.OnValueChanged = function(checkbox, isChecked) - checkbox.dropdown:ToggleValue(checkbox.value, isChecked); - end - end - - return optionButton; - end - - dropdown.buttonUpdate = function(parent, itemFrame, data) - itemFrame:SetWidth(parent:GetWidth()); - itemFrame:SetText(data.text); + end, - if itemFrame.dropdown.multi then - itemFrame:SetValue(data.value); - else - itemFrame.value = data.value; - end - end - - function dropdown:RepaintOptions() + RepaintOptions = function(self) local scrollChild = self.optsFrame.scrollChild; - this:ObjectList(scrollChild, scrollChild.items, self.buttonCreate, self.buttonUpdate, self.options); + self.stdUi:ObjectList( + scrollChild, + scrollChild.items, + self.buttonCreate, + self.buttonUpdate, + self.options + ); self.optsFrame:UpdateItemsCount(#self.options); - end + end, - function dropdown:SetOptions(newOptions) + SetOptions = function(self, newOptions) self.options = newOptions; local optionsHeight = #newOptions * 20; local scrollChild = self.optsFrame.scrollChild; - - self.optsFrame:SetHeight(math.min(optionsHeight + 4, 200)); - scrollChild:SetHeight(optionsHeight); - if not scrollChild.items then scrollChild.items = {}; end + self.optsFrame:SetHeight(math.min(optionsHeight + 4, 200)); + scrollChild:SetHeight(optionsHeight); + self:RepaintOptions(); - end + end, - function dropdown:ToggleValue(value, state) + ToggleValue = function(self, value, state) assert(self.multi, 'Single dropdown cannot have more than one value!'); -- Treat is as associative array @@ -138,7 +123,7 @@ function StdUi:Dropdown(parent, width, height, options, value, multi, assoc) if state then -- we are toggling it on if not tContains(self.value, value) then - tinsert(self.value, value); + TableInsert(self.value, value); end else -- we are removing it from table @@ -149,9 +134,9 @@ function StdUi:Dropdown(parent, width, height, options, value, multi, assoc) end self:SetValue(self.value); - end + end, - function dropdown:SetValue(value, text) + SetValue = function(self, value, text) self.value = value; if text then @@ -176,13 +161,13 @@ function StdUi:Dropdown(parent, width, height, options, value, multi, assoc) if self.OnValueChanged then self.OnValueChanged(self, value, self:GetText()); end - end + end, - function dropdown:GetValue() + GetValue = function(self) return self.value; - end + end, - function dropdown:FindValueText(value) + FindValueText = function(self, value) if type(value) ~= 'table' then for i = 1, #self.options do local opt = self.options[i]; @@ -229,6 +214,52 @@ function StdUi:Dropdown(parent, width, height, options, value, multi, assoc) end end end +}; + +local DropdownEvents = { + OnClick = function(self) + self:ToggleOptions(); + end +}; + +--- Creates a single level dropdown menu +--- local options = { +--- {text = 'some text', value = 10}, +--- {text = 'some text2', value = 11}, +--- {text = 'some text3', value = 12}, +--- } +--- @return Dropdown +function StdUi:Dropdown(parent, width, height, options, value, multi, assoc) + --- @class Dropdown + local dropdown = self:Button(parent, width, height, ''); + dropdown.stdUi = self; + + dropdown.text:SetJustifyH('LEFT'); + -- make it shorter because of arrow + dropdown.text:ClearAllPoints(); + self:GlueAcross(dropdown.text, dropdown, 2, -2, -16, 2); + + local dropTex = self:Texture(dropdown, 15, 15, [[Interface\Buttons\SquareButtonTextures]]); + dropTex:SetTexCoord(0.45312500, 0.64062500, 0.20312500, 0.01562500); + self:GlueRight(dropTex, dropdown, -2, 0, true); + + local optsFrame = self:FauxScrollFrame(dropdown, dropdown:GetWidth(), 200, 10, 20); + optsFrame:Hide(); + self:GlueBelow(optsFrame, dropdown, 0, 1, 'LEFT'); + dropdown:SetFrameLevel(optsFrame:GetFrameLevel() + 1); + + dropdown.multi = multi; + dropdown.assoc = assoc; + + dropdown.optsFrame = optsFrame; + dropdown.dropTex = dropTex; + dropdown.options = options; + + optsFrame.scrollChild.dropdown = dropdown; + + for k, v in pairs(DropdownMethods) do + dropdown[k] = v; + end if options then dropdown:SetOptions(options); @@ -240,11 +271,11 @@ function StdUi:Dropdown(parent, width, height, options, value, multi, assoc) dropdown.value = {}; end - dropdown:SetScript('OnClick', function(self) - self:ToggleOptions(); - end); + for k, v in pairs(DropdownEvents) do + dropdown:SetScript(k, v); + end - tinsert(dropdowns, dropdown); + TableInsert(dropdowns, dropdown); return dropdown; end diff --git a/widgets/EditBox.lua b/widgets/EditBox.lua index db802ed..c55bf78 100644 --- a/widgets/EditBox.lua +++ b/widgets/EditBox.lua @@ -1,29 +1,48 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'EditBox', 5; +local module, version = 'EditBox', 6; if not StdUi:UpgradeNeeded(module, version) then return end; +local pairs = pairs; +local strlen = strlen; + +---------------------------------------------------- +--- SimpleEditBox +---------------------------------------------------- + +local SimpleEditBoxMethods = { + SetFontSize = function(self, newSize) + self:SetFont(self:GetFont(), newSize, self.stdUi.config.font.effect); + end +}; + +local SimpleEditBoxEvents = { + OnEscapePressed = function (self) + self:ClearFocus(); + end +} + --- @return EditBox function StdUi:SimpleEditBox(parent, width, height, text) - local this = self; --- @type EditBox local editBox = CreateFrame('EditBox', nil, parent); + editBox.stdUi = self; self:InitWidget(editBox); editBox:SetTextInsets(3, 3, 3, 3); editBox:SetFontObject(ChatFontNormal); editBox:SetAutoFocus(false); - editBox:SetScript('OnEscapePressed', function (self) - self:ClearFocus(); - end); + for k, v in pairs(SimpleEditBoxMethods) do + editBox[k] = v; + end - function editBox:SetFontSize(newSize) - self:SetFont(self:GetFont(), newSize, this.config.font.effect); + for k, v in pairs(SimpleEditBoxEvents) do + editBox:SetScript(k, v); end if text then @@ -38,6 +57,20 @@ function StdUi:SimpleEditBox(parent, width, height, text) return editBox; end +---------------------------------------------------- +--- ApplyPlaceholder +---------------------------------------------------- + +local ApplyPlaceholderOnTextChanged = function(self) + if strlen(self:GetText()) > 0 then + self.placeholder.icon:Hide(); + self.placeholder.label:Hide(); + else + self.placeholder.icon:Show(); + self.placeholder.label:Show(); + end +end + function StdUi:ApplyPlaceholder(widget, placeholderText, icon, iconColor) widget.placeholder = {}; @@ -57,15 +90,17 @@ function StdUi:ApplyPlaceholder(widget, placeholderText, icon, iconColor) self:GlueLeft(label, widget, 2, 0, true); end - widget:HookScript('OnTextChanged', function(self) - if strlen(self:GetText()) > 0 then - self.placeholder.icon:Hide(); - self.placeholder.label:Hide(); - else - self.placeholder.icon:Show(); - self.placeholder.label:Show(); - end - end); + widget:HookScript('OnTextChanged', ApplyPlaceholderOnTextChanged); +end + +---------------------------------------------------- +--- SearchEditBox +---------------------------------------------------- + +local SearchEditBoxOnTextChanged = function(self) + if self.OnValueChanged then + self:OnValueChanged(self:GetText()); + end end function StdUi:SearchEditBox(parent, width, height, placeholderText) @@ -73,38 +108,32 @@ function StdUi:SearchEditBox(parent, width, height, placeholderText) self:ApplyPlaceholder(editBox, placeholderText, [[Interface\Common\UI-Searchbox-Icon]]); - editBox:SetScript('OnTextChanged', function(self) - if self.OnValueChanged then - self:OnValueChanged(self:GetText()); - end - end); + editBox:SetScript('OnTextChanged', SearchEditBoxOnTextChanged); return editBox; end ---- @return EditBox -function StdUi:EditBox(parent, width, height, text, validator) - validator = validator or StdUi.Util.editBoxValidator; - - local editBox = self:SimpleEditBox(parent, width, height, text); - editBox.validator = validator; +---------------------------------------------------- +--- SearchEditBox +---------------------------------------------------- - function editBox:GetValue() +local EditBoxMethods = { + GetValue = function(self) return self.value; - end; + end, - function editBox:SetValue(value) + SetValue = function(self, value) self.value = value; self:SetText(value); self:Validate(); self.button:Hide(); - end; + end, - function editBox:IsValid() + IsValid = function(self) return self.isValid; - end; + end, - function editBox:Validate() + Validate = function(self) self.isValidated = true; self.isValid = self.validator(self); @@ -118,24 +147,21 @@ function StdUi:EditBox(parent, width, height, text, validator) self.lastValue = self.value; end end + self.isValidated = false; end; +} - local button = self:Button(editBox, 40, height - 4, OKAY); - button:SetPoint('RIGHT', -2, 0); - button:Hide(); - button.editBox = editBox; - editBox.button = button; - - button:SetScript('OnClick', function(b) - b.editBox:Validate(b.editBox); - end); +local EditBoxButtonOnClick = function(self) + self.editBox:Validate(self.editBox); +end - editBox:SetScript('OnEnterPressed', function(e) - e:Validate(); - end) +local EditBoxEvents = { + OnEnterPressed = function(self) + self:Validate(); + end, - editBox:SetScript('OnTextChanged', function(self, isUserInput) + OnTextChanged = function(self, isUserInput) local value = StdUi.Util.stripColors(self:GetText()); if tostring(value) ~= tostring(self.value) then if not self.isValidated and self.button and isUserInput then @@ -144,62 +170,178 @@ function StdUi:EditBox(parent, width, height, text, validator) else self.button:Hide(); end - end); + end +} + +--- @return EditBox +function StdUi:EditBox(parent, width, height, text, validator) + validator = validator or StdUi.Util.editBoxValidator; + + local editBox = self:SimpleEditBox(parent, width, height, text); + editBox.validator = validator; + + local button = self:Button(editBox, 40, height - 4, OKAY); + button:SetPoint('RIGHT', -2, 0); + button:Hide(); + button.editBox = editBox; + editBox.button = button; + + for k, v in pairs(EditBoxMethods) do + editBox[k] = v; + end + + button:SetScript('OnClick', EditBoxButtonOnClick); + + for k, v in pairs(EditBoxEvents) do + editBox:SetScript(k, v); + end return editBox; end -function StdUi:NumericBox(parent, width, height, text, validator) - validator = validator or self.Util.numericBoxValidator; +---------------------------------------------------- +--- SearchEditBox +---------------------------------------------------- - local editBox = self:EditBox(parent, width, height, text, validator); - editBox:SetNumeric(true); - - function editBox:SetMaxValue(value) +local NumericBoxMethods = { + SetMaxValue = function(self, value) self.maxValue = value; self:Validate(); end; - function editBox:SetMinValue(value) + SetMinValue = function(self, value) self.minValue = value; self:Validate(); end; - function editBox:SetMinMaxValue(min, max) + SetMinMaxValue = function(self, min, max) self.minValue = min; self.maxValue = max; self:Validate(); end +} + +function StdUi:NumericBox(parent, width, height, text, validator) + validator = validator or self.Util.numericBoxValidator; + + local editBox = self:EditBox(parent, width, height, text, validator); + editBox:SetNumeric(true); + + for k, v in pairs(NumericBoxMethods) do + editBox[k] = v; + end return editBox; end -function StdUi:MoneyBox(parent, width, height, text, validator) - validator = validator or self.Util.moneyBoxValidator; - - local editBox = self:EditBox(parent, width, height, text, validator); - editBox:SetMaxLetters(20); +---------------------------------------------------- +--- MoneyBox +---------------------------------------------------- - local formatMoney = StdUi.Util.formatMoney; - function editBox:SetValue(value) +local MoneyBoxMethods = { + SetValue = function(self, value) self.value = value; - local formatted = formatMoney(value); + local formatted = self.stdUi.Util.formatMoney(value); self:SetText(formatted); self:Validate(); self.button:Hide(); end; +}; + +function StdUi:MoneyBox(parent, width, height, text, validator) + validator = validator or self.Util.moneyBoxValidator; + + local editBox = self:EditBox(parent, width, height, text, validator); + editBox.stdUi = self; + editBox:SetMaxLetters(20); + + for k, v in pairs(MoneyBoxMethods) do + editBox[k] = v; + end return editBox; end +---------------------------------------------------- +--- MultiLineBox +---------------------------------------------------- + +local MultiLineBoxMethods = { + SetValue = function(self, value) + self.editBox:SetText(value); + + if self.OnValueChanged then + self:OnValueChanged(value); + end + end, + + GetValue = function(self) + return self.editBox:GetText(); + end, + + SetFont = function(self, font, size, flags) + self.editBox:SetFont(font, size, flags); + end, + + Enable = function(self) + self.editBox:Enable(); + end, + + Disable = function(self) + self.editBox:Disable(); + end, + + SetFocus = function(self) + self.editBox:SetFocus(); + end, + + ClearFocus = function(self) + self.editBox:ClearFocus(); + end, + + HasFocus = function(self) + return self.editBox:HasFocus(); + end +}; + +local MultiLineBoxOnCursorChanged = function(self, _, y, _, cursorHeight) + local sf, newY = self.scrollFrame, -y; + local offset = sf:GetVerticalScroll(); + + if newY < offset then + sf:SetVerticalScroll(newY); + else + newY = newY + cursorHeight - sf:GetHeight() + 6; --text insets + if newY > offset then + sf:SetVerticalScroll(math.ceil(newY)); + end + end +end + +local MultiLineBoxOnTextChanged = function(self) + if self.panel.OnValueChanged then + self.panel.OnValueChanged(self.panel, self:GetText()); + end +end + +local MultiLineBoxScrollOnMouseDown = function(self, button) + self.scrollChild:SetFocus(); +end + +local MultiLineBoxScrollOnVerticalScroll = function(self, offset) + self.scrollChild:SetHitRectInsets(0, 0, offset, self.scrollChild:GetHeight() - offset - self:GetHeight()); +end + function StdUi:MultiLineBox(parent, width, height, text) local editBox = CreateFrame('EditBox'); - local panel, scrollFrame = self:ScrollFrame(parent, width, height, editBox); + local widget = self:ScrollFrame(parent, width, height, editBox); - scrollFrame.target = panel; - editBox.target = panel; + local scrollFrame = widget.scrollFrame; + scrollFrame.editBox = editBox; + widget.editBox = editBox; + editBox.panel = widget; - self:ApplyBackdrop(panel, 'button'); + self:ApplyBackdrop(widget, 'button'); self:HookHoverBorder(scrollFrame); self:HookHoverBorder(editBox); @@ -217,42 +359,27 @@ function StdUi:MultiLineBox(parent, width, height, text) editBox:SetAllPoints(); editBox.scrollFrame = scrollFrame; - editBox.panel = panel; + editBox.panel = widget; + + for k, v in pairs(MultiLineBoxMethods) do + widget[k] = v; + end if text then editBox:SetText(text); end - editBox:SetScript('OnCursorChanged', function(self, _, y, _, cursorHeight) - local sf, y = self.scrollFrame, -y; - local offset = sf:GetVerticalScroll(); + editBox:SetScript('OnCursorChanged', MultiLineBoxOnCursorChanged) + editBox:SetScript('OnTextChanged', MultiLineBoxOnTextChanged); - if y < offset then - sf:SetVerticalScroll(y); - else - y = y + cursorHeight - sf:GetHeight() + 6; --text insets - if y > offset then - sf:SetVerticalScroll(math.ceil(y)); - end - end - end) + scrollFrame:HookScript('OnMouseDown', MultiLineBoxScrollOnMouseDown); + scrollFrame:HookScript('OnVerticalScroll', MultiLineBoxScrollOnVerticalScroll); - editBox:SetScript('OnTextChanged', function(self) - if self.OnValueChanged then - self:OnValueChanged(self:GetText()); - end - end); - scrollFrame:HookScript('OnMouseDown', function(sf, button) - sf.scrollChild:SetFocus(); - end); + widget.SetText = widget.SetValue; + widget.GetText = widget.GetValue; - scrollFrame:HookScript('OnVerticalScroll', function(self, offset) - self.scrollChild:SetHitRectInsets(0, 0, offset, self.scrollChild:GetHeight() - offset - self:GetHeight()); - end); - - - return editBox; + return widget; end StdUi:RegisterModule(module, version); \ No newline at end of file diff --git a/widgets/Label.lua b/widgets/Label.lua index ade9439..14d4d22 100644 --- a/widgets/Label.lua +++ b/widgets/Label.lua @@ -1,28 +1,41 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Label', 2; +local module, version = 'Label', 3; if not StdUi:UpgradeNeeded(module, version) then return end; +---------------------------------------------------- +--- FontString +---------------------------------------------------- + +local FontStringMethods = { + SetFontSize = function(self, newSize) + self:SetFont(self:GetFont(), newSize); + end +} + --- @return FontString function StdUi:FontString(parent, text, inherit) - local this = self; local fs = parent:CreateFontString(nil, self.config.font.strata, inherit or 'GameFontNormal'); fs:SetText(text); fs:SetJustifyH('LEFT'); fs:SetJustifyV('MIDDLE'); - function fs:SetFontSize(newSize) - self:SetFont(self:GetFont(), newSize); + for k, v in pairs(FontStringMethods) do + fs[k] = v; end return fs; end +---------------------------------------------------- +--- Label +---------------------------------------------------- + --- @return FontString function StdUi:Label(parent, text, size, inherit, width, height) local fs = self:FontString(parent, text, inherit); @@ -36,6 +49,10 @@ function StdUi:Label(parent, text, size, inherit, width, height) return fs; end +---------------------------------------------------- +--- Header +---------------------------------------------------- + --- @return FontString function StdUi:Header(parent, text, size, inherit, width, height) local fs = self:Label(parent, text, size, inherit or 'GameFontNormalLarge', width, height); @@ -45,6 +62,10 @@ function StdUi:Header(parent, text, size, inherit, width, height) return fs; end +---------------------------------------------------- +--- AddLabel +---------------------------------------------------- + --- @return FontString function StdUi:AddLabel(parent, object, text, labelPosition, labelWidth) local labelHeight = (self.config.font.size) + 4; diff --git a/widgets/ProgressBar.lua b/widgets/ProgressBar.lua index 9f10b17..5e9cc63 100644 --- a/widgets/ProgressBar.lua +++ b/widgets/ProgressBar.lua @@ -1,12 +1,41 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'ProgressBar', 2; +local module, version = 'ProgressBar', 3; if not StdUi:UpgradeNeeded(module, version) then return end; +---------------------------------------------------- +--- ProgressBar +---------------------------------------------------- + +local ProgressBarMethods = { + GetPercentageValue = function(self) + local _, max = self:GetMinMaxValues(); + local value = self:GetValue(); + return (value/max) * 100; + end, + + TextUpdate = function(self) -- min, max, value + return Round(self:GetPercentageValue()) .. '%'; + end +}; + +local ProgressBarEvents = { + OnValueChanged = function(self, value) + local min, max = self:GetMinMaxValues(); + self.text:SetText(self:TextUpdate(min, max, value)); + end, + + OnMinMaxChanged = function(self) + local min, max = self:GetMinMaxValues(); + local value = self:GetValue(); + self.text:SetText(self:TextUpdate(min, max, value)); + end +} + --- @return StatusBar function StdUi:ProgressBar(parent, width, height, vertical) vertical = vertical or false; @@ -34,27 +63,14 @@ function StdUi:ProgressBar(parent, width, height, vertical) self:ApplyBackdrop(progressBar); - function progressBar:GetPercentageValue() - local min, max = self:GetMinMaxValues(); - local value = self:GetValue(); - return (value/max) * 100; + for k, v in pairs(ProgressBarMethods) do + progressBar[k] = v; end - function progressBar:TextUpdate(min, max, value) - return Round(self:GetPercentageValue()) .. '%'; + for k, v in pairs(ProgressBarEvents) do + progressBar:SetScript(k, v); end - progressBar:SetScript('OnValueChanged', function(self, value) - local min, max = self:GetMinMaxValues(); - self.text:SetText(self:TextUpdate(min, max, value)); - end); - - progressBar:SetScript('OnMinMaxChanged', function(self) - local min, max = self:GetMinMaxValues(); - local value = self:GetValue(); - self.text:SetText(self:TextUpdate(min, max, value)); - end); - return progressBar; end diff --git a/widgets/Scroll.lua b/widgets/Scroll.lua index 9898825..6c5bc4b 100644 --- a/widgets/Scroll.lua +++ b/widgets/Scroll.lua @@ -1,60 +1,44 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Scroll', 3; -if not StdUi:UpgradeNeeded(module, version) then return end; - -StdUi.ScrollBarEvents = { - UpButtonOnClick = function(self) - local scrollBar = self.scrollBar; - local scrollStep = scrollBar.ScrollFrame.scrollStep or (scrollBar.ScrollFrame:GetHeight() / 2); - scrollBar:SetValue(scrollBar:GetValue() - scrollStep); - end, - DownButtonOnClick = function(self) - local scrollBar = self.scrollBar; - local scrollStep = scrollBar.ScrollFrame.scrollStep or (scrollBar.ScrollFrame:GetHeight() / 2); - scrollBar:SetValue(scrollBar:GetValue() + scrollStep); - end, - OnValueChanged = function(self, value) - self.ScrollFrame:SetVerticalScroll(value); - end -}; +local module, version = 'Scroll', 5; +if not StdUi:UpgradeNeeded(module, version) then + return +end -StdUi.ScrollFrameEvents = { - OnLoad = function(self) - local scrollbar = self.ScrollBar; +local round = function(num) + return math.floor(num + .5); +end - scrollbar:SetMinMaxValues(0, 0); - scrollbar:SetValue(0); - self.offset = 0; +---------------------------------------------------- +--- ScrollFrame +---------------------------------------------------- - local scrollDownButton = scrollbar.ScrollDownButton; - local scrollUpButton = scrollbar.ScrollUpButton; +StdUi.ScrollBarEvents = { - scrollDownButton:Disable(); - scrollUpButton:Disable(); + UpDownButtonOnClick = function(self) + local scrollBar = self.scrollBar; + local scrollFrame = scrollBar.scrollFrame; + local scrollStep = scrollBar.scrollStep or (scrollFrame:GetHeight() / 2); - if self.scrollBarHideable then - scrollbar:Hide(); - scrollDownButton:Hide(); - scrollUpButton:Hide(); + if self.direction == 1 then + scrollBar:SetValue(scrollBar:GetValue() - scrollStep); else - scrollDownButton:Disable(); - scrollUpButton:Disable(); - scrollDownButton:Show(); - scrollUpButton:Show(); - end - - if self.noScrollThumb then - scrollbar.ThumbTexture:Hide(); + scrollBar:SetValue(scrollBar:GetValue() + scrollStep); end end, - OnMouseWheel = function(self, value, scrollBar) - scrollBar = scrollBar or self.ScrollBar; + OnValueChanged = function(self, value) + self.scrollFrame:SetVerticalScroll(value); + end +}; + +StdUi.ScrollFrameEvents = { + OnMouseWheel = function(self, value, scrollBar) + scrollBar = scrollBar or self.scrollBar; local scrollStep = scrollBar.scrollStep or scrollBar:GetHeight() / 2; if value > 0 then @@ -64,25 +48,26 @@ StdUi.ScrollFrameEvents = { end end, - OnScrollRangeChanged = function(self, xrange, yrange) - local scrollbar = self.ScrollBar; - if ( not yrange ) then - yrange = self:GetVerticalScrollRange(); + OnScrollRangeChanged = function(self, _, yRange) + -- xRange + local scrollbar = self.scrollBar; + if not yRange then + yRange = self:GetVerticalScrollRange(); end -- Accounting for very small ranges - yrange = math.floor(yrange); + yRange = math.floor(yRange); - local value = math.min(scrollbar:GetValue(), yrange); - scrollbar:SetMinMaxValues(0, yrange); + local value = math.min(scrollbar:GetValue(), yRange); + scrollbar:SetMinMaxValues(0, yRange); scrollbar:SetValue(value); local scrollDownButton = scrollbar.ScrollDownButton; local scrollUpButton = scrollbar.ScrollUpButton; local thumbTexture = scrollbar.ThumbTexture; - if ( yrange == 0 ) then - if ( self.scrollBarHideable ) then + if yRange == 0 then + if self.scrollBarHideable then scrollbar:Hide(); scrollDownButton:Hide(); scrollUpButton:Hide(); @@ -92,7 +77,7 @@ StdUi.ScrollFrameEvents = { scrollUpButton:Disable(); scrollDownButton:Show(); scrollUpButton:Show(); - if ( not self.noScrollThumb ) then + if (not self.noScrollThumb) then thumbTexture:Show(); end end @@ -100,11 +85,11 @@ StdUi.ScrollFrameEvents = { scrollDownButton:Show(); scrollUpButton:Show(); scrollbar:Show(); - if ( not self.noScrollThumb ) then + if not self.noScrollThumb then thumbTexture:Show(); end -- The 0.005 is to account for precision errors - if ( yrange - value > 0.005 ) then + if yRange - value > 0.005 then scrollDownButton:Enable(); else scrollDownButton:Disable(); @@ -112,65 +97,163 @@ StdUi.ScrollFrameEvents = { end end, - OnVerticalScroll = function(self, offset) - local scrollBar = self.ScrollBar; + OnVerticalScroll = function(self, offset) + local scrollBar = self.scrollBar; scrollBar:SetValue(offset); - local min, max = scrollBar:GetMinMaxValues(); + local _, max = scrollBar:GetMinMaxValues(); scrollBar.ScrollUpButton:SetEnabled(offset ~= 0); scrollBar.ScrollDownButton:SetEnabled((scrollBar:GetValue() - max) ~= 0); end } -StdUi.FauxScrollFrameMethods = { - GetChildFrames = function(frame) - local scrollBar = frame.ScrollBar; - local ScrollChildFrame = frame.scrollChild; +StdUi.ScrollFrameMethods = { + SetScrollStep = function(self, scrollStep) + scrollStep = round(scrollStep); + self.scrollBar.scrollStep = scrollStep; + self.scrollBar:SetValueStep(scrollStep); + end, - if not frame.ScrollChildFrame then - frame.ScrollChildFrame = ScrollChildFrame; - end + GetChildFrames = function(self) + return self.scrollBar, self.scrollChild, self.scrollBar.ScrollUpButton, self.scrollBar.ScrollDownButton; + end, - if not frame.ScrollBar then - frame.ScrollBar = scrollBar; + UpdateSize = function(self, newWidth, newHeight) + self:SetSize(newWidth, newHeight); + self.scrollFrame:ClearAllPoints(); + + -- scrollbar width and margins + self.scrollFrame:SetSize(newWidth - self.scrollBarWidth - 5, newHeight - 4); + self.stdUi:GlueAcross(self.scrollFrame, self, 2, -2, -self.scrollBarWidth - 2, 2); + + -- panel of scrollBar + self.scrollBar.panel:SetPoint('TOPRIGHT', self, 'TOPRIGHT', -2, -2); + self.scrollBar.panel:SetPoint('BOTTOMRIGHT', self, 'BOTTOMRIGHT', -2, 2); + + if self.scrollChild then + self.scrollChild:SetWidth(self.scrollFrame:GetWidth()); + self.scrollChild:SetHeight(self.scrollFrame:GetHeight()); end + end +}; - return scrollBar, ScrollChildFrame, scrollBar.ScrollUpButton, scrollBar.ScrollDownButton; - end, +function StdUi:ScrollFrame(parent, width, height, scrollChild) + local panel = self:Panel(parent, width, height); + panel.stdUi = self; + panel.offset = 0; + panel.scrollBarWidth = 16; - GetOffset = function(frame) - return frame.offset or 0; + local scrollFrame = CreateFrame('ScrollFrame', nil, panel); + local scrollBar = self:ScrollBar(panel, panel.scrollBarWidth); + scrollBar:SetMinMaxValues(0, 0); + scrollBar:SetValue(0); + + scrollBar:SetScript('OnValueChanged', self.ScrollBarEvents.OnValueChanged); + scrollBar.ScrollUpButton.direction = 1; + scrollBar.ScrollDownButton.direction = -1; + scrollBar.ScrollDownButton:SetScript('OnClick', self.ScrollBarEvents.UpDownButtonOnClick); + scrollBar.ScrollUpButton:SetScript('OnClick', self.ScrollBarEvents.UpDownButtonOnClick); + scrollBar.ScrollDownButton:Disable(); + scrollBar.ScrollUpButton:Disable(); + + if self.noScrollThumb then + scrollBar.ThumbTexture:Hide(); + end + + scrollBar.scrollFrame = scrollFrame; + scrollFrame.scrollBar = scrollBar; + scrollFrame.panel = panel; + + panel.scrollBar = scrollBar; + panel.scrollFrame = scrollFrame; + + for k, v in pairs(self.ScrollFrameMethods) do + panel[k] = v; + end + + for k, v in pairs(self.ScrollFrameEvents) do + scrollFrame:SetScript(k, v); + end + + if not scrollChild then + scrollChild = CreateFrame('Frame', nil, scrollFrame); + scrollChild:SetWidth(scrollFrame:GetWidth()); + scrollChild:SetHeight(scrollFrame:GetHeight()); + else + scrollChild:SetParent(scrollFrame); + end + panel.scrollChild = scrollChild; + + panel:UpdateSize(width, height); + + scrollFrame:SetScrollChild(scrollChild); + scrollFrame:EnableMouse(true); + scrollFrame:SetClampedToScreen(true); + scrollFrame:SetClipsChildren(true); + + scrollChild:SetPoint('RIGHT', scrollFrame, 'RIGHT', 0, 0); + + scrollFrame.scrollChild = scrollChild; + + return panel; +end + + +---------------------------------------------------- +--- FauxScrollFrame +---------------------------------------------------- + +StdUi.FauxScrollFrameMethods = { + GetOffset = function(self) + return self.offset or 0; end, - OnVerticalScroll = function(self, value, itemHeight, updateFunction) - local scrollBar = self.ScrollBar; + --- Performs vertical scroll + DoVerticalScroll = function(self, value, itemHeight, updateFunction) + local scrollBar = self.scrollBar; itemHeight = itemHeight or self.lineHeight; scrollBar:SetValue(value); self.offset = floor((value / itemHeight) + 0.5); - if (updateFunction) then + + if updateFunction then updateFunction(self); end end, - Update = function(frame, numItems, numToDisplay, buttonHeight) - local scrollBar, scrollChildFrame, scrollUpButton, scrollDownButton = - StdUi.FauxScrollFrameMethods.GetChildFrames(frame); + --- Redraws items in case of manual update from outside without changing parameters + Redraw = function(self) + self:Update( + self.itemCount or #self.scrollChild.items, + self.displayCount, + self.lineHeight + ); + end, + + UpdateItemsCount = function(self, newCount) + self.itemCount = newCount; + self:Update( + newCount, + self.displayCount, + self.lineHeight + ); + end, + + Update = function(self, numItems, numToDisplay, buttonHeight) + local scrollBar, scrollChildFrame, scrollUpButton, scrollDownButton = self:GetChildFrames(); local showScrollBar; - if (numItems > numToDisplay) then - frame:Show(); + if numItems > numToDisplay then showScrollBar = 1; else scrollBar:SetValue(0); - --frame:Hide(); --TODO: Need to rethink it, so far its left commented out because it breaks dropdown end - if (frame:IsShown()) then + if self:IsShown() then local scrollFrameHeight = 0; local scrollChildHeight = 0; - if (numItems > 0) then + if numItems > 0 then scrollFrameHeight = (numItems - numToDisplay) * buttonHeight; scrollChildHeight = numItems * buttonHeight; if (scrollFrameHeight < 0) then @@ -182,23 +265,23 @@ StdUi.FauxScrollFrameMethods = { end local maxRange = (numItems - numToDisplay) * buttonHeight; - if (maxRange < 0) then + if maxRange < 0 then maxRange = 0; end scrollBar:SetMinMaxValues(0, maxRange); - scrollBar:SetValueStep(buttonHeight); + self:SetScrollStep(buttonHeight); scrollBar:SetStepsPerPage(numToDisplay - 1); scrollChildFrame:SetHeight(scrollChildHeight); -- Arrow button handling - if (scrollBar:GetValue() == 0) then + if scrollBar:GetValue() == 0 then scrollUpButton:Disable(); else scrollUpButton:Enable(); end - if ((scrollBar:GetValue() - scrollFrameHeight) == 0) then + if (scrollBar:GetValue() - scrollFrameHeight) == 0 then scrollDownButton:Disable(); else scrollDownButton:Enable(); @@ -207,96 +290,305 @@ StdUi.FauxScrollFrameMethods = { return showScrollBar; end, -} +}; -function StdUi:ScrollFrame(parent, width, height, scrollChild) - local panel = self:Panel(parent, width, height); - local stdUi = self; - local scrollBarWidth = 16; +local OnVerticalScrollUpdate = function(self) + self:Redraw(); +end; - local scrollFrame = CreateFrame('ScrollFrame', nil, panel); - scrollFrame:SetScript('OnScrollRangeChanged', StdUi.ScrollFrameEvents.OnScrollRangeChanged); - scrollFrame:SetScript('OnVerticalScroll', StdUi.ScrollFrameEvents.OnVerticalScroll); - scrollFrame:SetScript('OnMouseWheel', StdUi.ScrollFrameEvents.OnMouseWheel); +StdUi.FauxScrollFrameEvents = { + OnVerticalScroll = function(self, value) + value = round(value); + local panel = self.panel; - local scrollBar = self:ScrollBar(panel, scrollBarWidth); - scrollBar:SetScript('OnValueChanged', StdUi.ScrollBarEvents.OnValueChanged); - scrollBar.ScrollDownButton:SetScript('OnClick', StdUi.ScrollBarEvents.DownButtonOnClick); - scrollBar.ScrollUpButton:SetScript('OnClick', StdUi.ScrollBarEvents.UpButtonOnClick); + panel:DoVerticalScroll( + value, + panel.lineHeight, + OnVerticalScrollUpdate + ); + end +}; - scrollFrame.ScrollBar = scrollBar; - scrollBar.ScrollFrame = scrollFrame; - panel.scrollBar = scrollBar; +--- Works pretty much the same as scroll frame however it does not have smooth scroll and only display a certain amount +--- of items +function StdUi:FauxScrollFrame(parent, width, height, displayCount, lineHeight, scrollChild) + local panel = self:ScrollFrame(parent, width, height, scrollChild); - StdUi.ScrollFrameEvents.OnLoad(scrollFrame); + panel.lineHeight = lineHeight; + panel.displayCount = displayCount; - scrollFrame.panel = panel; - panel.scrollFrame = scrollFrame; + for k, v in pairs(self.FauxScrollFrameMethods) do + panel[k] = v; + end - function panel:UpdateSize(newWidth, newHeight) - self:SetSize(newWidth, newHeight); - self.scrollFrame:ClearAllPoints(); + for k, v in pairs(self.FauxScrollFrameEvents) do + panel.scrollFrame:SetScript(k, v); + end - self.scrollFrame:SetSize(newWidth - scrollBarWidth - 5, newHeight - 4); -- scrollbar width and margins - stdUi:GlueAcross(self.scrollFrame, self, 2, -2, -scrollBarWidth - 2, 2); + return panel; +end - self.scrollBar.panel:SetPoint('TOPRIGHT', self, 'TOPRIGHT', -2, - 2); - self.scrollBar.panel:SetPoint('BOTTOMRIGHT', self, 'BOTTOMRIGHT', -2, 2); +---------------------------------------------------- +--- HybridScrollFrame +---------------------------------------------------- +StdUi.HybridScrollFrameMethods = { + Update = function(self, totalHeight) + local range = floor(totalHeight - self.scrollChild:GetHeight() + 0.5); + + if range > 0 and self.scrollBar then + local _, maxVal = self.scrollBar:GetMinMaxValues(); + + if math.floor(self.scrollBar:GetValue()) >= math.floor(maxVal) then + self.scrollBar:SetMinMaxValues(0, range); + + if range < maxVal then + if math.floor(self.scrollBar:GetValue()) ~= math.floor(range) then + self.scrollBar:SetValue(range); + else + -- If we've scrolled to the bottom, we need to recalculate the offset. + self:SetOffset(self, range); + end + end + else + self.scrollBar:SetMinMaxValues(0, range) + end - if self.scrollChild then - self.scrollChild:SetWidth(self.scrollFrame:GetWidth()); - self.scrollChild:SetHeight(self.scrollFrame:GetHeight()); + self.scrollBar:Enable(); + self:UpdateScrollBarState(); + self.scrollBar:Show(); + elseif self.scrollBar then + self.scrollBar:SetValue(0); + if self.scrollBar.doNotHide then + self.scrollBar:Disable(); + self.scrollBar.ScrollUpButton:Disable(); + self.scrollBar.ScrollDownButton:Disable(); + self.scrollBar.ThumbTexture:Hide(); + else + self.scrollBar:Hide(); + end end - end - if not scrollChild then - scrollChild = CreateFrame('Frame', nil, scrollFrame); - scrollChild:SetWidth(scrollFrame:GetWidth()); - scrollChild:SetHeight(scrollFrame:GetHeight()); - else - scrollChild:SetParent(scrollFrame); - end - panel.scrollChild = scrollChild; + self.range = range; + self.totalHeight = totalHeight; + self.scrollFrame:UpdateScrollChildRect(); + end, - panel:UpdateSize(width, height); + SetData = function(self, data) + self.data = data; + end, - scrollFrame:SetScrollChild(scrollChild); - scrollFrame:EnableMouse(true); - scrollFrame:SetClampedToScreen(true); - scrollFrame:SetClipsChildren(true); + SetUpdateFunction = function(self, updateFn) + self.updateFn = updateFn; + end, - scrollChild:SetPoint('RIGHT', scrollFrame, 'RIGHT', 0, 0); + UpdateScrollBarState = function(self, currValue) + if not currValue then + currValue = self.scrollBar:GetValue(); + end - scrollFrame.scrollChild = scrollChild; + self.scrollBar.ScrollUpButton:Enable(); + self.scrollBar.ScrollDownButton:Enable(); - return panel, scrollFrame, scrollChild, scrollBar; -end + local minVal, maxVal = self.scrollBar:GetMinMaxValues(); + if currValue >= maxVal then + self.scrollBar.ThumbTexture:Show(); + if self.scrollBar.ScrollDownButton then + self.scrollBar.ScrollDownButton:Disable() + end + end ---- Works pretty much the same as scroll frame however it does not have smooth scroll and only display a certain amount ---- of items -function StdUi:FauxScrollFrame(parent, width, height, displayCount, lineHeight, scrollChild) - local this = self; - local panel, scrollFrame, scrollChild, scrollBar = self:ScrollFrame(parent, width, height, scrollChild); + if currValue <= minVal then + self.scrollBar.ThumbTexture:Show(); + if self.scrollBar.ScrollUpButton then + self.scrollBar.ScrollUpButton:Disable(); + end + end + end, + + GetOffset = function(self) + return math.floor(self.offset or 0), (self.offset or 0); + end, - scrollFrame.lineHeight = lineHeight; - scrollFrame.displayCount = displayCount; + SetOffset = function(self, offset) + local items = self.items; + local itemHeight = self.itemHeight; + local element, overflow; - scrollFrame:SetScript('OnVerticalScroll', function(frame, value) - this.FauxScrollFrameMethods.OnVerticalScroll(frame, value, lineHeight, function () - this.FauxScrollFrameMethods.Update(frame, panel.itemCount or #scrollChild.items, displayCount, lineHeight); + local scrollHeight = 0; + + if self.dynamic then + --This is for frames where items will have different heights + if offset < itemHeight then + -- a little optimization + element, scrollHeight = 0, offset; + else + element, scrollHeight = self.dynamic(offset); + end + else + element = offset / itemHeight; + overflow = element - math.floor(element); + scrollHeight = overflow * itemHeight; + end + + if math.floor(self.offset or 0) ~= math.floor(element) and self.updateFn then + self.offset = element; + self:UpdateItems(); + else + self.offset = element; + end + + self.scrollFrame:SetVerticalScroll(scrollHeight); + end, + + CreateItems = function(self, data, create, update, padding, oX, oY) + local scrollChild = self.scrollChild; + local itemHeight = 0; + local numItems = #data; + --initialPoint = initialPoint or 'TOPLEFT'; + --initialRelative = initialRelative or 'TOPLEFT'; + --point = point or 'TOPLEFT'; + --relativePoint = relativePoint or 'BOTTOMLEFT'; + --offsetX = offsetX or 0; + --offsetY = offsetY or 0; + + if not self.items then + self.items = {}; + end + + self.data = data; + self.createFn = create; + self.updateFn = update; + self.itemPadding = padding; + + self.stdUi:ObjectList(scrollChild, self.items, create, update, data, padding, oX, oY, + function (i, totalHeight, lih) + return totalHeight > self:GetHeight() + lih; end); - end); - function panel:Update() - this.FauxScrollFrameMethods.Update(self.scrollFrame, panel.itemCount or #scrollChild.items, displayCount, lineHeight); + if self.items[1] then + itemHeight = round(self.items[1]:GetHeight() + padding); + end + self.itemHeight = itemHeight; + + local totalHeight = numItems * itemHeight; + self.scrollFrame:SetVerticalScroll(0); + + local scrollBar = self.scrollBar; + scrollBar:SetMinMaxValues(0, totalHeight); + scrollBar.itemHeight = itemHeight; + self:SetScrollStep(itemHeight / 2); + + -- one additional item was added above. Need to remove that, + -- and one more to make the current bottom the new top (and vice versa) + scrollBar:SetStepsPerPage(numItems - 2); + scrollBar:SetValue(0); + + self:Update(totalHeight); + end, + + UpdateItems = function(self) + local count = #self.data; + + local offset = self:GetOffset(); + local items = self:GetItems(); + + for i = 1, #items do + local item = items[i]; + + local index = offset + i; + if index <= count then + self.updateFn(self.scrollChild, item, self.data[index], index, i); + end + end + + local firstButton = items[1]; + local totalHeight = 0; + if firstButton then + totalHeight = count * (firstButton:GetHeight() + self.itemPadding); + end + + self:Update(totalHeight, self:GetHeight()); + end, + + GetItems = function(self) + return self.items; + end, + + SetDoNotHideScrollBar = function(self, doNotHide) + if not self.scrollBar or self.scrollBar.doNotHide == doNotHide then + return ; + end + + self.scrollBar.doNotHide = doNotHide; + self:Update(self.totalHeight or 0, self.scrollChild:GetHeight()); + end, + + ScrollToIndex = function(self, index, getHeightFunc) + local totalHeight = 0; + local scrollFrameHeight = self:GetHeight(); + + for i = 1, index do + local entryHeight = getHeightFunc(i); + + if i == index then + local offset = 0; + + -- we don't need to do anything if the entry is fully displayed with the scroll all the way up + if totalHeight + entryHeight > scrollFrameHeight then + if (entryHeight > scrollFrameHeight) then + -- this entry is larger than the entire scrollframe, put it at the top + offset = totalHeight; + else + -- otherwise place it in the center + local diff = scrollFrameHeight - entryHeight; + offset = totalHeight - diff / 2; + end + + -- because of valuestep our positioning might change + -- we'll do the adjustment ourselves to make sure the entry ends up above the center rather than below + local valueStep = self.scrollBar:GetValueStep(); + offset = offset + valueStep - mod(offset, valueStep); + + -- but if we ended up moving the entry so high up that its top is not visible, move it back down + if offset > totalHeight then + offset = offset - valueStep; + end + end + + self.scrollBar:SetValue(offset); + break; + end + + totalHeight = totalHeight + entryHeight; + end end +}; - function panel:UpdateItemsCount(newCount) - self.itemCount = newCount; - this.FauxScrollFrameMethods.Update(self.scrollFrame, newCount, displayCount, lineHeight); +local HybridScrollBarOnValueChanged = function(self, value) + local widget = self.scrollFrame.panel; + value = round(value); + widget:SetOffset(value); + widget:UpdateScrollBarState(value); +end + +function StdUi:HybridScrollFrame(parent, width, height, scrollChild) + local panel = self:ScrollFrame(parent, width, height, scrollChild); + + panel.scrollBar:SetScript('OnValueChanged', HybridScrollBarOnValueChanged); + + panel.scrollFrame:SetScript('OnScrollRangeChanged', nil); + panel.scrollFrame:SetScript('OnVerticalScroll', nil); + + for k, v in pairs(self.HybridScrollFrameMethods) do + panel[k] = v; end - return panel, scrollFrame, scrollChild, scrollBar; + --for k, v in pairs(self.HybridScrollFrameEvents) do + -- panel.scrollFrame:SetScript(k, v); + --end + + + return panel; end StdUi:RegisterModule(module, version); \ No newline at end of file diff --git a/widgets/ScrollTable.lua b/widgets/ScrollTable.lua index 359a378..55b7931 100644 --- a/widgets/ScrollTable.lua +++ b/widgets/ScrollTable.lua @@ -1,13 +1,17 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return ; + return end -local module, version = 'ScrollTable', 4; -if not StdUi:UpgradeNeeded(module, version) then return end; +local module, version = 'ScrollTable', 5; +if not StdUi:UpgradeNeeded(module, version) then + return +end -local lrpadding = 2.5; +local TableInsert = tinsert; +local TableSort = table.sort; +local padding = 2.5; --- Public methods of ScrollTable local methods = { @@ -16,28 +20,27 @@ local methods = { --- Basic Methods ------------------------------------------------------------- - SetAutoHeight = function(self) + SetAutoHeight = function(self) self:SetHeight((self.numberOfRows * self.rowHeight) + 10); self:Refresh(); end, - SetAutoWidth = function(self) + SetAutoWidth = function(self) local width = 13; - for num, col in pairs(self.columns) do + for _, col in pairs(self.columns) do width = width + col.width; end self:SetWidth(width + 20); self:Refresh(); end, - ScrollToLine = function(self, line) + ScrollToLine = function(self, line) line = Clamp(line, 1, #self.filtered - self.numberOfRows + 1); - self.stdUi.FauxScrollFrameMethods.OnVerticalScroll( - self.scrollFrame, + self:DoVerticalScroll( self.rowHeight * (line - 1), - self.rowHeight, function() - self:Refresh(); + self.rowHeight, function(s) + s:Refresh(); end ); end, @@ -48,12 +51,12 @@ local methods = { --- Set the column info for the scrolling table --- @usage st:SetColumns(columns) - SetColumns = function(self, columns) + SetColumns = function(self, columns) local table = self; -- reference saved for closure self.columns = columns; local columnHeadFrame = self.head; - + if not columnHeadFrame then columnHeadFrame = CreateFrame('Frame', nil, self); columnHeadFrame:SetPoint('BOTTOMLEFT', self, 'TOPLEFT', 4, 0); @@ -117,7 +120,7 @@ local methods = { --- Set the number and height of displayed rows --- @usage st:SetDisplayRows(10, 15) - SetDisplayRows = function(self, numberOfRows, rowHeight) + SetDisplayRows = function(self, numberOfRows, rowHeight) local table = self; -- reference saved for closure -- should always set columns first self.numberOfRows = numberOfRows; @@ -172,7 +175,7 @@ local methods = { local rowIndex = table.filtered[i + table.offset]; local rowData = table:GetRow(rowIndex); table:FireCellEvent(event, handler, cellFrame, rowFrame, rowData, columnData, - rowIndex, ...); + rowIndex, ...); end end); end @@ -187,7 +190,7 @@ local methods = { local rowIndex = table.filtered[i + table.offset]; local rowData = table:GetRow(rowIndex); table:FireCellEvent(event, handler, cellFrame, rowFrame, rowData, columnData, - rowIndex, ...); + rowIndex, ...); end end); end @@ -205,11 +208,11 @@ local methods = { cell.text:SetPoint('TOP', cell, 'TOP', 0, 0); cell.text:SetPoint('BOTTOM', cell, 'BOTTOM', 0, 0); - cell.text:SetWidth(self.columns[j].width - 2 * lrpadding); + cell.text:SetWidth(self.columns[j].width - 2 * padding); end - j = #self.columns + 1; - col = rowFrame.columns[j]; + local j = #self.columns + 1; + local col = rowFrame.columns[j]; while col do col:Hide(); j = j + 1; @@ -230,7 +233,7 @@ local methods = { --- Resorts the table using the rules specified in the table column info. --- @usage st:SortData() - SortData = function(self, sortBy) + SortData = function(self, sortBy) -- sanity check if not (self.sortTable) or (#self.sortTable ~= #self.data) then self.sortTable = {}; @@ -254,7 +257,7 @@ local methods = { end if sortBy then - table.sort(self.sortTable, function(rowA, rowB) + TableSort(self.sortTable, function(rowA, rowB) local column = self.columns[sortBy]; if column.compareSort then return column.compareSort(self, rowA, rowB, sortBy); @@ -271,7 +274,7 @@ local methods = { --- CompareSort function used to determine how to sort column values. Can be overridden in column data or table data. --- @usage used internally. - CompareSort = function(self, rowA, rowB, sortBy) + CompareSort = function(self, rowA, rowB, sortBy) local a = self:GetRow(rowA); local b = self:GetRow(rowB); local column = self.columns[sortBy]; @@ -286,20 +289,20 @@ local methods = { end end, - Filter = function(self, rowData) + Filter = function(self, rowData) return true; end, --- Set a display filter for the table. --- @usage st:SetFilter( function (self, ...) return true end ) - SetFilter = function(self, filter, noSort) + SetFilter = function(self, filter, noSort) self.Filter = filter; if not noSort then self:SortData(); end end, - DoFilter = function(self) + DoFilter = function(self) local result = {}; for row = 1, #self.data do @@ -307,7 +310,7 @@ local methods = { local rowData = self:GetRow(realRow); if self:Filter(rowData) then - table.insert(result, realRow); + TableInsert(result, realRow); end end @@ -325,6 +328,7 @@ local methods = { frame.highlight = frame:CreateTexture(nil, 'OVERLAY'); frame.highlight:SetAllPoints(frame); end + if not color then frame.highlight:SetColorTexture(0, 0, 0, 0); else @@ -339,20 +343,20 @@ local methods = { --- Turn on or off selection on a table according to flag. Will not refresh the table display. --- @usage st:EnableSelection(true) - EnableSelection = function(self, flag) + EnableSelection = function(self, flag) self.selectionEnabled = flag; end, --- Clear the currently selected row. You should not need to refresh the table. --- @usage st:ClearSelection() - ClearSelection = function(self) + ClearSelection = function(self) self:SetSelection(nil); end, --- Sets the currently selected row to 'realRow'. RealRow is the unaltered index of the data row in your table. --- You should not need to refresh the table. --- @usage st:SetSelection(12) - SetSelection = function(self, rowIndex) + SetSelection = function(self, rowIndex) self.selected = rowIndex; self:Refresh(); end, @@ -360,14 +364,14 @@ local methods = { --- Gets the currently selected row. --- Return will be the unaltered index of the data row that is selected. --- @usage st:GetSelection() - GetSelection = function(self) + GetSelection = function(self) return self.selected; end, --- Gets the currently selected row. --- Return will be the unaltered index of the data row that is selected. --- @usage st:GetSelection() - GetSelectedItem = function(self) + GetSelectedItem = function(self) return self:GetRow(self.selected); end, @@ -377,20 +381,20 @@ local methods = { --- Sets the data for the scrolling table --- @usage st:SetData(datatable) - SetData = function(self, data) + SetData = function(self, data) self.data = data; self:SortData(); end, --- Returns the data row of the table from the given data row index --- @usage used internally. - GetRow = function(self, rowIndex) + GetRow = function(self, rowIndex) return self.data[rowIndex]; end, --- Returns the cell data of the given row from the given row and column index --- @usage used internally. - GetCell = function(self, row, col) + GetCell = function(self, row, col) local rowData = row; if type(row) == 'number' then rowData = self:GetRow(row); @@ -402,7 +406,7 @@ local methods = { --- Checks if a row is currently being shown --- @usage st:IsRowVisible(realrow) --- @thanks sapu94 - IsRowVisible = function(self, rowIndex) + IsRowVisible = function(self, rowIndex) return (rowIndex > self.offset and rowIndex <= (self.numberOfRows + self.offset)); end, @@ -412,7 +416,7 @@ local methods = { --- Cell update function used to paint each cell. Can be overridden in column data or table data. --- @usage used internally. - DoCellUpdate = function(table, shouldShow, rowFrame, cellFrame, value, columnData, rowData, rowIndex) + DoCellUpdate = function(table, shouldShow, rowFrame, cellFrame, value, columnData, rowData, rowIndex) if shouldShow then local format = columnData.format; @@ -465,11 +469,10 @@ local methods = { end end, - Refresh = function(self) - local scrollFrame = self.scrollFrame; - self.stdUi.FauxScrollFrameMethods.Update(scrollFrame, #self.filtered, self.numberOfRows, self.rowHeight); + Refresh = function(self) + self:Update(#self.filtered, self.numberOfRows, self.rowHeight); - local o = self.stdUi.FauxScrollFrameMethods.GetOffset(scrollFrame); + local o = self:GetOffset(); self.offset = o; for i = 1, self.numberOfRows do @@ -513,9 +516,9 @@ local methods = { --- Private Methods ------------------------------------------------------------- - UpdateSortArrows = function(self, sortBy) + UpdateSortArrows = function(self, sortBy) if not self.head then - return ; + return end for i = 1, #self.columns do @@ -538,7 +541,7 @@ local methods = { end end, - FireCellEvent = function(self, event, handler, ...) + FireCellEvent = function(self, event, handler, ...) if not handler(self, ...) then if self.cellEvents[event] then self.cellEvents[event](self, ...); @@ -546,7 +549,7 @@ local methods = { end end, - FireHeaderEvent = function(self, event, handler, ...) + FireHeaderEvent = function(self, event, handler, ...) if not handler(self, ...) then if self.headerEvents[event] then self.headerEvents[event](self, ...); @@ -556,7 +559,7 @@ local methods = { --- Set the event handlers for various ui events for each cell. --- @usage st:RegisterEvents(events, true) - RegisterEvents = function(self, cellEvents, headerEvents, removeOldEvents) + RegisterEvents = function(self, cellEvents, headerEvents, removeOldEvents) local table = self; -- save for closure later if cellEvents then @@ -579,7 +582,7 @@ local methods = { local rowIndex = table.filtered[i + table.offset]; local rowData = table:GetRow(rowIndex); table:FireCellEvent(event, handler, cellFrame, rowFrame, rowData, columnData, - rowIndex, ...); + rowIndex, ...); end); end @@ -591,7 +594,7 @@ local methods = { local rowIndex = table.filtered[i + table.offset]; local rowData = table:GetRow(rowIndex); table:FireCellEvent(event, handler, cellFrame, rowFrame, rowData, columnData, - rowIndex, ...); + rowIndex, ...); end end); end @@ -605,7 +608,7 @@ local methods = { for columnIndex, columnFrame in ipairs(self.head.columns) do -- unregister old events. if removeOldEvents and self.headerEvents then - for event, handler in pairs(self.headerEvents) do + for event, _ in pairs(self.headerEvents) do columnFrame:SetScript(event, nil); end end @@ -654,9 +657,9 @@ local headerEvents = { local columns = table.columns; local column = columns[columnIndex]; - + -- clear sort for other columns - for i, columnFrame in ipairs(columnHeadFrame.columns) do + for i, _ in ipairs(columnHeadFrame.columns) do if i ~= columnIndex then columns[i].sort = nil; end @@ -679,8 +682,20 @@ local headerEvents = { end }; +local ScrollTableUpdateFn = function(self) + self:Refresh(); +end + +local ScrollTableOnVerticalScroll = function(self, offset) + local scrollTable = self.panel; + -- LS: putting st:Refresh() in a function call passes the st as the 1st arg which lets you + -- reference the st if you decide to hook the refresh + scrollTable:DoVerticalScroll(offset, scrollTable.rowHeight, ScrollTableUpdateFn); +end + function StdUi:ScrollTable(parent, columns, numRows, rowHeight) - local scrollTable, scrollFrame, scrollChild, scrollBar = self:FauxScrollFrame(parent, 100, 100, rowHeight or 15); + local scrollTable = self:FauxScrollFrame(parent, 100, 100, rowHeight or 15); + local scrollFrame = scrollTable.scrollFrame; scrollTable.stdUi = self; scrollTable.numberOfRows = numRows or 12; @@ -695,16 +710,7 @@ function StdUi:ScrollTable(parent, columns, numRows, rowHeight) scrollTable[methodName] = method; end - scrollTable.scrollFrame = scrollFrame; - - scrollFrame:SetScript('OnVerticalScroll', function(self, offset) - -- LS: putting st:Refresh() in a function call passes the st as the 1st arg which lets you - -- reference the st if you decide to hook the refresh - scrollTable.stdUi.FauxScrollFrameMethods.OnVerticalScroll(self, offset, scrollTable.rowHeight, function() - scrollTable:Refresh(); - end); - end); - + scrollFrame:SetScript('OnVerticalScroll', ScrollTableOnVerticalScroll); scrollTable:SortData(); scrollTable:SetColumns(scrollTable.columns); scrollTable:UpdateSortArrows(); diff --git a/widgets/Slider.lua b/widgets/Slider.lua index c54fa50..560e919 100644 --- a/widgets/Slider.lua +++ b/widgets/Slider.lua @@ -1,17 +1,21 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Slider', 4; -if not StdUi:UpgradeNeeded(module, version) then return end; +local module, version = 'Slider', 6; +if not StdUi:UpgradeNeeded(module, version) then return end local function roundPrecision(value, precision) local multiplier = 10 ^ (precision or 0); return math.floor(value * multiplier + 0.5) / multiplier; end +---------------------------------------------------- +--- SliderButton +---------------------------------------------------- + function StdUi:SliderButton(parent, width, height, direction) local button = self:Button(parent, width, height); @@ -28,6 +32,10 @@ function StdUi:SliderButton(parent, width, height, direction) return button; end +---------------------------------------------------- +--- StyleScrollBar +---------------------------------------------------- + --- This is only useful for scrollBars not created using StdUi function StdUi:StyleScrollBar(scrollBar) local buttonUp, buttonDown = scrollBar:GetChildren(); @@ -76,6 +84,40 @@ function StdUi:StyleScrollBar(scrollBar) self:ApplyBackdrop(scrollBar.thumb, 'button'); end +---------------------------------------------------- +--- Slider +---------------------------------------------------- + +local SliderMethods = { + SetPrecision = function(self, numberOfDecimals) + self.precision = numberOfDecimals; + end, + + GetPrecision = function(self) + return self.precision; + end, + + GetValue = function(self) + local minimum, maximum = self:GetMinMaxValues(); + return Clamp(roundPrecision(self:OriginalGetValue(), self.precision), minimum, maximum); + end +}; + +local SliderEvents = { + OnValueChanged = function(self, value, ...) + if self.lock then return end + self.lock = true; + + value = self:GetValue(); + + if self.OnValueChanged then + self:OnValueChanged(value, ...); + end + + self.lock = false; + end +} + function StdUi:Slider(parent, width, height, value, vertical, min, max) local slider = CreateFrame('Slider', nil, parent); self:InitWidget(slider); @@ -111,107 +153,97 @@ function StdUi:Slider(parent, width, height, value, vertical, min, max) slider.ThumbTexture:SetPoint('BOTTOM'); end - function slider:SetPrecision(numberOfDecimals) - self.precision = numberOfDecimals; - end - - function slider:GetPrecision() - return self.precision; - end - slider.OriginalGetValue = slider.GetValue; - function slider:GetValue() - local minimum, maximum = self:GetMinMaxValues(); - return Clamp(roundPrecision(self:OriginalGetValue(), self.precision), minimum, maximum); + for k, v in pairs(SliderMethods) do + slider[k] = v; end slider:SetMinMaxValues(min or 0, max or 100); slider:SetValue(value or min or 0); - slider:HookScript('OnValueChanged', function(s, value, ...) - if s.lock then return; end - s.lock = true; - value = slider:GetValue(); - - if s.OnValueChanged then - s.OnValueChanged(s, value, ...); - end - - s.lock = false; - end); + for k, v in pairs(SliderEvents) do + slider:HookScript(k, v); + end return slider; end -function StdUi:SliderWithBox(parent, width, height, value, min, max) - local widget = CreateFrame('Frame', nil, parent); - self:SetObjSize(widget, width, height); - - widget.slider = self:Slider(widget, 100, 12, value, false); - widget.editBox = self:NumericBox(widget, 80, 16, value); - widget.value = value; - widget.editBox:SetNumeric(false); - widget.leftLabel = self:Label(widget, ''); - widget.rightLabel = self:Label(widget, ''); - - widget.slider.widget = widget; - widget.editBox.widget = widget; +---------------------------------------------------- +--- SliderWithBox +---------------------------------------------------- - function widget:SetValue(value) +local SliderWithBoxMethods = { + SetValue = function(self, v) self.lock = true; - self.slider:SetValue(value); - value = self.slider:GetValue(); - self.editBox:SetValue(value); - self.value = value; + self.slider:SetValue(v); + v = self.slider:GetValue(); + self.editBox:SetValue(v); + self.value = v; self.lock = false; if self.OnValueChanged then - self.OnValueChanged(self, value); + self.OnValueChanged(self, v); end - end + end, - function widget:GetValue() + GetValue = function(self) return self.value; - end + end, - function widget:SetValueStep(step) + SetValueStep = function(self, step) self.slider:SetValueStep(step); - end + end, - function widget:SetPrecision(numberOfDecimals) + SetPrecision = function(self, numberOfDecimals) self.slider.precision = numberOfDecimals; - end + end, - function widget:GetPrecision() + GetPrecision = function(self) return self.slider.precision; + end, + + SetMinMaxValues = function(self, min, max) + self.min = min; + self.max = max; + + self.editBox:SetMinMaxValue(min, max); + self.slider:SetMinMaxValues(min, max); + self.leftLabel:SetText(min); + self.rightLabel:SetText(max); end +}; + +local SliderWithBoxOnValueChanged = function(self, val) + if self.widget.lock then return end; + + self.widget:SetValue(val); +end - function widget:SetMinMaxValues(min, max) - widget.min = min; - widget.max = max; +function StdUi:SliderWithBox(parent, width, height, value, min, max) + local widget = CreateFrame('Frame', nil, parent); + self:SetObjSize(widget, width, height); + + widget.slider = self:Slider(widget, 100, 12, value, false); + widget.editBox = self:NumericBox(widget, 80, 16, value); + widget.value = value; + widget.editBox:SetNumeric(false); + widget.leftLabel = self:Label(widget, ''); + widget.rightLabel = self:Label(widget, ''); - widget.editBox:SetMinMaxValue(min, max); - widget.slider:SetMinMaxValues(min, max); - widget.leftLabel:SetText(min); - widget.rightLabel:SetText(max); + widget.slider.widget = widget; + widget.editBox.widget = widget; + + for k, v in pairs(SliderWithBoxMethods) do + widget[k] = v; end if min and max then widget:SetMinMaxValues(min, max); end - widget.slider.OnValueChanged = function(s, val) - if s.widget.lock then return end; - - s.widget:SetValue(val); - end; - - widget.editBox.OnValueChanged = function(e, val) - if e.widget.lock then return end; - - e.widget:SetValue(val); - end; + widget.slider.OnValueChanged = SliderWithBoxOnValueChanged; + widget.editBox.OnValueChanged = SliderWithBoxOnValueChanged; widget.slider:SetPoint('TOPLEFT', widget, 'TOPLEFT', 0, 0); widget.slider:SetPoint('TOPRIGHT', widget, 'TOPRIGHT', 0, 0); @@ -222,8 +254,11 @@ function StdUi:SliderWithBox(parent, width, height, value, min, max) return widget; end -function StdUi:ScrollBar(parent, width, height, horizontal) +---------------------------------------------------- +--- ScrollBar +---------------------------------------------------- +function StdUi:ScrollBar(parent, width, height, horizontal) local panel = self:Panel(parent, width, height); local scrollBar = self:Slider(parent, width, height, 0, not horizontal); diff --git a/widgets/Spell.lua b/widgets/Spell.lua index 15969e3..cdd86ef 100644 --- a/widgets/Spell.lua +++ b/widgets/Spell.lua @@ -1,13 +1,33 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return ; + return end -local module, version = 'Spell', 1; +local module, version = 'Spell', 2; if not StdUi:UpgradeNeeded(module, version) then return -end ; +end + +---------------------------------------------------- +--- SpellBox +---------------------------------------------------- + +local SpellBoxEvents = { + OnEnter = function(self) + if self.editBox.value then + GameTooltip:SetOwner(self.editBox); + GameTooltip:SetSpellByID(self.editBox.value); + GameTooltip:Show(); + end + end, + + OnLeave = function(self) + if self.editBox.value then + GameTooltip:Hide(); + end + end +}; function StdUi:SpellBox(parent, width, height, iconSize, spellValidator) iconSize = iconSize or 16; @@ -21,24 +41,41 @@ function StdUi:SpellBox(parent, width, height, iconSize, spellValidator) icon:SetAllPoints(); editBox.icon = icon; + iconFrame.editBox = editBox; - iconFrame:SetScript('OnEnter', function() - if editBox.value then - GameTooltip:SetOwner(editBox); - GameTooltip:SetSpellByID(editBox.value) - GameTooltip:Show(); - end - end) - - iconFrame:SetScript('OnLeave', function() - if editBox.value then - GameTooltip:Hide(); - end - end) + for k, v in pairs(SpellBoxEvents) do + iconFrame:SetScript(k, v); + end return editBox; end +---------------------------------------------------- +--- SpellInfo +---------------------------------------------------- +local SpellInfoMethods = { + SetSpell = function(self, nameOrId) + local name, _, i, _, _, _, spellId = GetSpellInfo(nameOrId); + self.spellId = spellId; + self.spellName = name; + + self.icon:SetTexture(i); + self.text:SetText(name); + end +}; + +local SpellInfoEvents = { + OnEnter = function(self) + GameTooltip:SetOwner(self.widget); + GameTooltip:SetSpellByID(self.widget.spellId); + GameTooltip:Show(); + end, + + OnLeave = function() + GameTooltip:Hide(); + end +}; + function StdUi:SpellInfo(parent, width, height, iconSize) iconSize = iconSize or 16; local frame = self:Panel(parent, width, height); @@ -62,17 +99,25 @@ function StdUi:SpellInfo(parent, width, height, iconSize) btn.parent = frame; - iconFrame:SetScript('OnEnter', function() - GameTooltip:SetOwner(frame); - GameTooltip:SetSpellByID(frame.spellId); - GameTooltip:Show(); - end) + iconFrame.widget = frame; - iconFrame:SetScript('OnLeave', function() - GameTooltip:Hide(); - end) + for k, v in pairs(SpellInfoMethods) do + frame[k] = v; + end + + for k, v in pairs(SpellInfoEvents) do + iconFrame:SetScript(k, v); + end + + return frame; +end; - function frame:SetSpell(nameOrId) +---------------------------------------------------- +--- SpellCheckbox +---------------------------------------------------- + +local SpellCheckboxMethods = { + SetSpell = function(self, nameOrId) local name, _, i, _, _, _, spellId = GetSpellInfo(nameOrId); self.spellId = spellId; self.spellName = name; @@ -80,9 +125,23 @@ function StdUi:SpellInfo(parent, width, height, iconSize) self.icon:SetTexture(i); self.text:SetText(name); end +}; - return frame; -end; +local SpellCheckboxEvents = { + OnEnter = function(self) + if self.spellId then + GameTooltip:SetOwner(self); + GameTooltip:SetSpellByID(self.spellId); + GameTooltip:Show(); + end + end, + + OnLeave = function(self) + if self.spellId then + GameTooltip:Hide(); + end + end +}; function StdUi:SpellCheckbox(parent, width, height, iconSize) iconSize = iconSize or 16; @@ -100,27 +159,12 @@ function StdUi:SpellCheckbox(parent, width, height, iconSize) checkbox.text:SetPoint('LEFT', iconFrame, 'RIGHT', 5, 0); - checkbox:SetScript('OnEnter', function() - if checkbox.spellId then - GameTooltip:SetOwner(checkbox); - GameTooltip:SetSpellByID(checkbox.spellId); - GameTooltip:Show(); - end - end) - - checkbox:SetScript('OnLeave', function() - if checkbox.spellId then - GameTooltip:Hide(); - end - end) - - function checkbox:SetSpell(nameOrId) - local name, _, i, _, _, _, spellId = GetSpellInfo(nameOrId); - self.spellId = spellId; - self.spellName = name; + for k, v in pairs(SpellCheckboxMethods) do + checkbox[k] = v; + end - self.icon:SetTexture(i); - self.text:SetText(name); + for k, v in pairs(SpellCheckboxEvents) do + checkbox:SetScript(k, v); end return checkbox; diff --git a/widgets/Tab.lua b/widgets/Tab.lua index ceeb1fc..aa119b1 100644 --- a/widgets/Tab.lua +++ b/widgets/Tab.lua @@ -1,96 +1,56 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return ; + return end -local module, version = 'Tab', 3; +local module, version = 'Tab', 4; if not StdUi:UpgradeNeeded(module, version) then return -end ; - ---- ----local t = { ---- { ---- name = 'firstTab', ---- title = 'First', ---- }, ---- { ---- name = 'secondTab', ---- title = 'Second', ---- }, ---- { ---- name = 'thirdTab', ---- title = 'Third' ---- } ----} -function StdUi:TabPanel(parent, width, height, tabs, vertical, buttonWidth, buttonHeight) - local this = self; - vertical = vertical or false; - buttonHeight = buttonHeight or 20; - buttonWidth = buttonWidth or 160; - - local tabFrame = self:Frame(parent, width, height); - tabFrame.vertical = vertical; - - tabFrame.tabs = tabs; - - tabFrame.buttonContainer = self:Frame(tabFrame); - tabFrame.container = self:Panel(tabFrame); - - if vertical then - tabFrame.buttonContainer:SetPoint('TOPLEFT', tabFrame, 'TOPLEFT', 0, 0); - tabFrame.buttonContainer:SetPoint('BOTTOMLEFT', tabFrame, 'BOTTOMLEFT', 0, 0); - tabFrame.buttonContainer:SetWidth(buttonWidth); +end - tabFrame.container:SetPoint('TOPLEFT', tabFrame.buttonContainer, 'TOPRIGHT', 5, 0); - tabFrame.container:SetPoint('BOTTOMLEFT', tabFrame.buttonContainer, 'BOTTOMRIGHT', 5, 0); - tabFrame.container:SetPoint('TOPRIGHT', tabFrame, 'TOPRIGHT', 0, 0); - tabFrame.container:SetPoint('BOTTOMRIGHT', tabFrame, 'BOTTOMRIGHT', 0, 0); - else - tabFrame.buttonContainer:SetPoint('TOPLEFT', tabFrame, 'TOPLEFT', 0, 0); - tabFrame.buttonContainer:SetPoint('TOPRIGHT', tabFrame, 'TOPRIGHT', 0, 0); - tabFrame.buttonContainer:SetHeight(buttonHeight); +---------------------------------------------------- +--- TabPanel +---------------------------------------------------- - tabFrame.container:SetPoint('TOPLEFT', tabFrame.buttonContainer, 'BOTTOMLEFT', 0, -5); - tabFrame.container:SetPoint('TOPRIGHT', tabFrame.buttonContainer, 'BOTTOMRIGHT', 0, -5); - tabFrame.container:SetPoint('BOTTOMLEFT', tabFrame, 'BOTTOMLEFT', 0, 0); - tabFrame.container:SetPoint('BOTTOMRIGHT', tabFrame, 'BOTTOMRIGHT', 0, 0); - end +local TabPanelMethods = { + --- Runs callback thru all tabs, if callback returns truthy value, enumeration stops and function returns result + EnumerateTabs = function(self, callback, ...) + local result; - function tabFrame:EnumerateTabs(callback) for i = 1, #self.tabs do local tab = self.tabs[i]; - if callback(tab, self) then - break ; + result = callback(tab, self, i, ...); + if result then + break end end - end - function tabFrame:HideAllFrames() - self:EnumerateTabs(function(tab) + return result; + end, + + HideAllFrames = function(self) + for _, tab in pairs(self.tabs) do if tab.frame then tab.frame:Hide(); end - end); - end + end + end, - function tabFrame:DrawButtons() - self:EnumerateTabs(function(tab) + DrawButtons = function(self) + local prevBtn; + for _, tab in pairs(self.tabs) do if tab.button then tab.button:Hide(); end - end); - local prevBtn; - self:EnumerateTabs(function(tab, parentTabFrame) local btn = tab.button; - local btnContainer = parentTabFrame.buttonContainer; + local btnContainer = self.buttonContainer; if not btn then - btn = this:Button(btnContainer, nil, buttonHeight); + btn = self.stdUi:Button(btnContainer, nil, self.buttonHeight); tab.button = btn; - btn.tabFrame = parentTabFrame; + btn.tabFrame = self; btn:SetScript('OnClick', function(bt) bt.tabFrame:SelectTab(bt.tab.name); @@ -101,63 +61,72 @@ function StdUi:TabPanel(parent, width, height, tabs, vertical, buttonWidth, butt btn:SetText(tab.title); btn:ClearAllPoints(); - if parentTabFrame.vertical then - btn:SetWidth(buttonWidth); + if self.vertical then + btn:SetWidth(self.buttonWidth); else - this:ButtonAutoWidth(btn); + self.stdUi:ButtonAutoWidth(btn); end - if parentTabFrame.vertical then + if self.vertical then if not prevBtn then - this:GlueTop(btn, btnContainer, 0, 0, 'CENTER'); + self.stdUi:GlueTop(btn, btnContainer, 0, 0, 'CENTER'); else - this:GlueBelow(btn, prevBtn, 0, -1); + self.stdUi:GlueBelow(btn, prevBtn, 0, -1); end else if not prevBtn then - this:GlueTop(btn, btnContainer, 0, 0, 'LEFT'); + self.stdUi:GlueTop(btn, btnContainer, 0, 0, 'LEFT'); else - this:GlueRight(btn, prevBtn, 5, 0); + self.stdUi:GlueRight(btn, prevBtn, 5, 0); end end btn:Show(); prevBtn = btn; - end); - end + end + end, - function tabFrame:DrawFrames() - self:EnumerateTabs(function(tab) + DrawFrames = function(self) + for _, tab in pairs(self.tabs) do if not tab.frame then - tab.frame = this:Frame(self.container); + tab.frame = self.stdUi:Frame(self.container); end tab.frame:ClearAllPoints(); tab.frame:SetAllPoints(); - end); - end - function tabFrame:Update(newTabs) + if tab.layout then + self.stdUi:BuildWindow(tab.frame, tab.layout); + self.stdUi:EasyLayout(tab.frame, { padding = { top = 10 } }); + + tab.frame:SetScript('OnShow', function(of) + of:DoLayout(); + end); + end + + if tab.onHide then + tab.frame:SetScript('OnHide', tab.onHide); + end + end + end, + + Update = function(self, newTabs) if newTabs then self.tabs = newTabs; end self:DrawButtons(); self:DrawFrames(); - end - - function tabFrame:GetTabByName(name) - local foundTab; + end, - self:EnumerateTabs(function(tab) + GetTabByName = function(self, name) + for _, tab in pairs(self.tabs) do if tab.name == name then - foundTab = tab; - return true; + return tab; end - end); - return foundTab; - end + end + end, - function tabFrame:SelectTab(name) + SelectTab = function(self, name) self.selected = name; if self.selectedTab then self.selectedTab.button:Enable(); @@ -169,13 +138,78 @@ function StdUi:TabPanel(parent, width, height, tabs, vertical, buttonWidth, butt if foundTab.name == name and foundTab.frame then foundTab.button:Disable(); foundTab.frame:Show(); - tabFrame.selectedTab = foundTab; + self.selectedTab = foundTab; return true; end - end + end, - function tabFrame:GetSelectedTab() + GetSelectedTab = function(self) return self.selectedTab; + end, + + DoLayout = function(self) + -- redoing layout as container + local tab = self:GetSelectedTab(); + if tab then + if tab.frame and tab.frame.DoLayout then + tab.frame:DoLayout(); + end + end + end +}; + +--- +---local t = { +--- { +--- name = 'firstTab', +--- title = 'First', +--- }, +--- { +--- name = 'secondTab', +--- title = 'Second', +--- }, +--- { +--- name = 'thirdTab', +--- title = 'Third' +--- } +---} +function StdUi:TabPanel(parent, width, height, tabs, vertical, buttonWidth, buttonHeight) + vertical = vertical or false; + buttonWidth = buttonWidth or 160; + buttonHeight = buttonHeight or 20; + + local tabFrame = self:Frame(parent, width, height); + tabFrame.stdUi = self; + tabFrame.tabs = tabs; + tabFrame.vertical = vertical; + tabFrame.buttonWidth = buttonWidth; + tabFrame.buttonHeight = buttonHeight; + + tabFrame.buttonContainer = self:Frame(tabFrame); + tabFrame.container = self:Panel(tabFrame); + + if vertical then + tabFrame.buttonContainer:SetPoint('TOPLEFT', tabFrame, 'TOPLEFT', 0, 0); + tabFrame.buttonContainer:SetPoint('BOTTOMLEFT', tabFrame, 'BOTTOMLEFT', 0, 0); + tabFrame.buttonContainer:SetWidth(buttonWidth); + + tabFrame.container:SetPoint('TOPLEFT', tabFrame.buttonContainer, 'TOPRIGHT', 5, 0); + tabFrame.container:SetPoint('BOTTOMLEFT', tabFrame.buttonContainer, 'BOTTOMRIGHT', 5, 0); + tabFrame.container:SetPoint('TOPRIGHT', tabFrame, 'TOPRIGHT', 0, 0); + tabFrame.container:SetPoint('BOTTOMRIGHT', tabFrame, 'BOTTOMRIGHT', 0, 0); + else + tabFrame.buttonContainer:SetPoint('TOPLEFT', tabFrame, 'TOPLEFT', 0, 0); + tabFrame.buttonContainer:SetPoint('TOPRIGHT', tabFrame, 'TOPRIGHT', 0, 0); + tabFrame.buttonContainer:SetHeight(buttonHeight); + + tabFrame.container:SetPoint('TOPLEFT', tabFrame.buttonContainer, 'BOTTOMLEFT', 0, -5); + tabFrame.container:SetPoint('TOPRIGHT', tabFrame.buttonContainer, 'BOTTOMRIGHT', 0, -5); + tabFrame.container:SetPoint('BOTTOMLEFT', tabFrame, 'BOTTOMLEFT', 0, 0); + tabFrame.container:SetPoint('BOTTOMRIGHT', tabFrame, 'BOTTOMRIGHT', 0, 0); + end + + for k, v in pairs(TabPanelMethods) do + tabFrame[k] = v; end tabFrame:Update(); diff --git a/widgets/Table.lua b/widgets/Table.lua index a68e9b7..f5c7c52 100644 --- a/widgets/Table.lua +++ b/widgets/Table.lua @@ -1,45 +1,50 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Table', 1; -if not StdUi:UpgradeNeeded(module, version) then return end; +local module, version = 'Table', 2; +if not StdUi:UpgradeNeeded(module, version) then + return +end + +local TableInsert = tinsert; +local StringLength = strlen; + +---------------------------------------------------- +--- Table +---------------------------------------------------- --- Draws table in a panel according to data, example: --- local columns = { ---- {header = 'Name', index = 'name', width = 20, align = 'RIGHT'}, ---- {header = 'Price', index = 'price', width = 60}, +--- {header = 'Name', index = 'name', width = 20, align = 'RIGHT'}, +--- {header = 'Price', index = 'price', width = 60}, --- }; --- local data { ---- {name = 'Item one', price = 12.22}, ---- {name = 'Item two', price = 11.11}, ---- {name = 'Item three', price = 10.12}, +--- {name = 'Item one', price = 12.22}, +--- {name = 'Item two', price = 11.11}, +--- {name = 'Item three', price = 10.12}, --- } ---- -function StdUi:Table(parent, width, height, rowHeight, columns, data) - local this = self; - local panel = self:Panel(parent, width, height); - panel.rowHeight = rowHeight; - function panel:SetColumns(columns) - panel.columns = columns; - end +local TableMethods = { + SetColumns = function(self, columns) + self.columns = columns; + end, - function panel:SetData(data) + SetData = function(self, data) self.tableData = data; - end + end, - function panel:AddRow(row) + AddRow = function(self, row) if not self.tableData then self.tableData = {}; end - tinsert(self.tableData, row); - end + TableInsert(self.tableData, row); + end, - function panel:DrawHeaders() + DrawHeaders = function(self) if not self.headers then self.headers = {}; end @@ -48,10 +53,10 @@ function StdUi:Table(parent, width, height, rowHeight, columns, data) for i = 1, #self.columns do local col = self.columns[i]; - if col.header and strlen(col.header) > 0 then + if col.header and StringLength(col.header) > 0 then if not self.headers[i] then self.headers[i] = { - text = this:FontString(self, ''), + text = self.stdUi:FontString(self, ''), }; end @@ -59,27 +64,27 @@ function StdUi:Table(parent, width, height, rowHeight, columns, data) column.text:SetText(col.header); column.text:SetWidth(col.width); - column.text:SetHeight(rowHeight); + column.text:SetHeight(self.rowHeight); column.text:ClearAllPoints(); if col.align then column.text:SetJustifyH(col.align); end - this:GlueTop(column.text, self, marginLeft, 0, 'LEFT'); + self.stdUi:GlueTop(column.text, self, marginLeft, 0, 'LEFT'); marginLeft = marginLeft + col.width; column.index = col.index column.width = col.width end end - end + end, - function panel:DrawData() + DrawData = function(self) if not self.rows then self.rows = {}; end - local marginTop = -rowHeight; + local marginTop = -self.rowHeight; for y = 1, #self.tableData do local row = self.tableData[y]; @@ -93,7 +98,7 @@ function StdUi:Table(parent, width, height, rowHeight, columns, data) if not self.rows[y][x] then self.rows[y][x] = { - text = this:FontString(self, ''); + text = self.stdUi:FontString(self, ''); }; end @@ -101,24 +106,34 @@ function StdUi:Table(parent, width, height, rowHeight, columns, data) cell.text:SetText(row[col.index]); cell.text:SetWidth(col.width); - cell.text:SetHeight(rowHeight); + cell.text:SetHeight(self.rowHeight); cell.text:ClearAllPoints(); if col.align then cell.text:SetJustifyH(col.align); end - this:GlueTop(cell.text, self, marginLeft, marginTop, 'LEFT'); + self.stdUi:GlueTop(cell.text, self, marginLeft, marginTop, 'LEFT'); marginLeft = marginLeft + col.width; end - marginTop = marginTop - rowHeight; + marginTop = marginTop - self.rowHeight; end - end + end, - function panel:DrawTable() + DrawTable = function(self) self:DrawHeaders(); self:DrawData(); end +}; + +function StdUi:Table(parent, width, height, rowHeight, columns, data) + local panel = self:Panel(parent, width, height); + panel.stdUi = self; + panel.rowHeight = rowHeight; + + for k, v in pairs(TableMethods) do + panel[k] = v; + end panel:SetColumns(columns); panel:SetData(data); diff --git a/widgets/Tooltip.lua b/widgets/Tooltip.lua index 44c30c2..894bbab 100644 --- a/widgets/Tooltip.lua +++ b/widgets/Tooltip.lua @@ -1,119 +1,161 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Tooltip', 2; -if not StdUi:UpgradeNeeded(module, version) then return end; +local module, version = 'Tooltip', 3; +if not StdUi:UpgradeNeeded(module, version) then + return +end + +StdUi.tooltips = {}; +StdUi.frameTooltips = {}; + +---------------------------------------------------- +--- Tooltip +---------------------------------------------------- + +local TooltipEvents = { + OnEnter = function(self) + local tip = self.stdUiTooltip; + tip:SetOwner(tip.owner or UIParent, tip.anchor or 'ANCHOR_NONE'); + + if type(tip.text) == 'string' then + tip:SetText(tip.text, + tip.stdUi.config.font.color.r, + tip.stdUi.config.font.color.g, + tip.stdUi.config.font.color.b, + tip.stdUi.config.font.color.a + ); + elseif type(tip.text) == 'function' then + tip.text(tip); + end + + tip:Show(); + tip:ClearAllPoints(); + tip.stdUi:GlueOpposite(tip, tip.owner, 0, 0, tip.anchor); + end, -StdUi.tooltips = {} -StdUi.frameTooltips = {} + OnLeave = function(self) + local tip = self.stdUiTooltip; + tip:Hide(); + end +} --- Standard blizzard tooltip ---@return GameTooltip function StdUi:Tooltip(owner, text, tooltipName, anchor, automatic) --- @type GameTooltip local tip; - local this = self; if tooltipName and self.tooltips[tooltipName] then tip = self.tooltips[tooltipName]; else tip = CreateFrame('GameTooltip', tooltipName, UIParent, 'GameTooltipTemplate'); - self:ApplyBackdrop(tip, 'panel'); end tip.owner = owner; tip.anchor = anchor; + tip.text = text; + tip.stdUi = self; + owner.stdUiTooltip = tip; if automatic then - owner:HookScript('OnEnter', function (self) - tip:SetOwner(owner or UIParent, anchor or 'ANCHOR_NONE'); - - if type(text) == 'string' then - tip:SetText(text, - this.config.font.color.r, - this.config.font.color.g, - this.config.font.color.b, - this.config.font.color.a - ); - elseif type(text) == 'function' then - text(tip); - end - - tip:Show(); - tip:ClearAllPoints(); - this:GlueOpposite(tip, tip.owner, 0, 0, tip.anchor); - end); - - owner:HookScript('OnLeave', function () - tip:Hide(); - end); + for k, v in pairs(TooltipEvents) do + owner:HookScript(k, v); + end end return tip; end +---------------------------------------------------- +--- Tooltip +---------------------------------------------------- + +local FrameTooltipMethods = { + SetText = function(self, text, r, g, b) + if r and g and b then + text = self.stdUi.Util.WrapTextInColor(text, r, g, b, 1); + end + self.text:SetText(text); + + self:RecalculateSize(); + end, + + GetText = function(self) + return self.text:GetText(); + end, + + AddLine = function(self, text, r, g, b) + local txt = self:GetText(); + if not txt then + txt = ''; + else + txt = txt .. '\n' + end + if r and g and b then + text = self.stdUi.Util.WrapTextInColor(text, r, g, b, 1); + end + self:SetText(txt .. text); + end, + + RecalculateSize = function(self) + self:SetSize( + self.text:GetWidth() + self.padding * 2, + self.text:GetHeight() + self.padding * 2 + ); + end +}; + +local OnShowFrameTooltip = function(self) + self:RecalculateSize(); + self:ClearAllPoints(); + self.stdUi:GlueOpposite(self, self.owner, 0, 0, self.anchor); +end + +local FrameTooltipEvents = { + OnEnter = function(self) + self.stdUiTooltip:Show(); + end, + + OnLeave = function(self) + self.stdUiTooltip:Hide(); + end, +}; + function StdUi:FrameTooltip(owner, text, tooltipName, anchor, automatic, manualPosition) local tip; - local this = self; if tooltipName and self.frameTooltips[tooltipName] then tip = self.frameTooltips[tooltipName]; else tip = self:Panel(owner, 10, 10); + tip.stdUi = self; tip:SetFrameStrata('TOOLTIP'); self:ApplyBackdrop(tip, 'panel'); - local padding = self.config.tooltip.padding; + tip.padding = self.config.tooltip.padding; tip.text = self:FontString(tip, ''); - self:GlueTop(tip.text, tip, padding, -padding, 'LEFT'); - - function tip:SetText(text, r, g, b) - if r and g and b then - text = this.Util.WrapTextInColor(text, r, g, b, 1); - end - tip.text:SetText(text); - - tip:RecalculateSize(); - end - - function tip:GetText() - return tip.text:GetText(); - end + self:GlueTop(tip.text, tip, tip.padding, -tip.padding, 'LEFT'); - function tip:AddLine(text, r, g, b) - local txt = self:GetText(); - if not txt then - txt = ''; - else - txt = txt .. '\n' - end - if r and g and b then - text = this.Util.WrapTextInColor(text, r, g, b, 1); - end - self:SetText(txt .. text); + for k, v in pairs(FrameTooltipMethods) do + tip[k] = v; end - function tip:RecalculateSize() - tip:SetSize(tip.text:GetWidth() + padding * 2, tip.text:GetHeight() + padding * 2); - end - - if manualPosition then - hooksecurefunc(tip, 'Show', function(self) - self:RecalculateSize(); - self:ClearAllPoints(); - this:GlueOpposite(self, self.owner, 0, 0, self.anchor); - end); + if not manualPosition then + hooksecurefunc(tip, 'Show', OnShowFrameTooltip); end end tip.owner = owner; tip.anchor = anchor; + owner.stdUiTooltip = tip; + if type(text) == 'string' then tip:SetText(text); elseif type(text) == 'function' then @@ -121,12 +163,9 @@ function StdUi:FrameTooltip(owner, text, tooltipName, anchor, automatic, manualP end if automatic then - owner:HookScript('OnEnter', function () - tip:Show(); - end); - owner:HookScript('OnLeave', function () - tip:Hide(); - end); + for k, v in pairs(FrameTooltipEvents) do + owner:HookScript(k, v); + end end return tip; diff --git a/widgets/Window.lua b/widgets/Window.lua index f22b2c1..6a04500 100644 --- a/widgets/Window.lua +++ b/widgets/Window.lua @@ -1,14 +1,14 @@ --- @type StdUi local StdUi = LibStub and LibStub('StdUi', true); if not StdUi then - return; + return end -local module, version = 'Window', 4; +local module, version = 'Window', 5; if not StdUi:UpgradeNeeded(module, version) then return end; --- @return Frame -function StdUi:Window(parent, title, width, height) +function StdUi:Window(parent, width, height, title) parent = parent or UIParent; local frame = self:PanelWithTitle(parent, width, height, title); frame:SetClampedToScreen(true); @@ -26,8 +26,8 @@ function StdUi:Window(parent, title, width, height) frame.closeBtn = closeBtn; - function frame:SetWindowTitle(title) - self.titlePanel.label:SetText(title); + function frame:SetWindowTitle(t) + self.titlePanel.label:SetText(t); end return frame; @@ -41,7 +41,7 @@ function StdUi:Dialog(title, message, dialogId) if dialogId and self.dialogs[dialogId] then window = self.dialogs[dialogId]; else - window = self:Window(nil, title, self.config.dialog.width, self.config.dialog.height); + window = self:Window(nil, self.config.dialog.width, self.config.dialog.height, title); window:SetPoint('CENTER'); window:SetFrameStrata('DIALOG'); end @@ -59,6 +59,7 @@ function StdUi:Dialog(title, message, dialogId) if dialogId then self.dialogs[dialogId] = window; end + return window; end