forked from Bastion/Bastion
parent
98d46b6b42
commit
53679d748f
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@ |
|||||||
|
.idea |
||||||
|
tests |
@ -0,0 +1,297 @@ |
|||||||
|
local MAJOR, MINOR = 'StdUi', 5; |
||||||
|
--- @class StdUi |
||||||
|
local StdUi = LibStub:NewLibrary(MAJOR, MINOR); |
||||||
|
|
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local TableInsert = tinsert; |
||||||
|
|
||||||
|
StdUi.moduleVersions = {}; |
||||||
|
if not StdUiInstances then |
||||||
|
StdUiInstances = {StdUi}; |
||||||
|
else |
||||||
|
TableInsert(StdUiInstances, StdUi); |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:NewInstance() |
||||||
|
local instance = CopyTable(self); |
||||||
|
instance:ResetConfig(); |
||||||
|
TableInsert(StdUiInstances, instance); |
||||||
|
return instance; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:RegisterModule(module, version) |
||||||
|
self.moduleVersions[module] = version; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:UpgradeNeeded(module, version) |
||||||
|
if not self.moduleVersions[module] then |
||||||
|
return true; |
||||||
|
end |
||||||
|
|
||||||
|
return self.moduleVersions[module] < version; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:RegisterWidget(name, func) |
||||||
|
if not self[name] then |
||||||
|
self[name] = func; |
||||||
|
return true; |
||||||
|
end |
||||||
|
|
||||||
|
return false; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:InitWidget(widget) |
||||||
|
widget.isWidget = true; |
||||||
|
|
||||||
|
function widget:GetChildrenWidgets() |
||||||
|
local children = {widget:GetChildren()}; |
||||||
|
local result = {}; |
||||||
|
for i = 1, #children do |
||||||
|
local child = children[i]; |
||||||
|
if child.isWidget then |
||||||
|
TableInsert(result, child); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return result; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:SetObjSize(obj, width, height) |
||||||
|
if width then |
||||||
|
obj:SetWidth(width); |
||||||
|
end |
||||||
|
|
||||||
|
if height then |
||||||
|
obj:SetHeight(height); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:SetTextColor(fontString, colorType) |
||||||
|
colorType = colorType or 'normal'; |
||||||
|
if fontString.SetTextColor then |
||||||
|
local c = self.config.font.color[colorType]; |
||||||
|
fontString:SetTextColor(c.r, c.g, c.b, c.a); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
StdUi.SetHighlightBorder = function(self) |
||||||
|
if self.target then |
||||||
|
self = self.target; |
||||||
|
end |
||||||
|
|
||||||
|
if self.isDisabled then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local hc = self.stdUi.config.highlight.color; |
||||||
|
if not self.origBackdropBorderColor then |
||||||
|
self.origBackdropBorderColor = {self:GetBackdropBorderColor()}; |
||||||
|
end |
||||||
|
self:SetBackdropBorderColor(hc.r, hc.g, hc.b, 1); |
||||||
|
end |
||||||
|
|
||||||
|
StdUi.ResetHighlightBorder = function(self) |
||||||
|
if self.target then |
||||||
|
self = self.target; |
||||||
|
end |
||||||
|
|
||||||
|
if self.isDisabled then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local hc = self.origBackdropBorderColor; |
||||||
|
if hc then |
||||||
|
self:SetBackdropBorderColor(unpack(hc)); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:HookHoverBorder(object) |
||||||
|
if not object.SetBackdrop then |
||||||
|
Mixin(object, BackdropTemplateMixin) |
||||||
|
end |
||||||
|
object:HookScript('OnEnter', self.SetHighlightBorder); |
||||||
|
object:HookScript('OnLeave', self.ResetHighlightBorder); |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:ApplyBackdrop(frame, type, border, insets) |
||||||
|
local config = frame.config or self.config; |
||||||
|
local backdrop = { |
||||||
|
bgFile = config.backdrop.texture, |
||||||
|
edgeFile = config.backdrop.texture, |
||||||
|
edgeSize = 1, |
||||||
|
}; |
||||||
|
if insets then |
||||||
|
backdrop.insets = insets; |
||||||
|
end |
||||||
|
if not frame.SetBackdrop then |
||||||
|
Mixin(frame, BackdropTemplateMixin) |
||||||
|
end |
||||||
|
frame:SetBackdrop(backdrop); |
||||||
|
|
||||||
|
type = type or 'button'; |
||||||
|
border = border or 'border'; |
||||||
|
|
||||||
|
if config.backdrop[type] then |
||||||
|
frame:SetBackdropColor( |
||||||
|
config.backdrop[type].r, |
||||||
|
config.backdrop[type].g, |
||||||
|
config.backdrop[type].b, |
||||||
|
config.backdrop[type].a |
||||||
|
); |
||||||
|
end |
||||||
|
|
||||||
|
if config.backdrop[border] then |
||||||
|
frame:SetBackdropBorderColor( |
||||||
|
config.backdrop[border].r, |
||||||
|
config.backdrop[border].g, |
||||||
|
config.backdrop[border].b, |
||||||
|
config.backdrop[border].a |
||||||
|
); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:ClearBackdrop(frame) |
||||||
|
if not frame.SetBackdrop then |
||||||
|
Mixin(frame, BackdropTemplateMixin) |
||||||
|
end |
||||||
|
frame:SetBackdrop(nil); |
||||||
|
end |
||||||
|
|
||||||
|
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'); |
||||||
|
if frame.label then |
||||||
|
self:SetTextColor(frame.label, 'normal'); |
||||||
|
end |
||||||
|
|
||||||
|
if frame.text then |
||||||
|
self:SetTextColor(frame.text, 'normal'); |
||||||
|
end |
||||||
|
frame.isDisabled = false; |
||||||
|
else |
||||||
|
self:ApplyBackdrop(frame, 'buttonDisabled', 'borderDisabled'); |
||||||
|
self:SetTextColor(frame, 'disabled'); |
||||||
|
if frame.label then |
||||||
|
self:SetTextColor(frame.label, 'disabled'); |
||||||
|
end |
||||||
|
|
||||||
|
if frame.text then |
||||||
|
self:SetTextColor(frame.text, 'disabled'); |
||||||
|
end |
||||||
|
frame.isDisabled = true; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:HookDisabledBackdrop(frame) |
||||||
|
local this = self; |
||||||
|
hooksecurefunc(frame, 'Disable', function(self) |
||||||
|
this:ApplyDisabledBackdrop(self, false); |
||||||
|
end); |
||||||
|
|
||||||
|
hooksecurefunc(frame, 'Enable', function(self) |
||||||
|
this:ApplyDisabledBackdrop(self, true); |
||||||
|
end); |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:StripTextures(frame) |
||||||
|
for i = 1, frame:GetNumRegions() do |
||||||
|
local region = select(i, frame:GetRegions()); |
||||||
|
|
||||||
|
if region and region:GetObjectType() == 'Texture' then |
||||||
|
region:SetTexture(nil); |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:MakeDraggable(frame, handle) |
||||||
|
frame:SetMovable(true); |
||||||
|
frame:EnableMouse(true); |
||||||
|
frame:RegisterForDrag('LeftButton'); |
||||||
|
frame:SetScript('OnDragStart', frame.StartMoving); |
||||||
|
frame:SetScript('OnDragStop', frame.StopMovingOrSizing); |
||||||
|
|
||||||
|
if handle then |
||||||
|
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); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Make a frame resizable |
||||||
|
function StdUi:MakeResizable(frame, direction) |
||||||
|
-- Possible resize directions and handle rotation values |
||||||
|
local anchorDirections = { |
||||||
|
["TOP"] = 0, |
||||||
|
["TOPRIGHT"] = 1.5708, |
||||||
|
["RIGHT"] = 0, |
||||||
|
["BOTTOMRIGHT"] = 0, |
||||||
|
["BOTTOM"] = 0, |
||||||
|
["BOTTOMLEFT"] = -1.5708, |
||||||
|
["LEFT"] = 0, |
||||||
|
["TOPLEFT"] = 3.1416, |
||||||
|
} |
||||||
|
|
||||||
|
direction = string.upper(direction); |
||||||
|
|
||||||
|
-- Return if invalid direction |
||||||
|
if not anchorDirections[direction] then return false end |
||||||
|
|
||||||
|
frame:SetResizable(true); |
||||||
|
|
||||||
|
-- Create the resize anchor |
||||||
|
local anchor = CreateFrame("Button", nil, frame); |
||||||
|
anchor:SetPoint(direction, frame, direction); |
||||||
|
|
||||||
|
-- Attach side anchor to adjacent sides of frame |
||||||
|
if direction == "TOP" or direction == "BOTTOM" then |
||||||
|
anchor:SetHeight(self.config.resizeHandle.height); |
||||||
|
anchor:SetPoint("LEFT", frame, "LEFT", self.config.resizeHandle.width, 0); |
||||||
|
anchor:SetPoint("RIGHT", frame, "RIGHT", self.config.resizeHandle.width*-1, 0); |
||||||
|
elseif direction == "LEFT" or direction == "RIGHT" then |
||||||
|
anchor:SetWidth(self.config.resizeHandle.width); |
||||||
|
anchor:SetPoint("TOP", frame, "TOP", 0, self.config.resizeHandle.height*-1); |
||||||
|
anchor:SetPoint("BOTTOM", frame, "BOTTOM", 0, self.config.resizeHandle.height); |
||||||
|
else |
||||||
|
-- Set the corner anchor textures |
||||||
|
anchor:SetNormalTexture(self.config.resizeHandle.texture.normal); |
||||||
|
anchor:SetHighlightTexture(self.config.resizeHandle.texture.highlight); |
||||||
|
anchor:SetPushedTexture(self.config.resizeHandle.texture.pushed); |
||||||
|
|
||||||
|
-- Set size and rotate corner anchor |
||||||
|
anchor:SetSize(self.config.resizeHandle.width, self.config.resizeHandle.height); |
||||||
|
anchor:GetNormalTexture():SetRotation(anchorDirections[direction]); |
||||||
|
anchor:GetHighlightTexture():SetRotation(anchorDirections[direction]); |
||||||
|
anchor:GetPushedTexture():SetRotation(anchorDirections[direction]); |
||||||
|
end |
||||||
|
|
||||||
|
-- Resize anchor click handlers |
||||||
|
anchor:SetScript("OnMouseDown", function(self, button) |
||||||
|
if button == "LeftButton" then |
||||||
|
frame:StartSizing(direction); |
||||||
|
frame:SetUserPlaced(true); |
||||||
|
end |
||||||
|
end) |
||||||
|
anchor:SetScript("OnMouseUp", function(self, button) |
||||||
|
if button == "LeftButton" then |
||||||
|
frame:StopMovingOrSizing(); |
||||||
|
end |
||||||
|
end) |
||||||
|
end |
||||||
|
|
@ -0,0 +1,30 @@ |
|||||||
|
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||||
|
xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd"> |
||||||
|
<Script file="StdUi.lua"/> |
||||||
|
<!--Config--> |
||||||
|
<Script file="StdUiConfig.lua"/> |
||||||
|
<Script file="StdUiPosition.lua"/> |
||||||
|
<Script file="StdUiUtil.lua"/> |
||||||
|
<Script file="StdUiLayout.lua"/> |
||||||
|
<Script file="StdUiGrid.lua"/> |
||||||
|
<Script file="StdUiBuilder.lua"/> |
||||||
|
<!--Widgets--> |
||||||
|
<Script file="widgets\Basic.lua"/> |
||||||
|
<Script file="widgets\Window.lua"/> |
||||||
|
<Script file="widgets\Button.lua"/> |
||||||
|
<Script file="widgets\EditBox.lua"/> |
||||||
|
<Script file="widgets\CheckBox.lua"/> |
||||||
|
<Script file="widgets\Dropdown.lua"/> |
||||||
|
<Script file="widgets\Autocomplete.lua"/> |
||||||
|
<Script file="widgets\Label.lua"/> |
||||||
|
<Script file="widgets\Scroll.lua"/> |
||||||
|
<Script file="widgets\ScrollTable.lua"/> |
||||||
|
<Script file="widgets\Slider.lua"/> |
||||||
|
<Script file="widgets\Tooltip.lua"/> |
||||||
|
<Script file="widgets\Table.lua"/> |
||||||
|
<Script file="widgets\ProgressBar.lua"/> |
||||||
|
<Script file="widgets\ColorPicker.lua"/> |
||||||
|
<Script file="widgets\Tab.lua"/> |
||||||
|
<Script file="widgets\Spell.lua"/> |
||||||
|
<Script file="widgets\ContextMenu.lua"/> |
||||||
|
</Ui> |
@ -0,0 +1,267 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Builder', 6; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local util = StdUi.Util; |
||||||
|
|
||||||
|
local function setDatabaseValue(db, key, value) |
||||||
|
if key:find('.') then |
||||||
|
local accessor = StdUi.Util.stringSplit('.', key); |
||||||
|
local startPos = db; |
||||||
|
|
||||||
|
for i, subKey in pairs(accessor) do |
||||||
|
if i == #accessor then |
||||||
|
startPos[subKey] = value; |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
startPos = startPos[subKey]; |
||||||
|
end |
||||||
|
else |
||||||
|
db[key] = value; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
local function getDatabaseValue(db, key) |
||||||
|
if key:find('.') then |
||||||
|
local accessor = StdUi.Util.stringSplit('.', key); |
||||||
|
local startPos = db; |
||||||
|
|
||||||
|
for i, subKey in pairs(accessor) do |
||||||
|
if i == #accessor then |
||||||
|
return startPos[subKey]; |
||||||
|
end |
||||||
|
|
||||||
|
startPos = startPos[subKey]; |
||||||
|
end |
||||||
|
else |
||||||
|
return db[key]; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
---BuildElement |
||||||
|
---@param frame Frame |
||||||
|
---@param row EasyLayoutRow |
||||||
|
---@param info table |
||||||
|
---@param dataKey string |
||||||
|
---@param db table |
||||||
|
function StdUi:BuildElement(frame, row, info, dataKey, db) |
||||||
|
local element; |
||||||
|
|
||||||
|
local genericChangeEvent = function(el, value) |
||||||
|
setDatabaseValue(el.dbReference, el.dataKey, 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 == '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 |
||||||
|
element = self:Autocomplete(frame, 300, 20, ''); |
||||||
|
|
||||||
|
if info.validator then |
||||||
|
element.validator = info.validator; |
||||||
|
end |
||||||
|
if info.transformer then |
||||||
|
element.transformer = info.transformer; |
||||||
|
end |
||||||
|
if info.buttonCreate then |
||||||
|
element.buttonCreate = info.buttonCreate; |
||||||
|
end |
||||||
|
if info.buttonUpdate then |
||||||
|
element.buttonUpdate = info.buttonUpdate; |
||||||
|
end |
||||||
|
if info.items then |
||||||
|
element:SetItems(info.items); |
||||||
|
end |
||||||
|
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 ''); |
||||||
|
|
||||||
|
if info.onClick then |
||||||
|
element:SetScript('OnClick', info.onClick); |
||||||
|
end |
||||||
|
elseif info.type == 'header' then |
||||||
|
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' 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 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(iVal) |
||||||
|
elseif element.SetColor then |
||||||
|
element:SetColor(iVal); |
||||||
|
elseif element.SetValue then |
||||||
|
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) |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return element; |
||||||
|
end |
||||||
|
|
||||||
|
---BuildRow |
||||||
|
---@param frame Frame |
||||||
|
---@param info table |
||||||
|
---@param db table |
||||||
|
function StdUi:BuildRow(frame, info, db) |
||||||
|
local row = frame:AddRow(); |
||||||
|
|
||||||
|
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); |
||||||
|
if element then |
||||||
|
if not frame.elements then |
||||||
|
frame.elements = {}; |
||||||
|
end |
||||||
|
|
||||||
|
frame.elements[key] = el; |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
---BuildWindow |
||||||
|
---@param frame Frame |
||||||
|
---@param info table |
||||||
|
function StdUi:BuildWindow(frame, info) |
||||||
|
local db = info.database or nil; |
||||||
|
|
||||||
|
assert(info.rows, 'Rows are required in order to build table'); |
||||||
|
local rows = info.rows; |
||||||
|
|
||||||
|
self:EasyLayout(frame, info.layoutConfig); |
||||||
|
|
||||||
|
for _, row in util.orderedPairs(rows) do |
||||||
|
self:BuildRow(frame, row, db); |
||||||
|
end |
||||||
|
|
||||||
|
frame:DoLayout(); |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,95 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Config', 4; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local IsAddOnLoaded = IsAddOnLoaded; |
||||||
|
|
||||||
|
StdUi.config = {}; |
||||||
|
|
||||||
|
function StdUi:ResetConfig() |
||||||
|
local font, fontSize = GameFontNormal:GetFont(); |
||||||
|
local _, largeFontSize = GameFontNormalLarge:GetFont(); |
||||||
|
|
||||||
|
self.config = { |
||||||
|
font = { |
||||||
|
family = font, |
||||||
|
size = fontSize, |
||||||
|
titleSize = largeFontSize, |
||||||
|
effect = '', |
||||||
|
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 = { |
||||||
|
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 }, |
||||||
|
|
||||||
|
border = { r = 0.00, g = 0.00, b = 0.00, a = 1 }, |
||||||
|
borderDisabled = { r = 0.40, g = 0.40, b = 0.40, a = 1 } |
||||||
|
}, |
||||||
|
|
||||||
|
progressBar = { |
||||||
|
color = { r = 1, g = 0.9, b = 0, a = 0.5 }, |
||||||
|
}, |
||||||
|
|
||||||
|
highlight = { |
||||||
|
color = { r = 1, g = 0.9, b = 0, a = 0.4 }, |
||||||
|
blank = { r = 0, g = 0, b = 0, a = 0 } |
||||||
|
}, |
||||||
|
|
||||||
|
dialog = { |
||||||
|
width = 400, |
||||||
|
height = 100, |
||||||
|
button = { |
||||||
|
width = 100, |
||||||
|
height = 20, |
||||||
|
margin = 5 |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
tooltip = { |
||||||
|
padding = 10 |
||||||
|
}, |
||||||
|
|
||||||
|
resizeHandle = { |
||||||
|
width = 10, |
||||||
|
height = 10, |
||||||
|
texture = { |
||||||
|
normal = "Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up", |
||||||
|
highlight = "Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up", |
||||||
|
pushed = "Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Down" |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
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] }; |
||||||
|
end |
||||||
|
end |
||||||
|
StdUi:ResetConfig(); |
||||||
|
|
||||||
|
function StdUi:SetDefaultFont(font, size, effect, strata) |
||||||
|
self.config.font.family = font; |
||||||
|
self.config.font.size = size; |
||||||
|
self.config.font.effect = effect; |
||||||
|
self.config.font.strata = strata; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,139 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
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 |
||||||
|
--- @param parent Frame |
||||||
|
--- @param create function |
||||||
|
--- @param update function |
||||||
|
--- @param data table |
||||||
|
--- @param padding number |
||||||
|
--- @param oX number |
||||||
|
--- @param oY number |
||||||
|
--- @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; |
||||||
|
padding = padding or 0; |
||||||
|
|
||||||
|
if not itemsTable then |
||||||
|
itemsTable = {}; |
||||||
|
end |
||||||
|
|
||||||
|
for i = 1, #itemsTable do |
||||||
|
itemsTable[i]:Hide(); |
||||||
|
end |
||||||
|
|
||||||
|
local totalHeight = -oY; |
||||||
|
|
||||||
|
local i = 1; |
||||||
|
for key, value in pairs(data) do |
||||||
|
local itemFrame = itemsTable[i]; |
||||||
|
|
||||||
|
if not itemFrame then |
||||||
|
if type(create) == 'string' then |
||||||
|
-- create a widget and anchor it to |
||||||
|
itemsTable[i] = this[create](this, parent); |
||||||
|
else |
||||||
|
itemsTable[i] = create(parent, value, i, key); |
||||||
|
end |
||||||
|
itemFrame = itemsTable[i]; |
||||||
|
end |
||||||
|
|
||||||
|
-- If you create simple widget you need to handle anchoring yourself |
||||||
|
update(parent, itemFrame, value, i, key); |
||||||
|
itemFrame:Show(); |
||||||
|
|
||||||
|
totalHeight = totalHeight + itemFrame:GetHeight(); |
||||||
|
if i == 1 then |
||||||
|
-- glue first item to offset |
||||||
|
this:GlueTop(itemFrame, parent, oX, oY, 'LEFT'); |
||||||
|
else |
||||||
|
-- glue next items to previous |
||||||
|
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 |
||||||
|
|
||||||
|
return itemsTable, totalHeight; |
||||||
|
end |
||||||
|
|
||||||
|
--- Creates frame list that reuses frames and is based on array data |
||||||
|
--- @param parent Frame |
||||||
|
--- @param create function |
||||||
|
--- @param update function |
||||||
|
--- @param data table |
||||||
|
--- @param paddingX number |
||||||
|
--- @param paddingY number |
||||||
|
--- @param oX number |
||||||
|
--- @param oY number |
||||||
|
function StdUi:ObjectGrid(parent, itemsMatrix, create, update, data, paddingX, paddingY, oX, oY) |
||||||
|
oX = oX or 1; |
||||||
|
oY = oY or -1; |
||||||
|
paddingX = paddingX or 0; |
||||||
|
paddingY = paddingY or 0; |
||||||
|
|
||||||
|
if not itemsMatrix then |
||||||
|
itemsMatrix = {}; |
||||||
|
end |
||||||
|
|
||||||
|
for y = 1, #itemsMatrix do |
||||||
|
for x = 1, #itemsMatrix[y] do |
||||||
|
itemsMatrix[y][x]:Hide(); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
for rowI = 1, #data do |
||||||
|
local row = data[rowI]; |
||||||
|
|
||||||
|
for colI = 1, #row do |
||||||
|
if not itemsMatrix[rowI] then |
||||||
|
-- whole row does not exist yet |
||||||
|
itemsMatrix[rowI] = {}; |
||||||
|
end |
||||||
|
|
||||||
|
local itemFrame = itemsMatrix[rowI][colI]; |
||||||
|
|
||||||
|
if not itemFrame then |
||||||
|
if type(create) == 'string' then |
||||||
|
-- create a widget and set parent it to |
||||||
|
itemFrame = self[create](self, parent); |
||||||
|
else |
||||||
|
itemFrame = create(parent, data[rowI][colI], rowI, colI); |
||||||
|
end |
||||||
|
itemsMatrix[rowI][colI] = itemFrame; |
||||||
|
end |
||||||
|
|
||||||
|
-- If you create simple widget you need to handle anchoring yourself |
||||||
|
update(parent, itemFrame, data[rowI][colI], rowI, colI); |
||||||
|
itemFrame:Show(); |
||||||
|
|
||||||
|
if rowI == 1 and colI == 1 then |
||||||
|
-- glue first item to offset |
||||||
|
self:GlueTop(itemFrame, parent, oX, oY, 'LEFT'); |
||||||
|
else |
||||||
|
if colI == 1 then |
||||||
|
-- glue first item in column to previous row |
||||||
|
self:GlueBelow(itemFrame, itemsMatrix[rowI - 1][colI], 0, -paddingY, 'LEFT'); |
||||||
|
else |
||||||
|
-- glue next column to previous column |
||||||
|
self:GlueRight(itemFrame, itemsMatrix[rowI][colI - 1], paddingX, 0); |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,211 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
|
||||||
|
if not StdUi 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, |
||||||
|
columns = 12, |
||||||
|
padding = { |
||||||
|
top = 0, |
||||||
|
right = 10, |
||||||
|
left = 10, |
||||||
|
bottom = 10, |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
local defaultRowConfig = { |
||||||
|
margin = { |
||||||
|
top = 0, |
||||||
|
right = 0, |
||||||
|
bottom = 15, |
||||||
|
left = 0 |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
local defaultElementConfig = { |
||||||
|
margin = { |
||||||
|
top = 0, |
||||||
|
right = 0, |
||||||
|
bottom = 0, |
||||||
|
left = 0 |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
local EasyLayoutRow = { |
||||||
|
AddElement = function(self, frame, config) |
||||||
|
if not frame.layoutConfig then |
||||||
|
frame.layoutConfig = StdUi.Util.tableMerge(defaultElementConfig, config or {}); |
||||||
|
elseif config then |
||||||
|
frame.layoutConfig = StdUi.Util.tableMerge(frame.layoutConfig, config or {}); |
||||||
|
end |
||||||
|
|
||||||
|
TableInsert(self.elements, frame); |
||||||
|
end, |
||||||
|
|
||||||
|
AddElements = function(self, ...) |
||||||
|
local r = { ... }; |
||||||
|
local cfg = TableRemove(r, #r); |
||||||
|
|
||||||
|
if cfg.column == 'even' then |
||||||
|
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, |
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
local rowMargin = self.config.margin; |
||||||
|
local totalHeight = 0; |
||||||
|
local columnsTaken = 0; |
||||||
|
local x = g + l.padding.left + rowMargin.left; |
||||||
|
|
||||||
|
-- if row has margins, cut down available width |
||||||
|
parentWidth = parentWidth - rowMargin.left - rowMargin.right; |
||||||
|
|
||||||
|
for i = 1, #self.elements do |
||||||
|
local frame = self.elements[i]; |
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
frame:SetWidth(w); |
||||||
|
|
||||||
|
if columnsTaken + col > self.parent.layout.columns then |
||||||
|
print('Element will not fit row capacity: ' .. l.columns); |
||||||
|
return totalHeight; |
||||||
|
end |
||||||
|
|
||||||
|
-- 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 |
||||||
|
|
||||||
|
-- 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 |
||||||
|
} |
||||||
|
|
||||||
|
---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 = {} |
||||||
|
}; |
||||||
|
|
||||||
|
for k, v in pairs(EasyLayoutRow) do |
||||||
|
row[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
return row; |
||||||
|
end |
||||||
|
|
||||||
|
local EasyLayout = { |
||||||
|
---@return EasyLayoutRow |
||||||
|
AddRow = function(self, config) |
||||||
|
if not self.rows then |
||||||
|
self.rows = {}; |
||||||
|
end |
||||||
|
|
||||||
|
local row = self.stdUi:EasyLayoutRow(self, config); |
||||||
|
TableInsert(self.rows, row); |
||||||
|
|
||||||
|
return row; |
||||||
|
end, |
||||||
|
|
||||||
|
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 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); |
@ -0,0 +1,133 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
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'; |
||||||
|
local Right = 'RIGHT'; |
||||||
|
|
||||||
|
local TopLeft = 'TOPLEFT'; |
||||||
|
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 |
||||||
|
object:SetPoint(TopLeft, referencedObject, BottomLeft, x, y); |
||||||
|
elseif align == Right then |
||||||
|
object:SetPoint(TopRight, referencedObject, BottomRight, x, y); |
||||||
|
else |
||||||
|
object:SetPoint(Top, referencedObject, Bottom, x, y); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
--- Glues object above referenced object |
||||||
|
function StdUi:GlueAbove(object, referencedObject, x, y, align) |
||||||
|
if align == Left then |
||||||
|
object:SetPoint(BottomLeft, referencedObject, TopLeft, x, y); |
||||||
|
elseif align == Right then |
||||||
|
object:SetPoint(BottomRight, referencedObject, TopRight, x, y); |
||||||
|
else |
||||||
|
object:SetPoint(Bottom, referencedObject, Top, x, y); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:GlueTop(object, referencedObject, x, y, align) |
||||||
|
if align == Left then |
||||||
|
object:SetPoint(TopLeft, referencedObject, TopLeft, x, y); |
||||||
|
elseif align == Right then |
||||||
|
object:SetPoint(TopRight, referencedObject, TopRight, x, y); |
||||||
|
else |
||||||
|
object:SetPoint(Top, referencedObject, Top, x, y); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:GlueBottom(object, referencedObject, x, y, align) |
||||||
|
if align == Left then |
||||||
|
object:SetPoint(BottomLeft, referencedObject, BottomLeft, x, y); |
||||||
|
elseif align == Right then |
||||||
|
object:SetPoint(BottomRight, referencedObject, BottomRight, x, y); |
||||||
|
else |
||||||
|
object:SetPoint(Bottom, referencedObject, Bottom, x, y); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:GlueRight(object, referencedObject, x, y, inside) |
||||||
|
if inside then |
||||||
|
object:SetPoint(Right, referencedObject, Right, x, y); |
||||||
|
else |
||||||
|
object:SetPoint(Left, referencedObject, Right, x, y); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:GlueLeft(object, referencedObject, x, y, inside) |
||||||
|
if inside then |
||||||
|
object:SetPoint(Left, referencedObject, Left, x, y); |
||||||
|
else |
||||||
|
object:SetPoint(Right, referencedObject, Left, x, y); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:GlueAfter(object, referencedObject, topX, topY, bottomX, bottomY) |
||||||
|
if topX and topY then |
||||||
|
object:SetPoint(TopLeft, referencedObject, TopRight, topX, topY); |
||||||
|
end |
||||||
|
if bottomX and bottomY then |
||||||
|
object:SetPoint(BottomLeft, referencedObject, BottomRight, bottomX, bottomY); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:GlueBefore(object, referencedObject, topX, topY, bottomX, bottomY) |
||||||
|
if topX and topY then |
||||||
|
object:SetPoint(TopRight, referencedObject, TopLeft, topX, topY); |
||||||
|
end |
||||||
|
if bottomX and bottomY then |
||||||
|
object:SetPoint(BottomRight, referencedObject, BottomLeft, bottomX, bottomY); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- More advanced positioning functions |
||||||
|
function StdUi:GlueAcross(object, referencedObject, topLeftX, topLeftY, bottomRightX, bottomRightY) |
||||||
|
object:SetPoint(TopLeft, referencedObject, TopLeft, topLeftX, topLeftY); |
||||||
|
object:SetPoint(BottomRight, referencedObject, BottomRight, bottomRightX, bottomRightY); |
||||||
|
end |
||||||
|
|
||||||
|
-- Glues object to opposite side of anchor |
||||||
|
function StdUi:GlueOpposite(object, referencedObject, x, y, anchor) |
||||||
|
if anchor == 'TOP' then object:SetPoint('BOTTOM', referencedObject, anchor, x, y); |
||||||
|
elseif anchor == 'BOTTOM' then object:SetPoint('TOP', referencedObject, anchor, x, y); |
||||||
|
elseif anchor == 'LEFT' then object:SetPoint('RIGHT', referencedObject, anchor, x, y); |
||||||
|
elseif anchor == 'RIGHT' then object:SetPoint('LEFT', referencedObject, anchor, x, y); |
||||||
|
elseif anchor == 'TOPLEFT' then object:SetPoint('BOTTOMRIGHT', referencedObject, anchor, x, y); |
||||||
|
elseif anchor == 'TOPRIGHT' then object:SetPoint('BOTTOMLEFT', referencedObject, anchor, x, y); |
||||||
|
elseif anchor == 'BOTTOMLEFT' then object:SetPoint('TOPRIGHT', referencedObject, anchor, x, y); |
||||||
|
elseif anchor == 'BOTTOMRIGHT' then object:SetPoint('TOPLEFT', referencedObject, anchor, x, y); |
||||||
|
else object:SetPoint('CENTER', referencedObject, anchor, x, y); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,318 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Util', 11; |
||||||
|
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 frame.SetBackdrop then |
||||||
|
Mixin(frame, BackdropTemplateMixin) |
||||||
|
end |
||||||
|
if not valid then |
||||||
|
frame:SetBackdropBorderColor(1, 0, 0, 1); |
||||||
|
frame.origBackdropBorderColor = { frame:GetBackdropBorderColor() }; |
||||||
|
else |
||||||
|
frame:SetBackdropBorderColor( |
||||||
|
self.config.backdrop.border.r, |
||||||
|
self.config.backdrop.border.g, |
||||||
|
self.config.backdrop.border.b, |
||||||
|
self.config.backdrop.border.a |
||||||
|
); |
||||||
|
frame.origBackdropBorderColor = { frame:GetBackdropBorderColor() }; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
StdUi.Util = { |
||||||
|
--- @param self EditBox |
||||||
|
editBoxValidator = function(self) |
||||||
|
self.value = self:GetText(); |
||||||
|
|
||||||
|
self.stdUi:MarkAsValid(self, true); |
||||||
|
return true; |
||||||
|
end, |
||||||
|
|
||||||
|
--- @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 |
||||||
|
self.stdUi:MarkAsValid(self, false); |
||||||
|
return false; |
||||||
|
end |
||||||
|
|
||||||
|
self:SetText(StdUi.Util.formatMoney(total)); |
||||||
|
self.value = total; |
||||||
|
|
||||||
|
self.stdUi:MarkAsValid(self, true); |
||||||
|
return true; |
||||||
|
end, |
||||||
|
|
||||||
|
--- @param self EditBox |
||||||
|
moneyBoxValidatorExC = 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 or (copper and tonumber(copper) > 0) then |
||||||
|
self.stdUi:MarkAsValid(self, false); |
||||||
|
return false; |
||||||
|
end |
||||||
|
|
||||||
|
self:SetText(StdUi.Util.formatMoney(total, true)); |
||||||
|
self.value = total; |
||||||
|
|
||||||
|
self.stdUi:MarkAsValid(self, true); |
||||||
|
return true; |
||||||
|
end, |
||||||
|
|
||||||
|
--- @param self EditBox |
||||||
|
numericBoxValidator = function(self) |
||||||
|
local text = self:GetText(); |
||||||
|
text = text:trim(); |
||||||
|
|
||||||
|
local value = tonumber(text); |
||||||
|
|
||||||
|
if value == nil then |
||||||
|
self.stdUi:MarkAsValid(self, false); |
||||||
|
return false; |
||||||
|
end |
||||||
|
|
||||||
|
if self.maxValue and self.maxValue < value then |
||||||
|
self.stdUi:MarkAsValid(self, false); |
||||||
|
return false; |
||||||
|
end |
||||||
|
|
||||||
|
if self.minValue and self.minValue > value then |
||||||
|
self.stdUi:MarkAsValid(self, false); |
||||||
|
return false; |
||||||
|
end |
||||||
|
|
||||||
|
self.value = value; |
||||||
|
|
||||||
|
self.stdUi:MarkAsValid(self, true); |
||||||
|
|
||||||
|
return true; |
||||||
|
end, |
||||||
|
|
||||||
|
--- @param self EditBox |
||||||
|
spellValidator = function(self) |
||||||
|
local text = self:GetText(); |
||||||
|
text = text:trim(); |
||||||
|
local name, _, icon, _, _, _, spellId = GetSpellInfo(text); |
||||||
|
|
||||||
|
if not name then |
||||||
|
self.stdUi:MarkAsValid(self, false); |
||||||
|
return false; |
||||||
|
end |
||||||
|
|
||||||
|
self:SetText(name); |
||||||
|
self.value = spellId; |
||||||
|
self.icon:SetTexture(icon); |
||||||
|
|
||||||
|
self.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 |
||||||
|
|
||||||
|
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 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 left = tonumber(text:len()); |
||||||
|
local isValid = (text:len() == 0 and total > 0); |
||||||
|
|
||||||
|
return total, gold, silver, copper, isValid; |
||||||
|
end, |
||||||
|
|
||||||
|
formatMoney = function(money, excludeCopper) |
||||||
|
if type(money) ~= 'number' then |
||||||
|
return money; |
||||||
|
end |
||||||
|
|
||||||
|
money = tonumber(money); |
||||||
|
local goldColor = '|cfffff209'; |
||||||
|
local silverColor = '|cff7b7b7a'; |
||||||
|
local copperColor = '|cffac7248'; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
local output = ''; |
||||||
|
|
||||||
|
if gold > 0 then |
||||||
|
output = format('%s%i%s ', goldColor, gold, '|rg'); |
||||||
|
end |
||||||
|
|
||||||
|
if gold > 0 or silver > 0 then |
||||||
|
output = format('%s%s%02i%s ', output, silverColor, silver, '|rs'); |
||||||
|
end |
||||||
|
|
||||||
|
if not excludeCopper then |
||||||
|
output = format('%s%s%02i%s ', output, copperColor, copper, '|rc'); |
||||||
|
end |
||||||
|
|
||||||
|
return output:trim(); |
||||||
|
end, |
||||||
|
|
||||||
|
formatTime = function(totalTime) |
||||||
|
local days = floor(totalTime/86400) |
||||||
|
local hours = floor(mod(totalTime, 86400)/3600) |
||||||
|
local minutes = floor(mod(totalTime,3600)/60) |
||||||
|
local seconds = floor(mod(totalTime,60)) |
||||||
|
|
||||||
|
if (days > 0) then |
||||||
|
return format("%dd %02dh %02dm %02ds",days,hours,minutes,seconds) |
||||||
|
elseif (hours > 0) then |
||||||
|
return format("%02dh %02dm %02ds",hours,minutes,seconds) |
||||||
|
elseif (minutes > 0) then |
||||||
|
return format("%02dm %02ds",minutes,seconds) |
||||||
|
elseif (seconds > 0) then |
||||||
|
return format("%02ds", seconds) |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
stripColors = function(text) |
||||||
|
text = string.gsub(text, '|c%x%x%x%x%x%x%x%x', ''); |
||||||
|
text = string.gsub(text, '|r', ''); |
||||||
|
return text; |
||||||
|
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) |
||||||
|
); |
||||||
|
|
||||||
|
return WrapTextInColorCode(text, hex); |
||||||
|
end, |
||||||
|
|
||||||
|
tableCount = function(tab) |
||||||
|
local n = #tab; |
||||||
|
|
||||||
|
if n == 0 then |
||||||
|
for _ in pairs(tab) do |
||||||
|
n = n + 1; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
for k, v in pairs(new) do |
||||||
|
if not result[k] then |
||||||
|
result[k] = v; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return result; |
||||||
|
end, |
||||||
|
|
||||||
|
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 |
||||||
|
-- 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 |
||||||
|
|
||||||
|
if key then |
||||||
|
return key, t[key]; |
||||||
|
end |
||||||
|
|
||||||
|
-- no more value to return, cleanup |
||||||
|
t.__orderedIndex = nil; |
||||||
|
return |
||||||
|
end, |
||||||
|
|
||||||
|
orderedPairs = function(t) |
||||||
|
return StdUi.Util.orderedNext, t, nil; |
||||||
|
end, |
||||||
|
|
||||||
|
roundPrecision = function(value, precision) |
||||||
|
local multiplier = 10 ^ (precision or 0); |
||||||
|
return math.floor(value * multiplier + 0.5) / multiplier; |
||||||
|
end |
||||||
|
}; |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
Binary file not shown.
@ -0,0 +1,285 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Autocomplete', 4; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then return end; |
||||||
|
|
||||||
|
local TableInsert = tinsert; |
||||||
|
|
||||||
|
StdUi.Util.autocompleteTransformer = function(_, value) |
||||||
|
return value; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi.Util.autocompleteValidator = function(self) |
||||||
|
self.stdUi:MarkAsValid(self, true); |
||||||
|
return true; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi.Util.autocompleteItemTransformer = function(_, value) |
||||||
|
if not value or value == '' then |
||||||
|
return value; |
||||||
|
end |
||||||
|
|
||||||
|
local itemName = GetItemInfo(value); |
||||||
|
return itemName; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi.Util.autocompleteItemValidator = function(ac) |
||||||
|
local itemName, itemId; |
||||||
|
local t = ac:GetText(); |
||||||
|
local v = ac:GetValue(); |
||||||
|
|
||||||
|
if tonumber(t) ~= nil then |
||||||
|
-- it's a number |
||||||
|
itemName = GetItemInfo(tonumber(t)); |
||||||
|
if itemName then |
||||||
|
itemId = tonumber(t); |
||||||
|
end |
||||||
|
elseif v then |
||||||
|
itemName = GetItemInfo(v); |
||||||
|
if itemName == t then |
||||||
|
itemId = v; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
if itemId then |
||||||
|
ac.value = itemId; |
||||||
|
ac:SetText(itemName); |
||||||
|
self.stdUi:MarkAsValid(ac, true); |
||||||
|
|
||||||
|
return true; |
||||||
|
else |
||||||
|
self.stdUi:MarkAsValid(ac, false); |
||||||
|
return false; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
local AutocompleteMethods = { |
||||||
|
--- Private methods |
||||||
|
buttonCreate = function(panel) |
||||||
|
local optionButton; |
||||||
|
|
||||||
|
optionButton = StdUi:HighlightButton(panel, panel:GetWidth(), 20, ''); |
||||||
|
optionButton.highlight = StdUi:HighlightButtonTexture(optionButton); |
||||||
|
optionButton.highlight:Hide(); |
||||||
|
optionButton.text:SetJustifyH('LEFT'); |
||||||
|
optionButton.autocomplete = panel.autocomplete; |
||||||
|
optionButton:SetFrameLevel(panel:GetFrameLevel() + 2); |
||||||
|
|
||||||
|
optionButton:SetScript('OnClick', function(b) |
||||||
|
local ac = b.autocomplete; |
||||||
|
if b.boundItem then |
||||||
|
b.autocomplete.selectedItem = b.boundItem; |
||||||
|
end |
||||||
|
|
||||||
|
ac.indexChosen = 0; |
||||||
|
ac:SetValue(b.value, b:GetText()); |
||||||
|
b.autocomplete.dropdown:Hide(); |
||||||
|
ac:SetFocus(); |
||||||
|
end); |
||||||
|
|
||||||
|
return optionButton; |
||||||
|
end, |
||||||
|
|
||||||
|
buttonUpdate = function(panel, optionButton, data) |
||||||
|
optionButton.boundItem = data; |
||||||
|
optionButton.value = data.value; |
||||||
|
|
||||||
|
optionButton:SetWidth(panel:GetWidth()); |
||||||
|
optionButton:SetText(data.text); |
||||||
|
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 |
||||||
|
|
||||||
|
return result; |
||||||
|
end, |
||||||
|
|
||||||
|
--- Public methods |
||||||
|
SetItems = function(self, newItems) |
||||||
|
self.items = newItems; |
||||||
|
self:RenderItems(); |
||||||
|
self.dropdown:Hide(); |
||||||
|
end, |
||||||
|
|
||||||
|
RenderItems = function(self) |
||||||
|
local dropdownHeight = 20 * #self.filteredItems; |
||||||
|
self.dropdown:SetHeight(dropdownHeight); |
||||||
|
|
||||||
|
self.stdUi:ObjectList( |
||||||
|
self.dropdown, |
||||||
|
self.itemTable, |
||||||
|
self.buttonCreate, |
||||||
|
self.buttonUpdate, |
||||||
|
self.filteredItems |
||||||
|
); |
||||||
|
end, |
||||||
|
|
||||||
|
ToggleHighlightItems = function(self, flag) |
||||||
|
for _, button in pairs(self.itemTable) do |
||||||
|
if flag then |
||||||
|
button.highlight:Show(); |
||||||
|
else |
||||||
|
button.highlight:Hide(); |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
ToggleHighlight = function(self, index, flag) |
||||||
|
if flag then |
||||||
|
self.itemTable[index].highlight:Show(); |
||||||
|
else |
||||||
|
self.itemTable[index].highlight:Hide(); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
ValueToText = function(self, value) |
||||||
|
return self.transformer(value) |
||||||
|
end, |
||||||
|
|
||||||
|
SetValue = function(self, value, t) |
||||||
|
self.value = value; |
||||||
|
self:SetText(t or self:ValueToText(value) or ''); |
||||||
|
self:Validate(); |
||||||
|
self.button:Hide(); |
||||||
|
end, |
||||||
|
|
||||||
|
Validate = function(self) |
||||||
|
self.isValidated = true; |
||||||
|
self.isValid = self:validator(); |
||||||
|
|
||||||
|
if self.isValid then |
||||||
|
if self.OnValueChanged then |
||||||
|
self:OnValueChanged(self.value, self:GetText()); |
||||||
|
end |
||||||
|
end |
||||||
|
self.isValidated = false; |
||||||
|
end, |
||||||
|
}; |
||||||
|
|
||||||
|
local AutocompleteEvents = { |
||||||
|
OnEditFocusLost = function(s) |
||||||
|
C_Timer.After(0.1, function() s.dropdown:Hide(); end); |
||||||
|
end, |
||||||
|
|
||||||
|
OnEnterPressed = function(ac) |
||||||
|
ac.dropdown:Hide(); |
||||||
|
|
||||||
|
-- User was using arrows to select item |
||||||
|
if ac.indexChosen > 0 and ac.indexChosen <= #ac.filteredItems then |
||||||
|
local item = ac.filteredItems[ac.indexChosen]; |
||||||
|
ac:SetValue(item.value, item.text); |
||||||
|
ac.indexChosen = 0; |
||||||
|
ac:ToggleHighlightItems(false); |
||||||
|
end |
||||||
|
ac:Validate(); |
||||||
|
|
||||||
|
if ac.CustomEnterPressed then |
||||||
|
ac:CustomEnterPressed(ac); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
OnTextChanged = function(ac, isUserInput) |
||||||
|
local plainText = StdUi.Util.stripColors(ac:GetText()); |
||||||
|
ac.selectedItem = nil; |
||||||
|
|
||||||
|
if isUserInput then |
||||||
|
-- reset value if user changed something |
||||||
|
ac.value = nil; |
||||||
|
|
||||||
|
if type(ac.items) == 'function' then |
||||||
|
-- We ensure to pass whole autocomplete as well |
||||||
|
ac.filteredItems = ac:items(plainText); |
||||||
|
elseif type(ac.items) == 'table' then |
||||||
|
ac.filteredItems = ac:filterItems(plainText, ac.items); |
||||||
|
end |
||||||
|
|
||||||
|
if not ac.filteredItems or #ac.filteredItems == 0 then |
||||||
|
ac.dropdown:Hide(); |
||||||
|
else |
||||||
|
ac:RenderItems(); |
||||||
|
ac.dropdown:Show(); |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
OnKeyUp = function(ac, key) |
||||||
|
local arrowKeyPressed = false; |
||||||
|
if key == 'UP' then |
||||||
|
ac.indexChosen = ac.indexChosen - 1; |
||||||
|
arrowKeyPressed = true; |
||||||
|
elseif key == 'DOWN' then |
||||||
|
ac.indexChosen = ac.indexChosen + 1; |
||||||
|
arrowKeyPressed = true; |
||||||
|
end |
||||||
|
|
||||||
|
if arrowKeyPressed then |
||||||
|
if ac.indexChosen < 1 then |
||||||
|
ac.indexChosen = #ac.filteredItems; |
||||||
|
end |
||||||
|
|
||||||
|
if ac.indexChosen > #ac.filteredItems then |
||||||
|
ac.indexChosen = 1; |
||||||
|
end |
||||||
|
|
||||||
|
ac:ToggleHighlightItems(false); |
||||||
|
ac:ToggleHighlight(ac.indexChosen, true); |
||||||
|
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.indexChosen = 0; |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,72 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Basic', 3; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then return end; |
||||||
|
|
||||||
|
function StdUi:Frame(parent, width, height, inherits) |
||||||
|
local frame = CreateFrame('Frame', nil, parent, inherits); |
||||||
|
self:InitWidget(frame); |
||||||
|
self:SetObjSize(frame, width, height); |
||||||
|
|
||||||
|
return frame; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:Panel(parent, width, height, inherits) |
||||||
|
local frame = self:Frame(parent, width, height, inherits); |
||||||
|
self:ApplyBackdrop(frame, 'panel'); |
||||||
|
|
||||||
|
return frame; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:PanelWithLabel(parent, width, height, inherits, text) |
||||||
|
local frame = self:Panel(parent, width, height, inherits); |
||||||
|
|
||||||
|
frame.label = self:Header(frame, text); |
||||||
|
frame.label:SetAllPoints(); |
||||||
|
frame.label:SetJustifyH('MIDDLE'); |
||||||
|
|
||||||
|
return frame; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:PanelWithTitle(parent, width, height, text) |
||||||
|
local frame = self:Panel(parent, width, height); |
||||||
|
|
||||||
|
frame.titlePanel = self:PanelWithLabel(frame, 100, 20, nil, text); |
||||||
|
frame.titlePanel:SetPoint('TOP', 0, -10); |
||||||
|
frame.titlePanel:SetPoint('LEFT', 30, 0); |
||||||
|
frame.titlePanel:SetPoint('RIGHT', -30, 0); |
||||||
|
frame.titlePanel:SetBackdrop(nil); |
||||||
|
|
||||||
|
return frame; |
||||||
|
end |
||||||
|
|
||||||
|
--- @return Texture |
||||||
|
function StdUi:Texture(parent, width, height, texture) |
||||||
|
local tex = parent:CreateTexture(nil, 'ARTWORK'); |
||||||
|
|
||||||
|
self:SetObjSize(tex, width, height); |
||||||
|
if texture then |
||||||
|
tex:SetTexture(texture); |
||||||
|
end |
||||||
|
|
||||||
|
return tex; |
||||||
|
end |
||||||
|
|
||||||
|
--- @return Texture |
||||||
|
function StdUi:ArrowTexture(parent, direction) |
||||||
|
local texture = self:Texture(parent, 16, 8, [[Interface\Buttons\Arrow-Up-Down]]); |
||||||
|
|
||||||
|
if direction == 'UP' then |
||||||
|
texture:SetTexCoord(0, 1, 0.5, 1); |
||||||
|
else |
||||||
|
texture:SetTexCoord(0, 1, 1, 0.5); |
||||||
|
end |
||||||
|
|
||||||
|
return texture; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,126 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Button', 6; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then return end; |
||||||
|
|
||||||
|
local SquareButtonCoords = { |
||||||
|
UP = { 0.45312500, 0.64062500, 0.01562500, 0.20312500}; |
||||||
|
DOWN = { 0.45312500, 0.64062500, 0.20312500, 0.01562500}; |
||||||
|
LEFT = { 0.23437500, 0.42187500, 0.01562500, 0.20312500}; |
||||||
|
RIGHT = { 0.42187500, 0.23437500, 0.01562500, 0.20312500}; |
||||||
|
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, name) |
||||||
|
local button = CreateFrame('Button', name, parent); |
||||||
|
button.stdUi = self; |
||||||
|
|
||||||
|
self:InitWidget(button); |
||||||
|
self:SetObjSize(button, width, height); |
||||||
|
|
||||||
|
self:ApplyBackdrop(button); |
||||||
|
self:HookDisabledBackdrop(button); |
||||||
|
self:HookHoverBorder(button); |
||||||
|
|
||||||
|
for k, v in pairs(SquareButtonMethods) do |
||||||
|
button[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
local coords = SquareButtonCoords[icon]; |
||||||
|
if coords then |
||||||
|
button:SetIcon([[Interface\Buttons\SquareButtonTextures]], 16, 16, true); |
||||||
|
button.icon:SetTexCoord(coords[1], coords[2], coords[3], coords[4]); |
||||||
|
button.iconDisabled:SetTexCoord(coords[1], coords[2], coords[3], coords[4]); |
||||||
|
end |
||||||
|
|
||||||
|
return button; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:ButtonLabel(parent, text) |
||||||
|
local label = self:Label(parent, text); |
||||||
|
label:SetJustifyH('CENTER'); |
||||||
|
self:GlueAcross(label, parent, 2, -2, -2, 2); |
||||||
|
parent:SetFontString(label); |
||||||
|
|
||||||
|
return label; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:HighlightButtonTexture(button) |
||||||
|
local hTex = self:Texture(button, nil, nil, nil); |
||||||
|
hTex:SetColorTexture( |
||||||
|
self.config.highlight.color.r, |
||||||
|
self.config.highlight.color.g, |
||||||
|
self.config.highlight.color.b, |
||||||
|
self.config.highlight.color.a |
||||||
|
); |
||||||
|
hTex:SetAllPoints(); |
||||||
|
|
||||||
|
return hTex; |
||||||
|
end |
||||||
|
|
||||||
|
--- Creates a button with only a highlight |
||||||
|
--- @return Button |
||||||
|
function StdUi:HighlightButton(parent, width, height, text, inherit, name) |
||||||
|
local button = CreateFrame('Button', name, parent, inherit); |
||||||
|
self:InitWidget(button); |
||||||
|
self:SetObjSize(button, width, height); |
||||||
|
button.text = self:ButtonLabel(button, text); |
||||||
|
|
||||||
|
function button:SetFontSize(newSize) |
||||||
|
self.text:SetFontSize(newSize); |
||||||
|
end |
||||||
|
|
||||||
|
local hTex = self:HighlightButtonTexture(button); |
||||||
|
hTex:SetBlendMode('ADD'); |
||||||
|
|
||||||
|
button:SetHighlightTexture(hTex); |
||||||
|
button.highlightTexture = hTex; |
||||||
|
|
||||||
|
return button; |
||||||
|
end |
||||||
|
|
||||||
|
--- @return Button |
||||||
|
function StdUi:Button(parent, width, height, text, inherit, name) |
||||||
|
local button = self:HighlightButton(parent, width, height, text, inherit, name) |
||||||
|
button.stdUi = self; |
||||||
|
|
||||||
|
button:SetHighlightTexture(''); |
||||||
|
|
||||||
|
self:ApplyBackdrop(button); |
||||||
|
self:HookDisabledBackdrop(button); |
||||||
|
self:HookHoverBorder(button); |
||||||
|
|
||||||
|
return button; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:ButtonAutoWidth(button, padding) |
||||||
|
padding = padding or 5; |
||||||
|
button:SetWidth(button.text:GetStringWidth() + padding * 2); |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,288 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Checkbox', 5; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- Checkbox |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
local CheckboxMethods = { |
||||||
|
--- Set checkbox state |
||||||
|
--- |
||||||
|
--- @param flag boolean |
||||||
|
--- @param internal boolean - indicates to not run OnValueChanged |
||||||
|
SetChecked = function(self, flag, internal) |
||||||
|
self.isChecked = flag; |
||||||
|
|
||||||
|
if not internal and self.OnValueChanged then |
||||||
|
self:OnValueChanged(flag, self.value); |
||||||
|
end |
||||||
|
|
||||||
|
if not flag then |
||||||
|
self.checkedTexture:Hide(); |
||||||
|
self.disabledCheckedTexture:Hide(); |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
if self.isDisabled then |
||||||
|
self.checkedTexture:Hide(); |
||||||
|
self.disabledCheckedTexture:Show(); |
||||||
|
else |
||||||
|
self.checkedTexture:Show(); |
||||||
|
self.disabledCheckedTexture:Hide(); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
GetChecked = function(self) |
||||||
|
return self.isChecked; |
||||||
|
end, |
||||||
|
|
||||||
|
SetText = function(self, t) |
||||||
|
self.text:SetText(t); |
||||||
|
end, |
||||||
|
|
||||||
|
SetValue = function(self, value) |
||||||
|
self.value = value; |
||||||
|
end, |
||||||
|
|
||||||
|
GetValue = function(self) |
||||||
|
if self:GetChecked() then |
||||||
|
return self.value; |
||||||
|
else |
||||||
|
return nil; |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
Disable = function(self) |
||||||
|
self.isDisabled = true; |
||||||
|
self:SetChecked(self.isChecked); |
||||||
|
end, |
||||||
|
|
||||||
|
Enable = function(self) |
||||||
|
self.isDisabled = false; |
||||||
|
self:SetChecked(self.isChecked); |
||||||
|
end, |
||||||
|
|
||||||
|
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.stdUi = self; |
||||||
|
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); |
||||||
|
self:HookHoverBorder(checkbox); |
||||||
|
|
||||||
|
if width == nil then |
||||||
|
checkbox:AutoWidth(); |
||||||
|
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); |
||||||
|
checkbox.icon = self:Texture(checkbox, iconSize, iconSize, icon); |
||||||
|
checkbox.icon:SetPoint('LEFT', checkbox.target, 'RIGHT', 5, 0); |
||||||
|
|
||||||
|
checkbox.text:ClearAllPoints(); |
||||||
|
checkbox.text:SetPoint('LEFT', checkbox.target, 'RIGHT', iconSize + 5, 0); |
||||||
|
checkbox.text:SetPoint('RIGHT', checkbox, 'RIGHT', -5, 0); |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
radio.checkedTexture = self:Texture(radio.target, nil, nil, [[Interface\Buttons\UI-RadioButton]]); |
||||||
|
radio.checkedTexture:SetAllPoints(radio.target); |
||||||
|
radio.checkedTexture:Hide(); |
||||||
|
radio.checkedTexture:SetTexCoord(0.25, 0.5, 0, 1); |
||||||
|
|
||||||
|
radio.disabledCheckedTexture = self:Texture(radio.target, nil, nil, |
||||||
|
[[Interface\Buttons\UI-RadioButton]]); |
||||||
|
radio.disabledCheckedTexture:SetAllPoints(radio.target); |
||||||
|
radio.disabledCheckedTexture:Hide(); |
||||||
|
radio.disabledCheckedTexture:SetTexCoord(0.75, 1, 0, 1); |
||||||
|
|
||||||
|
for k, v in pairs(RadioEvents) do |
||||||
|
radio:SetScript(k, v); |
||||||
|
end |
||||||
|
|
||||||
|
if groupName then |
||||||
|
self:AddToRadioGroup(radio, groupName); |
||||||
|
end |
||||||
|
|
||||||
|
return radio; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi.radioGroups = {}; |
||||||
|
StdUi.radioGroupValues = {}; |
||||||
|
|
||||||
|
---@return CheckButton[] |
||||||
|
function StdUi:RadioGroup(groupName) |
||||||
|
if not self.radioGroups[groupName] then |
||||||
|
self.radioGroups[groupName] = {}; |
||||||
|
end |
||||||
|
|
||||||
|
if not self.radioGroupValues[groupName] then |
||||||
|
self.radioGroupValues[groupName] = {}; |
||||||
|
end |
||||||
|
|
||||||
|
return self.radioGroups[groupName]; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:GetRadioGroupValue(groupName) |
||||||
|
local group = self:RadioGroup(groupName); |
||||||
|
|
||||||
|
for i = 1, #group do |
||||||
|
local radio = group[i]; |
||||||
|
if radio:GetChecked() then |
||||||
|
return radio:GetValue(); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return nil; |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:SetRadioGroupValue(groupName, value) |
||||||
|
local group = self:RadioGroup(groupName); |
||||||
|
|
||||||
|
for i = 1, #group do |
||||||
|
local radio = group[i]; |
||||||
|
radio:SetChecked(radio.value == value) |
||||||
|
end |
||||||
|
|
||||||
|
return nil; |
||||||
|
end |
||||||
|
|
||||||
|
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 |
||||||
|
end |
||||||
|
|
||||||
|
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 |
||||||
|
end |
||||||
|
|
||||||
|
function StdUi:OnRadioGroupValueChanged(groupName, callback) |
||||||
|
local group = self:RadioGroup(groupName); |
||||||
|
|
||||||
|
for i = 1, #group do |
||||||
|
local radio = group[i]; |
||||||
|
radio.OnValueChangedCallback = callback; |
||||||
|
radio.OnValueChanged = radioGroupOnValueChanged; |
||||||
|
end |
||||||
|
|
||||||
|
return nil; |
||||||
|
end |
||||||
|
|
||||||
|
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 |
||||||
|
}; |
||||||
|
|
||||||
|
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); |
@ -0,0 +1,323 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'ColorPicker', 6; |
||||||
|
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) |
||||||
|
local wheelWidth = 128; |
||||||
|
local thumbWidth = 10; |
||||||
|
local barWidth = 16; |
||||||
|
|
||||||
|
local cpf = CreateFrame('ColorSelect', nil, parent); |
||||||
|
--self:MakeDraggable(cpf); |
||||||
|
cpf:SetPoint('CENTER'); |
||||||
|
self:ApplyBackdrop(cpf, 'panel'); |
||||||
|
self:SetObjSize(cpf, 340, 200); |
||||||
|
|
||||||
|
-- Create colorpicker wheel. |
||||||
|
cpf.wheelTexture = self:Texture(cpf, wheelWidth, wheelWidth); |
||||||
|
self:GlueTop(cpf.wheelTexture, cpf, 10, -10, 'LEFT'); |
||||||
|
|
||||||
|
cpf.wheelThumbTexture = self:Texture(cpf, thumbWidth, thumbWidth, [[Interface\Buttons\UI-ColorPicker-Buttons]]); |
||||||
|
cpf.wheelThumbTexture:SetTexCoord(0, 0.15625, 0, 0.625); |
||||||
|
|
||||||
|
-- Create the colorpicker slider. |
||||||
|
cpf.valueTexture = self:Texture(cpf, barWidth, wheelWidth); |
||||||
|
self:GlueRight(cpf.valueTexture, cpf.wheelTexture, 10, 0); |
||||||
|
|
||||||
|
cpf.valueThumbTexture = self:Texture(cpf, barWidth, thumbWidth, [[Interface\Buttons\UI-ColorPicker-Buttons]]); |
||||||
|
cpf.valueThumbTexture:SetTexCoord(0.25, 1, 0.875, 0); |
||||||
|
|
||||||
|
cpf:SetColorWheelTexture(cpf.wheelTexture); |
||||||
|
cpf:SetColorWheelThumbTexture(cpf.wheelThumbTexture); |
||||||
|
cpf:SetColorValueTexture(cpf.valueTexture); |
||||||
|
cpf:SetColorValueThumbTexture(cpf.valueThumbTexture); |
||||||
|
|
||||||
|
cpf.alphaSlider = CreateFrame('Slider', nil, cpf); |
||||||
|
cpf.alphaSlider:SetOrientation('VERTICAL'); |
||||||
|
cpf.alphaSlider:SetMinMaxValues(0, 100); |
||||||
|
cpf.alphaSlider:SetValue(0); |
||||||
|
self:SetObjSize(cpf.alphaSlider, barWidth, wheelWidth + thumbWidth); -- hack |
||||||
|
self:GlueRight(cpf.alphaSlider, cpf.valueTexture, 10, 0); |
||||||
|
|
||||||
|
cpf.alphaTexture = self:Texture(cpf.alphaSlider, nil, nil, alphaSliderTexture); |
||||||
|
self:GlueAcross(cpf.alphaTexture, cpf.alphaSlider, 0, -thumbWidth / 2, 0, thumbWidth / 2); -- hack |
||||||
|
--cpf.alphaTexture:SetColorTexture(1, 1, 1, 1); |
||||||
|
--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]]); |
||||||
|
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); |
||||||
|
cpf.oldTexture:SetDrawLayer('ARTWORK', 4); |
||||||
|
|
||||||
|
self:GlueTop(cpf.newTexture, cpf, -30, -30, 'RIGHT'); |
||||||
|
self:GlueBelow(cpf.oldTexture, cpf.newTexture, 20, 45); |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- Buttons |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
cpf.rEdit = self:NumericBox(cpf, 60, 20); |
||||||
|
cpf.gEdit = self:NumericBox(cpf, 60, 20); |
||||||
|
cpf.bEdit = self:NumericBox(cpf, 60, 20); |
||||||
|
cpf.aEdit = self:NumericBox(cpf, 60, 20); |
||||||
|
|
||||||
|
cpf.rEdit:SetMinMaxValue(0, 255); |
||||||
|
cpf.gEdit:SetMinMaxValue(0, 255); |
||||||
|
cpf.bEdit:SetMinMaxValue(0, 255); |
||||||
|
cpf.aEdit:SetMinMaxValue(0, 100); |
||||||
|
|
||||||
|
self:AddLabel(cpf, cpf.rEdit, 'R', 'LEFT'); |
||||||
|
self:AddLabel(cpf, cpf.gEdit, 'G', 'LEFT'); |
||||||
|
self:AddLabel(cpf, cpf.bEdit, 'B', 'LEFT'); |
||||||
|
self:AddLabel(cpf, cpf.aEdit, 'A', 'LEFT'); |
||||||
|
|
||||||
|
self:GlueAfter(cpf.rEdit, cpf.alphaSlider, 20, -thumbWidth / 2); |
||||||
|
self:GlueBelow(cpf.gEdit, cpf.rEdit, 0, -10); |
||||||
|
self:GlueBelow(cpf.bEdit, cpf.gEdit, 0, -10); |
||||||
|
self:GlueBelow(cpf.aEdit, cpf.bEdit, 0, -10); |
||||||
|
|
||||||
|
cpf.okButton = StdUi:Button(cpf, 100, 20, OKAY); |
||||||
|
cpf.cancelButton = StdUi:Button(cpf, 100, 20, CANCEL); |
||||||
|
self:GlueBottom(cpf.okButton, cpf, 40, 20, 'LEFT'); |
||||||
|
self:GlueBottom(cpf.cancelButton, cpf, -40, 20, 'RIGHT'); |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- Methods |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
for k, v in pairs(ColorPickerMethods) do |
||||||
|
cpf[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- Events |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
cpf.alphaSlider:SetScript('OnValueChanged', function(slider) |
||||||
|
cpf:SetColorAlpha((100 - slider:GetValue()) / 100, true); |
||||||
|
end); |
||||||
|
|
||||||
|
for k, v in pairs(ColorPickerEvents) do |
||||||
|
cpf:SetScript(k, v); |
||||||
|
end |
||||||
|
|
||||||
|
cpf.rEdit.OnValueChanged = OnColorPickerValueChanged; |
||||||
|
cpf.gEdit.OnValueChanged = OnColorPickerValueChanged; |
||||||
|
cpf.bEdit.OnValueChanged = OnColorPickerValueChanged; |
||||||
|
cpf.aEdit.OnValueChanged = OnColorPickerValueChanged; |
||||||
|
|
||||||
|
return cpf; |
||||||
|
end |
||||||
|
|
||||||
|
local ColorPickerFrameOkCallback = function(self) |
||||||
|
local cpf = self:GetParent(); |
||||||
|
if cpf.okCallback then |
||||||
|
cpf.okCallback(cpf); |
||||||
|
end |
||||||
|
|
||||||
|
cpf:Hide(); |
||||||
|
end |
||||||
|
|
||||||
|
local ColorPickerFrameCancelCallback = function(self) |
||||||
|
local cpf = self:GetParent(); |
||||||
|
if cpf.cancelCallback then |
||||||
|
cpf.cancelCallback(cpf); |
||||||
|
end |
||||||
|
|
||||||
|
cpf:Hide(); |
||||||
|
end |
||||||
|
|
||||||
|
-- placeholder |
||||||
|
function StdUi:ColorPickerFrame(r, g, b, a, okCallback, cancelCallback, alphaSliderTexture) |
||||||
|
local colorPickerFrame = self.colorPickerFrame; |
||||||
|
if not colorPickerFrame then |
||||||
|
colorPickerFrame = self:ColorPicker(UIParent, alphaSliderTexture); |
||||||
|
colorPickerFrame:SetFrameStrata('FULLSCREEN_DIALOG'); |
||||||
|
self.colorPickerFrame = colorPickerFrame; |
||||||
|
end |
||||||
|
|
||||||
|
colorPickerFrame.okCallback = okCallback; |
||||||
|
colorPickerFrame.cancelCallback = cancelCallback; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
colorPickerFrame:ClearAllPoints(); |
||||||
|
colorPickerFrame:SetPoint('CENTER'); |
||||||
|
colorPickerFrame:Show(); |
||||||
|
end |
||||||
|
|
||||||
|
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 ColorInputEvents = { |
||||||
|
OnClick = function(self) |
||||||
|
self.stdUi:ColorPickerFrame( |
||||||
|
self.color.r, |
||||||
|
self.color.g, |
||||||
|
self.color.b, |
||||||
|
self.color.a, |
||||||
|
function(cpf) |
||||||
|
self:SetColor(cpf:GetColor()); |
||||||
|
end |
||||||
|
); |
||||||
|
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); |
||||||
|
|
||||||
|
button.target = self:Panel(button, 16, 16); |
||||||
|
button.target.stdUi = self; |
||||||
|
button.target:SetPoint('LEFT', 0, 0); |
||||||
|
|
||||||
|
button.text = self:Label(button, label); |
||||||
|
button.text:SetPoint('LEFT', button.target, 'RIGHT', 5, 0); |
||||||
|
button.text:SetPoint('RIGHT', button, 'RIGHT', -5, 0); |
||||||
|
|
||||||
|
button.color = {r = 1, g = 1, b = 1, a = 1}; |
||||||
|
|
||||||
|
if not button.SetBackdrop then |
||||||
|
Mixin(button, BackdropTemplateMixin) |
||||||
|
end |
||||||
|
self:HookDisabledBackdrop(button); --ColorInput has no visual difference when disabled unlike Checkbox |
||||||
|
self:HookHoverBorder(button); |
||||||
|
|
||||||
|
for k, v in pairs(ColorInputMethods) do |
||||||
|
button[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
for k, v in pairs(ColorInputEvents) do |
||||||
|
button:SetScript(k, v); |
||||||
|
end |
||||||
|
|
||||||
|
if color then |
||||||
|
button:SetColor(color); |
||||||
|
end |
||||||
|
|
||||||
|
return button; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,265 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi 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) |
||||||
|
local hide |
||||||
|
if button == 'LeftButton' and itemFrame.contextMenuData.callback then |
||||||
|
hide = itemFrame.contextMenuData.callback(itemFrame, itemFrame.parentContext) |
||||||
|
elseif button == 'RightButton' then |
||||||
|
hide = true |
||||||
|
end |
||||||
|
if hide == true and itemFrame.mainContext then |
||||||
|
itemFrame.mainContext:Hide() |
||||||
|
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) |
||||||
|
self:CloseSubMenus(); |
||||||
|
self:Hide(); |
||||||
|
end, |
||||||
|
|
||||||
|
CloseSubMenus = function(self) |
||||||
|
for i = 1, #self.optionFrames do |
||||||
|
local optionFrame = self.optionFrames[i]; |
||||||
|
if optionFrame.childContext then |
||||||
|
optionFrame.childContext:CloseMenu(); |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
HookRightClick = function(self) |
||||||
|
local parent = self:GetParent(); |
||||||
|
if parent then |
||||||
|
-- ContextMenuOnMouseUp requires a reference to this menu (self) |
||||||
|
local menu = self -- don't trust magic variable names |
||||||
|
parent:HookScript('OnMouseUp', function (_, button) ContextMenuOnMouseUp(menu, button) end); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
HookChildrenClick = function(self) |
||||||
|
|
||||||
|
end, |
||||||
|
|
||||||
|
CreateItem = function(parent, data, i) |
||||||
|
local itemFrame; |
||||||
|
|
||||||
|
if data.constructor and type(data.constructor) == 'function' then |
||||||
|
itemFrame = data.constructor(parent, data, i); |
||||||
|
elseif data.title then |
||||||
|
itemFrame = parent.stdUi:Frame(parent, nil, 20); |
||||||
|
itemFrame.text = parent.stdUi:Label(itemFrame); |
||||||
|
parent.stdUi:GlueLeft(itemFrame.text, itemFrame, 0, 0, true); |
||||||
|
elseif data.isSeparator then |
||||||
|
itemFrame = parent.stdUi:Frame(parent, nil, 20); |
||||||
|
itemFrame.texture = parent.stdUi:Texture(itemFrame, nil, 8, |
||||||
|
[[Interface\COMMON\UI-TooltipDivider-Transparent]]); |
||||||
|
itemFrame.texture:SetPoint('CENTER'); |
||||||
|
itemFrame.texture:SetPoint('LEFT'); |
||||||
|
itemFrame.texture:SetPoint('RIGHT'); |
||||||
|
elseif data.checkbox then |
||||||
|
itemFrame = parent.stdUi:Checkbox(parent, ''); |
||||||
|
elseif data.radio then |
||||||
|
itemFrame = parent.stdUi:Radio(parent, '', data.radioGroup); |
||||||
|
elseif data.text then |
||||||
|
itemFrame = parent.stdUi:HighlightButton(parent, nil, 20); |
||||||
|
end |
||||||
|
|
||||||
|
itemFrame.contextMenuData = data; |
||||||
|
|
||||||
|
-- Need mainContext on all items for right click compatibility. |
||||||
|
-- This will also keep propagating mainContext thru all children. |
||||||
|
-- Note: In the top-most level of items frames, the parent does NOT have a |
||||||
|
-- mainContext, and in that case the parent itself IS the mainContext. |
||||||
|
itemFrame.mainContext = parent.mainContext or parent |
||||||
|
|
||||||
|
if not data.isSeparator then |
||||||
|
itemFrame.text:SetJustifyH('LEFT'); |
||||||
|
end |
||||||
|
|
||||||
|
if not data.isSeparator and data.children then |
||||||
|
itemFrame.icon = parent.stdUi:Texture(itemFrame, 10, 10, [[Interface\Buttons\SquareButtonTextures]]); |
||||||
|
itemFrame.icon:SetTexCoord(0.42187500, 0.23437500, 0.01562500, 0.20312500); |
||||||
|
parent.stdUi:GlueRight(itemFrame.icon, itemFrame, -4, 0, true); |
||||||
|
|
||||||
|
itemFrame.childContext = parent.stdUi:ContextMenu(parent, data.children, true, parent.level + 1); |
||||||
|
itemFrame.parentContext = parent; |
||||||
|
|
||||||
|
itemFrame:HookScript('OnEnter', ContextMenuItemOnEnter); |
||||||
|
end |
||||||
|
|
||||||
|
if data.events then |
||||||
|
for eventName, eventHandler in pairs(data.events) do |
||||||
|
itemFrame:SetScript(eventName, eventHandler); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Always need Right click capability in item frames to close the menu |
||||||
|
if data.hookOnClickIndicator then |
||||||
|
itemFrame:HookScript('OnClick', ContextMenuItemOnMouseUp) |
||||||
|
else |
||||||
|
itemFrame:SetScript('OnMouseUp', ContextMenuItemOnMouseUp) |
||||||
|
end |
||||||
|
|
||||||
|
if data.custom then |
||||||
|
for key, value in pairs(data.custom) do |
||||||
|
itemFrame[key] = value; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return itemFrame; |
||||||
|
end, |
||||||
|
|
||||||
|
UpdateItem = function(parent, itemFrame, data, i) |
||||||
|
local padding = parent.padding; |
||||||
|
|
||||||
|
if data.renderer and type(data.renderer) == 'function' then |
||||||
|
data.renderer(parent, itemFrame, data, i); |
||||||
|
elseif data.title then |
||||||
|
itemFrame.text:SetText(data.title); |
||||||
|
parent.stdUi:ButtonAutoWidth(itemFrame); |
||||||
|
elseif data.checkbox or data.radio then |
||||||
|
itemFrame.text:SetText(data.checkbox or data.radio); |
||||||
|
itemFrame:AutoWidth(); |
||||||
|
if data.value then |
||||||
|
itemFrame:SetValue(data.value); |
||||||
|
end |
||||||
|
elseif data.text then |
||||||
|
itemFrame:SetText(data.text); |
||||||
|
parent.stdUi:ButtonAutoWidth(itemFrame); |
||||||
|
end |
||||||
|
|
||||||
|
if data.children then |
||||||
|
-- add arrow size |
||||||
|
itemFrame:SetWidth(itemFrame:GetWidth() + 16); |
||||||
|
end |
||||||
|
|
||||||
|
if (parent:GetWidth() - padding * 2) < itemFrame:GetWidth() then |
||||||
|
parent:SetWidth(itemFrame:GetWidth() + padding * 2); |
||||||
|
end |
||||||
|
|
||||||
|
itemFrame:SetPoint('LEFT', padding, 0); |
||||||
|
itemFrame:SetPoint('RIGHT', -padding, 0); |
||||||
|
|
||||||
|
if data.color and not data.isSeparator then |
||||||
|
itemFrame.text:SetTextColor(unpack(data.color)); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
DrawOptions = function(self, options) |
||||||
|
if not self.optionFrames then |
||||||
|
self.optionFrames = {}; |
||||||
|
end |
||||||
|
|
||||||
|
local _, totalHeight = self.stdUi:ObjectList( |
||||||
|
self, |
||||||
|
self.optionFrames, |
||||||
|
self.CreateItem, |
||||||
|
self.UpdateItem, |
||||||
|
options, |
||||||
|
0, |
||||||
|
self.padding, |
||||||
|
-self.padding |
||||||
|
); |
||||||
|
|
||||||
|
self:SetHeight(totalHeight + self.padding); |
||||||
|
end, |
||||||
|
|
||||||
|
StartHideCounter = function(self) |
||||||
|
if self.timer then |
||||||
|
self.timer:Cancel(); |
||||||
|
end |
||||||
|
self.timer = C_Timer:NewTimer(3, self.TimerCallback); |
||||||
|
end, |
||||||
|
|
||||||
|
StopHideCounter = function() |
||||||
|
|
||||||
|
end |
||||||
|
}; |
||||||
|
|
||||||
|
StdUi.ContextMenuEvents = { |
||||||
|
OnEnter = function(self) |
||||||
|
|
||||||
|
end, |
||||||
|
OnLeave = function(self) |
||||||
|
|
||||||
|
end |
||||||
|
}; |
||||||
|
|
||||||
|
function StdUi:ContextMenu(parent, options, stopHook, level) |
||||||
|
---@class ContextMenu |
||||||
|
local panel = self:Panel(parent); |
||||||
|
panel.stdUi = self; |
||||||
|
panel.level = level or 1; |
||||||
|
panel.padding = 16; |
||||||
|
|
||||||
|
panel:SetFrameStrata('FULLSCREEN_DIALOG'); |
||||||
|
|
||||||
|
-- force context menus to stay on the screen where they can be used |
||||||
|
panel:SetClampedToScreen(true) |
||||||
|
|
||||||
|
for k, v in pairs(self.ContextMenuMethods) do |
||||||
|
panel[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
for k, v in pairs(self.ContextMenuEvents) do |
||||||
|
panel:SetScript(k, v); |
||||||
|
end |
||||||
|
|
||||||
|
panel:DrawOptions(options); |
||||||
|
|
||||||
|
if panel.level == 1 then |
||||||
|
-- self reference for children |
||||||
|
panel.mainContext = panel; |
||||||
|
if not stopHook then |
||||||
|
panel:HookRightClick(); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
panel:Hide(); |
||||||
|
|
||||||
|
return panel; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,283 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
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 = StdUiDropdowns or {}; |
||||||
|
StdUiDropdowns = dropdowns; |
||||||
|
|
||||||
|
local DropdownItemOnClick = function(self) |
||||||
|
self.dropdown:SetValue(self.value, self:GetText()); |
||||||
|
self.dropdown.optsFrame:Hide(); |
||||||
|
end |
||||||
|
|
||||||
|
local DropdownItemOnValueChanged = function(checkbox, isChecked) |
||||||
|
checkbox.dropdown:ToggleValue(checkbox.value, isChecked); |
||||||
|
end |
||||||
|
|
||||||
|
local DropdownMethods = { |
||||||
|
buttonCreate = function(parent) |
||||||
|
local dropdown = parent.dropdown; |
||||||
|
local optionButton; |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
self.optsFrame:UpdateSize(self:GetWidth(), self.optsFrame:GetHeight()); |
||||||
|
self.optsFrame:Show(); |
||||||
|
self.optsFrame:Update(); |
||||||
|
self:RepaintOptions(); |
||||||
|
end, |
||||||
|
|
||||||
|
HideOptions = function(self) |
||||||
|
self.optsFrame:Hide(); |
||||||
|
end, |
||||||
|
|
||||||
|
ToggleOptions = function(self) |
||||||
|
if self.optsFrame:IsShown() then |
||||||
|
self:HideOptions(); |
||||||
|
else |
||||||
|
self:ShowOptions(); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
SetPlaceholder = function(self, placeholderText) |
||||||
|
if self:GetText() == '' or self:GetText() == self.placeholder then |
||||||
|
self:SetText(placeholderText); |
||||||
|
end |
||||||
|
|
||||||
|
self.placeholder = placeholderText; |
||||||
|
end, |
||||||
|
|
||||||
|
RepaintOptions = function(self) |
||||||
|
local scrollChild = self.optsFrame.scrollChild; |
||||||
|
self.stdUi:ObjectList( |
||||||
|
scrollChild, |
||||||
|
scrollChild.items, |
||||||
|
self.buttonCreate, |
||||||
|
self.buttonUpdate, |
||||||
|
self.options |
||||||
|
); |
||||||
|
self.optsFrame:UpdateItemsCount(#self.options); |
||||||
|
end, |
||||||
|
|
||||||
|
SetOptions = function(self, newOptions) |
||||||
|
self.options = newOptions; |
||||||
|
local optionsHeight = #newOptions * 20; |
||||||
|
local scrollChild = self.optsFrame.scrollChild; |
||||||
|
if not scrollChild.items then |
||||||
|
scrollChild.items = {}; |
||||||
|
end |
||||||
|
|
||||||
|
self.optsFrame:SetHeight(math.min(optionsHeight + 4, 200)); |
||||||
|
scrollChild:SetHeight(optionsHeight); |
||||||
|
|
||||||
|
self:RepaintOptions(); |
||||||
|
end, |
||||||
|
|
||||||
|
ToggleValue = function(self, value, state) |
||||||
|
assert(self.multi, 'Single dropdown cannot have more than one value!'); |
||||||
|
|
||||||
|
-- Treat is as associative array |
||||||
|
if self.assoc then |
||||||
|
self.value[value] = state; |
||||||
|
else |
||||||
|
if state then |
||||||
|
-- we are toggling it on |
||||||
|
if not tContains(self.value, value) then |
||||||
|
TableInsert(self.value, value); |
||||||
|
end |
||||||
|
else |
||||||
|
-- we are removing it from table |
||||||
|
if tContains(self.value, value) then |
||||||
|
tDeleteItem(self.value, value); |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
self:SetValue(self.value); |
||||||
|
end, |
||||||
|
|
||||||
|
SetValue = function(self, value, text) |
||||||
|
self.value = value; |
||||||
|
|
||||||
|
if text then |
||||||
|
self:SetText(text); |
||||||
|
else |
||||||
|
self:SetText(self:FindValueText(value)); |
||||||
|
end |
||||||
|
|
||||||
|
if self.multi then |
||||||
|
for _, checkbox in pairs(self.optsFrame.scrollChild.items) do |
||||||
|
local isChecked = false; |
||||||
|
if self.assoc then |
||||||
|
isChecked = self.value[checkbox.value]; |
||||||
|
else |
||||||
|
isChecked = tContains(self.value, checkbox.value); |
||||||
|
end |
||||||
|
|
||||||
|
checkbox:SetChecked(isChecked, true); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
if self.OnValueChanged then |
||||||
|
self.OnValueChanged(self, value, self:GetText()); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
GetValue = function(self) |
||||||
|
return self.value; |
||||||
|
end, |
||||||
|
|
||||||
|
FindValueText = function(self, value) |
||||||
|
if type(value) ~= 'table' then |
||||||
|
for i = 1, #self.options do |
||||||
|
local opt = self.options[i]; |
||||||
|
|
||||||
|
if opt.value == value then |
||||||
|
return opt.text; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return self.placeholder or ''; |
||||||
|
else |
||||||
|
local result = ''; |
||||||
|
|
||||||
|
for i = 1, #self.options do |
||||||
|
local opt = self.options[i]; |
||||||
|
|
||||||
|
if self.assoc then |
||||||
|
for key, checked in pairs(value) do |
||||||
|
if checked and key == opt.value then |
||||||
|
if result == '' then |
||||||
|
result = opt.text; |
||||||
|
else |
||||||
|
result = result .. ', ' .. opt.text; |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
else |
||||||
|
for x = 1, #value do |
||||||
|
if value[x] == opt.value then |
||||||
|
if result == '' then |
||||||
|
result = opt.text; |
||||||
|
else |
||||||
|
result = result .. ', ' .. opt.text; |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
if result ~= '' then |
||||||
|
return result |
||||||
|
else |
||||||
|
return self.placeholder or ''; |
||||||
|
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); |
||||||
|
end |
||||||
|
|
||||||
|
if value then |
||||||
|
dropdown:SetValue(value); |
||||||
|
elseif multi then |
||||||
|
dropdown.value = {}; |
||||||
|
end |
||||||
|
|
||||||
|
for k, v in pairs(DropdownEvents) do |
||||||
|
dropdown:SetScript(k, v); |
||||||
|
end |
||||||
|
|
||||||
|
TableInsert(dropdowns, dropdown); |
||||||
|
|
||||||
|
return dropdown; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,390 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'EditBox', 9; |
||||||
|
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) |
||||||
|
--- @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); |
||||||
|
|
||||||
|
for k, v in pairs(SimpleEditBoxMethods) do |
||||||
|
editBox[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
for k, v in pairs(SimpleEditBoxEvents) do |
||||||
|
editBox:SetScript(k, v); |
||||||
|
end |
||||||
|
|
||||||
|
if text then |
||||||
|
editBox:SetText(text); |
||||||
|
end |
||||||
|
|
||||||
|
self:HookDisabledBackdrop(editBox); |
||||||
|
self:HookHoverBorder(editBox); |
||||||
|
self:ApplyBackdrop(editBox); |
||||||
|
self:SetObjSize(editBox, width, height); |
||||||
|
|
||||||
|
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 = {}; |
||||||
|
|
||||||
|
local label = self:Label(widget, placeholderText); |
||||||
|
self:SetTextColor(label, 'disabled'); |
||||||
|
widget.placeholder.label = label; |
||||||
|
|
||||||
|
if icon then |
||||||
|
local texture = self:Texture(widget, 14, 14, icon); |
||||||
|
local c = iconColor or self.config.font.color.disabled; |
||||||
|
texture:SetVertexColor(c.r, c.g, c.b, c.a); |
||||||
|
|
||||||
|
self:GlueLeft(texture, widget, 5, 0, true); |
||||||
|
self:GlueRight(label, texture, 2, 0); |
||||||
|
widget.placeholder.icon = texture; |
||||||
|
else |
||||||
|
self:GlueLeft(label, widget, 2, 0, true); |
||||||
|
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) |
||||||
|
local editBox = self:SimpleEditBox(parent, width, height, ''); |
||||||
|
|
||||||
|
editBox:SetScript('OnTextChanged', SearchEditBoxOnTextChanged); |
||||||
|
|
||||||
|
self:ApplyPlaceholder(editBox, placeholderText, [[Interface\Common\UI-Searchbox-Icon]]); |
||||||
|
|
||||||
|
return editBox; |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- SearchEditBox |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
local EditBoxMethods = { |
||||||
|
GetValue = function(self) |
||||||
|
return self.value; |
||||||
|
end, |
||||||
|
|
||||||
|
SetValue = function(self, value) |
||||||
|
self.value = value; |
||||||
|
self:SetText(value); |
||||||
|
self:Validate(); |
||||||
|
self.button:Hide(); |
||||||
|
end, |
||||||
|
|
||||||
|
IsValid = function(self) |
||||||
|
return self.isValid; |
||||||
|
end, |
||||||
|
|
||||||
|
Validate = function(self) |
||||||
|
self.isValidated = true; |
||||||
|
self.isValid = self.validator(self); |
||||||
|
|
||||||
|
if self.isValid then |
||||||
|
if self.button then |
||||||
|
self.button:Hide(); |
||||||
|
end |
||||||
|
|
||||||
|
if self.OnValueChanged and tostring(self.lastValue) ~= tostring(self.value) then |
||||||
|
self:OnValueChanged(self.value); |
||||||
|
self.lastValue = self.value; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
self.isValidated = false; |
||||||
|
end; |
||||||
|
} |
||||||
|
|
||||||
|
local EditBoxButtonOnClick = function(self) |
||||||
|
self.editBox:Validate(self.editBox); |
||||||
|
end |
||||||
|
|
||||||
|
local EditBoxEvents = { |
||||||
|
OnEnterPressed = function(self) |
||||||
|
self:Validate(); |
||||||
|
end, |
||||||
|
|
||||||
|
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 |
||||||
|
self.button:Show(); |
||||||
|
end |
||||||
|
else |
||||||
|
self.button:Hide(); |
||||||
|
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 |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- SearchEditBox |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
local NumericBoxMethods = { |
||||||
|
SetMaxValue = function(self, value) |
||||||
|
self.maxValue = value; |
||||||
|
self:Validate(); |
||||||
|
end; |
||||||
|
|
||||||
|
SetMinValue = function(self, value) |
||||||
|
self.minValue = value; |
||||||
|
self:Validate(); |
||||||
|
end; |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- MoneyBox |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
local MoneyBoxMethods = { |
||||||
|
SetValue = function(self, value) |
||||||
|
self.value = 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, excludeCopper) |
||||||
|
if excludeCopper then |
||||||
|
validator = validator or self.Util.moneyBoxValidatorExC; |
||||||
|
else |
||||||
|
validator = validator or self.Util.moneyBoxValidator; |
||||||
|
end |
||||||
|
|
||||||
|
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 widget = self:ScrollFrame(parent, width, height, editBox); |
||||||
|
editBox.stdUi = self; |
||||||
|
|
||||||
|
local scrollFrame = widget.scrollFrame; |
||||||
|
scrollFrame.editBox = editBox; |
||||||
|
widget.editBox = editBox; |
||||||
|
editBox.panel = widget; |
||||||
|
|
||||||
|
self:ApplyBackdrop(widget, 'button'); |
||||||
|
self:HookHoverBorder(scrollFrame); |
||||||
|
self:HookHoverBorder(editBox); |
||||||
|
|
||||||
|
editBox:SetWidth(scrollFrame:GetWidth()); |
||||||
|
self:GlueAcross(scrollFrame, widget, 2, -2, -widget.scrollBarWidth - 2, 3); |
||||||
|
--editBox:SetHeight(scrollFrame:GetHeight()); |
||||||
|
|
||||||
|
editBox:SetTextInsets(3, 3, 3, 3); |
||||||
|
editBox:SetFontObject(ChatFontNormal); |
||||||
|
editBox:SetAutoFocus(false); |
||||||
|
editBox:SetScript('OnEscapePressed', editBox.ClearFocus); |
||||||
|
editBox:SetMultiLine(true); |
||||||
|
editBox:EnableMouse(true); |
||||||
|
editBox:SetAutoFocus(false); |
||||||
|
editBox:SetCountInvisibleLetters(false); |
||||||
|
editBox:SetAllPoints(); |
||||||
|
|
||||||
|
editBox.scrollFrame = scrollFrame; |
||||||
|
editBox.panel = widget; |
||||||
|
|
||||||
|
for k, v in pairs(MultiLineBoxMethods) do |
||||||
|
widget[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
if text then |
||||||
|
editBox:SetText(text); |
||||||
|
end |
||||||
|
|
||||||
|
editBox:SetScript('OnCursorChanged', MultiLineBoxOnCursorChanged) |
||||||
|
editBox:SetScript('OnTextChanged', MultiLineBoxOnTextChanged); |
||||||
|
|
||||||
|
scrollFrame:HookScript('OnMouseDown', MultiLineBoxScrollOnMouseDown); |
||||||
|
scrollFrame:HookScript('OnVerticalScroll', MultiLineBoxScrollOnVerticalScroll); |
||||||
|
|
||||||
|
widget.SetText = widget.SetValue; |
||||||
|
widget.GetText = widget.GetValue; |
||||||
|
|
||||||
|
return widget; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,88 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
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 fs = parent:CreateFontString(nil, self.config.font.strata, inherit or 'GameFontNormal'); |
||||||
|
|
||||||
|
fs:SetText(text); |
||||||
|
fs:SetJustifyH('LEFT'); |
||||||
|
fs:SetJustifyV('MIDDLE'); |
||||||
|
|
||||||
|
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); |
||||||
|
if size then |
||||||
|
fs:SetFontSize(size); |
||||||
|
end |
||||||
|
|
||||||
|
self:SetTextColor(fs, 'normal'); |
||||||
|
self:SetObjSize(fs, 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); |
||||||
|
|
||||||
|
self:SetTextColor(fs, 'header'); |
||||||
|
|
||||||
|
return fs; |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- AddLabel |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
--- @return FontString |
||||||
|
function StdUi:AddLabel(parent, object, text, labelPosition, labelWidth) |
||||||
|
local labelHeight = (self.config.font.size) + 4; |
||||||
|
local label = self:Label(parent, text, self.config.font.size, nil, labelWidth, labelHeight); |
||||||
|
|
||||||
|
if labelPosition == 'TOP' or labelPosition == nil then |
||||||
|
self:GlueAbove(label, object, 0, 4, 'LEFT'); |
||||||
|
elseif labelPosition == 'RIGHT' then |
||||||
|
self:GlueRight(label, object, 4, 0); |
||||||
|
else -- labelPosition == 'LEFT' |
||||||
|
label:SetWidth(labelWidth or label:GetStringWidth()) |
||||||
|
self:GlueLeft(label, object, -4, 0); |
||||||
|
end |
||||||
|
|
||||||
|
object.label = label; |
||||||
|
|
||||||
|
return label; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,77 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
local progressBar = CreateFrame('StatusBar', nil, parent); |
||||||
|
progressBar:SetStatusBarTexture(self.config.backdrop.texture); |
||||||
|
progressBar:SetStatusBarColor( |
||||||
|
self.config.progressBar.color.r, |
||||||
|
self.config.progressBar.color.g, |
||||||
|
self.config.progressBar.color.b, |
||||||
|
self.config.progressBar.color.a |
||||||
|
); |
||||||
|
self:SetObjSize(progressBar, width, height); |
||||||
|
|
||||||
|
progressBar.texture = progressBar:GetRegions(); |
||||||
|
progressBar.texture:SetDrawLayer('BORDER', -1); |
||||||
|
|
||||||
|
if (vertical) then |
||||||
|
progressBar:SetOrientation('VERTICAL'); |
||||||
|
end |
||||||
|
|
||||||
|
progressBar.text = self:Label(progressBar, ''); |
||||||
|
progressBar.text:SetJustifyH('MIDDLE'); |
||||||
|
progressBar.text:SetAllPoints(); |
||||||
|
|
||||||
|
self:ApplyBackdrop(progressBar); |
||||||
|
|
||||||
|
for k, v in pairs(ProgressBarMethods) do |
||||||
|
progressBar[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
for k, v in pairs(ProgressBarEvents) do |
||||||
|
progressBar:SetScript(k, v); |
||||||
|
end |
||||||
|
|
||||||
|
return progressBar; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,598 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Scroll', 6; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local round = function(num) |
||||||
|
return math.floor(num + .5); |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- ScrollFrame |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
StdUi.ScrollBarEvents = { |
||||||
|
|
||||||
|
UpDownButtonOnClick = function(self) |
||||||
|
local scrollBar = self.scrollBar; |
||||||
|
local scrollFrame = scrollBar.scrollFrame; |
||||||
|
local scrollStep = scrollBar.scrollStep or (scrollFrame:GetHeight() / 2); |
||||||
|
|
||||||
|
if self.direction == 1 then |
||||||
|
scrollBar:SetValue(scrollBar:GetValue() - scrollStep); |
||||||
|
else |
||||||
|
scrollBar:SetValue(scrollBar:GetValue() + scrollStep); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
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 |
||||||
|
scrollBar:SetValue(scrollBar:GetValue() - scrollStep); |
||||||
|
else |
||||||
|
scrollBar:SetValue(scrollBar:GetValue() + scrollStep); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
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 |
||||||
|
scrollbar:Hide(); |
||||||
|
scrollDownButton:Hide(); |
||||||
|
scrollUpButton:Hide(); |
||||||
|
thumbTexture:Hide(); |
||||||
|
else |
||||||
|
scrollDownButton:Disable(); |
||||||
|
scrollUpButton:Disable(); |
||||||
|
scrollDownButton:Show(); |
||||||
|
scrollUpButton:Show(); |
||||||
|
if (not self.noScrollThumb) then |
||||||
|
thumbTexture:Show(); |
||||||
|
end |
||||||
|
end |
||||||
|
else |
||||||
|
scrollDownButton:Show(); |
||||||
|
scrollUpButton:Show(); |
||||||
|
scrollbar:Show(); |
||||||
|
if not self.noScrollThumb then |
||||||
|
thumbTexture:Show(); |
||||||
|
end |
||||||
|
-- The 0.005 is to account for precision errors |
||||||
|
if yRange - value > 0.005 then |
||||||
|
scrollDownButton:Enable(); |
||||||
|
else |
||||||
|
scrollDownButton:Disable(); |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
OnVerticalScroll = function(self, offset) |
||||||
|
local scrollBar = self.scrollBar; |
||||||
|
scrollBar:SetValue(offset); |
||||||
|
|
||||||
|
local _, max = scrollBar:GetMinMaxValues(); |
||||||
|
scrollBar.ScrollUpButton:SetEnabled(offset ~= 0); |
||||||
|
scrollBar.ScrollDownButton:SetEnabled((scrollBar:GetValue() - max) ~= 0); |
||||||
|
end |
||||||
|
} |
||||||
|
|
||||||
|
StdUi.ScrollFrameMethods = { |
||||||
|
SetScrollStep = function(self, scrollStep) |
||||||
|
scrollStep = round(scrollStep); |
||||||
|
self.scrollBar.scrollStep = scrollStep; |
||||||
|
self.scrollBar:SetValueStep(scrollStep); |
||||||
|
end, |
||||||
|
|
||||||
|
GetChildFrames = function(self) |
||||||
|
return self.scrollBar, self.scrollChild, self.scrollBar.ScrollUpButton, self.scrollBar.ScrollDownButton; |
||||||
|
end, |
||||||
|
|
||||||
|
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 |
||||||
|
}; |
||||||
|
|
||||||
|
function StdUi:ScrollFrame(parent, width, height, scrollChild) |
||||||
|
local panel = self:Panel(parent, width, height); |
||||||
|
panel.stdUi = self; |
||||||
|
panel.offset = 0; |
||||||
|
panel.scrollBarWidth = 16; |
||||||
|
|
||||||
|
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, |
||||||
|
|
||||||
|
--- 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 |
||||||
|
updateFunction(self); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
--- 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 == nil or numToDisplay == nil then |
||||||
|
return; |
||||||
|
end |
||||||
|
|
||||||
|
if numItems > numToDisplay then |
||||||
|
showScrollBar = 1; |
||||||
|
else |
||||||
|
scrollBar:SetValue(0); |
||||||
|
end |
||||||
|
|
||||||
|
if self:IsShown() then |
||||||
|
local scrollFrameHeight = 0; |
||||||
|
local scrollChildHeight = 0; |
||||||
|
|
||||||
|
if numItems > 0 then |
||||||
|
scrollFrameHeight = (numItems - numToDisplay) * buttonHeight; |
||||||
|
scrollChildHeight = numItems * buttonHeight; |
||||||
|
if (scrollFrameHeight < 0) then |
||||||
|
scrollFrameHeight = 0; |
||||||
|
end |
||||||
|
scrollChildFrame:Show(); |
||||||
|
else |
||||||
|
scrollChildFrame:Hide(); |
||||||
|
end |
||||||
|
|
||||||
|
local maxRange = (numItems - numToDisplay) * buttonHeight; |
||||||
|
if maxRange < 0 then |
||||||
|
maxRange = 0; |
||||||
|
end |
||||||
|
|
||||||
|
scrollBar:SetMinMaxValues(0, maxRange); |
||||||
|
self:SetScrollStep(buttonHeight); |
||||||
|
scrollBar:SetStepsPerPage(numToDisplay - 1); |
||||||
|
scrollChildFrame:SetHeight(scrollChildHeight); |
||||||
|
|
||||||
|
-- Arrow button handling |
||||||
|
if scrollBar:GetValue() == 0 then |
||||||
|
scrollUpButton:Disable(); |
||||||
|
else |
||||||
|
scrollUpButton:Enable(); |
||||||
|
end |
||||||
|
|
||||||
|
if (scrollBar:GetValue() - scrollFrameHeight) == 0 then |
||||||
|
scrollDownButton:Disable(); |
||||||
|
else |
||||||
|
scrollDownButton:Enable(); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return showScrollBar; |
||||||
|
end, |
||||||
|
}; |
||||||
|
|
||||||
|
local OnVerticalScrollUpdate = function(self) |
||||||
|
self:Redraw(); |
||||||
|
end; |
||||||
|
|
||||||
|
StdUi.FauxScrollFrameEvents = { |
||||||
|
OnVerticalScroll = function(self, value) |
||||||
|
value = round(value); |
||||||
|
local panel = self.panel; |
||||||
|
|
||||||
|
panel:DoVerticalScroll( |
||||||
|
value, |
||||||
|
panel.lineHeight, |
||||||
|
OnVerticalScrollUpdate |
||||||
|
); |
||||||
|
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 panel = self:ScrollFrame(parent, width, height, scrollChild); |
||||||
|
|
||||||
|
panel.lineHeight = lineHeight; |
||||||
|
panel.displayCount = displayCount; |
||||||
|
|
||||||
|
for k, v in pairs(self.FauxScrollFrameMethods) do |
||||||
|
panel[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
for k, v in pairs(self.FauxScrollFrameEvents) do |
||||||
|
panel.scrollFrame:SetScript(k, v); |
||||||
|
end |
||||||
|
|
||||||
|
return panel; |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- 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 |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
self.range = range; |
||||||
|
self.totalHeight = totalHeight; |
||||||
|
self.scrollFrame:UpdateScrollChildRect(); |
||||||
|
end, |
||||||
|
|
||||||
|
SetData = function(self, data) |
||||||
|
self.data = data; |
||||||
|
end, |
||||||
|
|
||||||
|
SetUpdateFunction = function(self, updateFn) |
||||||
|
self.updateFn = updateFn; |
||||||
|
end, |
||||||
|
|
||||||
|
UpdateScrollBarState = function(self, currValue) |
||||||
|
if not currValue then |
||||||
|
currValue = self.scrollBar:GetValue(); |
||||||
|
end |
||||||
|
|
||||||
|
self.scrollBar.ScrollUpButton:Enable(); |
||||||
|
self.scrollBar.ScrollDownButton:Enable(); |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
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, |
||||||
|
|
||||||
|
SetOffset = function(self, offset) |
||||||
|
local items = self.items; |
||||||
|
local itemHeight = self.itemHeight; |
||||||
|
local element, overflow; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
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 |
||||||
|
}; |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
--for k, v in pairs(self.HybridScrollFrameEvents) do |
||||||
|
-- panel.scrollFrame:SetScript(k, v); |
||||||
|
--end |
||||||
|
|
||||||
|
|
||||||
|
return panel; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,778 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'ScrollTable', 7; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local TableInsert = tinsert; |
||||||
|
local TableSort = table.sort; |
||||||
|
local padding = 2.5; |
||||||
|
|
||||||
|
--- Public methods of ScrollTable |
||||||
|
local methods = { |
||||||
|
|
||||||
|
------------------------------------------------------------- |
||||||
|
--- Basic Methods |
||||||
|
------------------------------------------------------------- |
||||||
|
|
||||||
|
SetAutoHeight = function(self) |
||||||
|
self:SetHeight((self.numberOfRows * self.rowHeight) + 10); |
||||||
|
self:Refresh(); |
||||||
|
end, |
||||||
|
|
||||||
|
SetAutoWidth = function(self) |
||||||
|
local width = 13; |
||||||
|
for _, col in pairs(self.columns) do |
||||||
|
width = width + col.width; |
||||||
|
end |
||||||
|
self:SetWidth(width + 20); |
||||||
|
self:Refresh(); |
||||||
|
end, |
||||||
|
|
||||||
|
ScrollToLine = function(self, line) |
||||||
|
line = Clamp(line, 1, #self.filtered - self.numberOfRows + 1); |
||||||
|
|
||||||
|
self:DoVerticalScroll( |
||||||
|
self.rowHeight * (line - 1), |
||||||
|
self.rowHeight, function(s) |
||||||
|
s:Refresh(); |
||||||
|
end |
||||||
|
); |
||||||
|
end, |
||||||
|
|
||||||
|
------------------------------------------------------------- |
||||||
|
--- Drawing Methods |
||||||
|
------------------------------------------------------------- |
||||||
|
|
||||||
|
--- Set the column info for the scrolling table |
||||||
|
--- @usage st:SetColumns(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); |
||||||
|
columnHeadFrame:SetPoint('BOTTOMRIGHT', self, 'TOPRIGHT', -4, 0); |
||||||
|
columnHeadFrame:SetHeight(self.rowHeight); |
||||||
|
columnHeadFrame.columns = {}; |
||||||
|
self.head = columnHeadFrame; |
||||||
|
end |
||||||
|
|
||||||
|
for i = 1, #columns do |
||||||
|
local column = self.columns[i]; |
||||||
|
local columnFrame = columnHeadFrame.columns[i]; |
||||||
|
if not columnHeadFrame.columns[i] then |
||||||
|
columnFrame = self.stdUi:HighlightButton(columnHeadFrame); |
||||||
|
columnFrame:SetPushedTextOffset(0, 0); |
||||||
|
|
||||||
|
columnFrame.arrow = self.stdUi:Texture(columnFrame, 8, 8, [[Interface\Buttons\UI-SortArrow]]); |
||||||
|
columnFrame.arrow:Hide(); |
||||||
|
|
||||||
|
if self.headerEvents then |
||||||
|
for event, handler in pairs(self.headerEvents) do |
||||||
|
columnFrame:SetScript(event, function(cellFrame, ...) |
||||||
|
table:FireHeaderEvent(event, handler, columnFrame, columnHeadFrame, i, ...); |
||||||
|
end); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
columnHeadFrame.columns[i] = columnFrame; |
||||||
|
|
||||||
|
-- Add column head reference to it's column |
||||||
|
column.head = columnFrame; |
||||||
|
|
||||||
|
-- Create a table of empty column cell references |
||||||
|
column.cells = {}; |
||||||
|
end |
||||||
|
|
||||||
|
local align = columns[i].align or 'LEFT'; |
||||||
|
columnFrame.text:SetJustifyH(align); |
||||||
|
columnFrame.text:SetText(columns[i].name); |
||||||
|
|
||||||
|
if align == 'LEFT' then |
||||||
|
columnFrame.arrow:ClearAllPoints(); |
||||||
|
self.stdUi:GlueRight(columnFrame.arrow, columnFrame, 0, 0, true); |
||||||
|
else |
||||||
|
columnFrame.arrow:ClearAllPoints(); |
||||||
|
self.stdUi:GlueLeft(columnFrame.arrow, columnFrame, 5, 0, true); |
||||||
|
end |
||||||
|
|
||||||
|
if columns[i].sortable == false and columns[i].sortable ~= nil then |
||||||
|
|
||||||
|
else |
||||||
|
|
||||||
|
end |
||||||
|
|
||||||
|
if i > 1 then |
||||||
|
columnFrame:SetPoint('LEFT', columnHeadFrame.columns[i - 1], 'RIGHT', 0, 0); |
||||||
|
else |
||||||
|
columnFrame:SetPoint('LEFT', columnHeadFrame, 'LEFT', 2, 0); |
||||||
|
end |
||||||
|
|
||||||
|
columnFrame:SetHeight(self.rowHeight); |
||||||
|
columnFrame:SetWidth(columns[i].width); |
||||||
|
|
||||||
|
--- Set the width of a column |
||||||
|
--- @usage st.columns[i]:SetWidth(width) |
||||||
|
function column:SetWidth(width) |
||||||
|
-- Update the column's width value |
||||||
|
column.width = width; |
||||||
|
|
||||||
|
-- Set the width of the column's head |
||||||
|
column.head:SetWidth(width); |
||||||
|
|
||||||
|
-- Set the width of each cell in the column |
||||||
|
for j = 1, #column.cells do |
||||||
|
column.cells[j]:SetWidth(width) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
self:SetDisplayRows(self.numberOfRows, self.rowHeight); |
||||||
|
self:SetAutoWidth(); |
||||||
|
end, |
||||||
|
|
||||||
|
--- Set the number and height of displayed rows |
||||||
|
--- @usage st:SetDisplayRows(10, 15) |
||||||
|
SetDisplayRows = function(self, numberOfRows, rowHeight) |
||||||
|
local table = self; -- reference saved for closure |
||||||
|
-- should always set columns first |
||||||
|
self.numberOfRows = numberOfRows; |
||||||
|
self.rowHeight = rowHeight; |
||||||
|
|
||||||
|
if not self.rows then |
||||||
|
self.rows = {}; |
||||||
|
end |
||||||
|
|
||||||
|
for i = 1, numberOfRows do |
||||||
|
local rowFrame = self.rows[i]; |
||||||
|
|
||||||
|
if not rowFrame then |
||||||
|
rowFrame = CreateFrame('Button', nil, self); |
||||||
|
self.rows[i] = rowFrame; |
||||||
|
|
||||||
|
if i > 1 then |
||||||
|
rowFrame:SetPoint('TOPLEFT', self.rows[i - 1], 'BOTTOMLEFT', 0, 0); |
||||||
|
rowFrame:SetPoint('TOPRIGHT', self.rows[i - 1], 'BOTTOMRIGHT', 0, 0); |
||||||
|
else |
||||||
|
rowFrame:SetPoint('TOPLEFT', self.scrollFrame, 'TOPLEFT', 1, -1); |
||||||
|
rowFrame:SetPoint('TOPRIGHT', self.scrollFrame, 'TOPRIGHT', -1, -1); |
||||||
|
end |
||||||
|
|
||||||
|
rowFrame:SetHeight(rowHeight); |
||||||
|
end |
||||||
|
|
||||||
|
if not rowFrame.columns then |
||||||
|
rowFrame.columns = {}; |
||||||
|
end |
||||||
|
|
||||||
|
for j = 1, #self.columns do |
||||||
|
local columnData = self.columns[j]; |
||||||
|
|
||||||
|
local cell = rowFrame.columns[j]; |
||||||
|
if not cell then |
||||||
|
cell = CreateFrame('Button', nil, rowFrame); |
||||||
|
cell.text = self.stdUi:FontString(cell, ''); |
||||||
|
|
||||||
|
rowFrame.columns[j] = cell; |
||||||
|
|
||||||
|
-- Add cell reference to column |
||||||
|
self.columns[j].cells[i] = cell; |
||||||
|
|
||||||
|
local align = columnData.align or 'LEFT'; |
||||||
|
|
||||||
|
cell.text:SetJustifyH(align); |
||||||
|
cell:EnableMouse(true); |
||||||
|
cell:RegisterForClicks('AnyUp'); |
||||||
|
|
||||||
|
if self.cellEvents then |
||||||
|
for event, handler in pairs(self.cellEvents) do |
||||||
|
cell:SetScript(event, function(cellFrame, ...) |
||||||
|
if table.offset then |
||||||
|
local rowIndex = table.filtered[i + table.offset]; |
||||||
|
local rowData = table:GetRow(rowIndex); |
||||||
|
table:FireCellEvent(event, handler, cellFrame, rowFrame, rowData, columnData, |
||||||
|
rowIndex, ...); |
||||||
|
end |
||||||
|
end); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- override a column based events |
||||||
|
if columnData.events then |
||||||
|
for event, handler in pairs(columnData.events) do |
||||||
|
|
||||||
|
cell:SetScript(event, function(cellFrame, ...) |
||||||
|
if table.offset then |
||||||
|
local rowIndex = table.filtered[i + table.offset]; |
||||||
|
local rowData = table:GetRow(rowIndex); |
||||||
|
table:FireCellEvent(event, handler, cellFrame, rowFrame, rowData, columnData, |
||||||
|
rowIndex, ...); |
||||||
|
end |
||||||
|
end); |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
if j > 1 then |
||||||
|
cell:SetPoint('LEFT', rowFrame.columns[j - 1], 'RIGHT', 0, 0); |
||||||
|
else |
||||||
|
cell:SetPoint('LEFT', rowFrame, 'LEFT', 2, 0); |
||||||
|
end |
||||||
|
|
||||||
|
cell:SetHeight(rowHeight); |
||||||
|
cell:SetWidth(self.columns[j].width); |
||||||
|
|
||||||
|
cell.text:SetPoint('TOP', cell, 'TOP', 0, 0); |
||||||
|
cell.text:SetPoint('BOTTOM', cell, 'BOTTOM', 0, 0); |
||||||
|
cell.text:SetWidth(self.columns[j].width - 2 * padding); |
||||||
|
end |
||||||
|
|
||||||
|
local j = #self.columns + 1; |
||||||
|
local col = rowFrame.columns[j]; |
||||||
|
while col do |
||||||
|
col:Hide(); |
||||||
|
j = j + 1; |
||||||
|
col = rowFrame.columns[j]; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
for i = numberOfRows + 1, #self.rows do |
||||||
|
self.rows[i]:Hide(); |
||||||
|
end |
||||||
|
|
||||||
|
self:SetAutoHeight(); |
||||||
|
end, |
||||||
|
|
||||||
|
--- Set the width of a column |
||||||
|
--- @usage st:SetColumnWidth(2, 65) |
||||||
|
SetColumnWidth = function(self, columnNumber, width) |
||||||
|
self.columns[columnNumber]:SetWidth(width); |
||||||
|
end, |
||||||
|
|
||||||
|
------------------------------------------------------------- |
||||||
|
--- Sorting Methods |
||||||
|
------------------------------------------------------------- |
||||||
|
|
||||||
|
--- Resorts the table using the rules specified in the table column info. |
||||||
|
--- @usage st:SortData() |
||||||
|
SortData = function(self, sortBy) |
||||||
|
-- sanity check |
||||||
|
if not (self.sortTable) or (#self.sortTable ~= #self.data) then |
||||||
|
self.sortTable = {}; |
||||||
|
end |
||||||
|
|
||||||
|
if #self.sortTable ~= #self.data then |
||||||
|
for i = 1, #self.data do |
||||||
|
self.sortTable[i] = i; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- go on sorting |
||||||
|
if not sortBy then |
||||||
|
local i = 1; |
||||||
|
while i <= #self.columns and not sortBy do |
||||||
|
if self.columns[i].sort then |
||||||
|
sortBy = i; |
||||||
|
end |
||||||
|
i = i + 1; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
if sortBy then |
||||||
|
TableSort(self.sortTable, function(rowA, rowB) |
||||||
|
local column = self.columns[sortBy]; |
||||||
|
if column.compareSort then |
||||||
|
return column.compareSort(self, rowA, rowB, sortBy); |
||||||
|
else |
||||||
|
return self:CompareSort(rowA, rowB, sortBy); |
||||||
|
end |
||||||
|
end); |
||||||
|
end |
||||||
|
|
||||||
|
self.filtered = self:DoFilter(); |
||||||
|
self:Refresh(); |
||||||
|
self:UpdateSortArrows(sortBy); |
||||||
|
end, |
||||||
|
|
||||||
|
--- 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) |
||||||
|
local a = self:GetRow(rowA); |
||||||
|
local b = self:GetRow(rowB); |
||||||
|
local column = self.columns[sortBy]; |
||||||
|
local idx = column.index; |
||||||
|
|
||||||
|
local direction = column.sort or column.defaultSort or 'asc'; |
||||||
|
|
||||||
|
if direction:lower() == 'asc' then |
||||||
|
return a[idx] > b[idx]; |
||||||
|
else |
||||||
|
return a[idx] < b[idx]; |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
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) |
||||||
|
self.Filter = filter; |
||||||
|
if not noSort then |
||||||
|
self:SortData(); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
DoFilter = function(self) |
||||||
|
local result = {}; |
||||||
|
|
||||||
|
for row = 1, #self.data do |
||||||
|
local realRow = self.sortTable[row]; |
||||||
|
local rowData = self:GetRow(realRow); |
||||||
|
|
||||||
|
if self:Filter(rowData) then |
||||||
|
TableInsert(result, realRow); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return result; |
||||||
|
end, |
||||||
|
|
||||||
|
------------------------------------------------------------- |
||||||
|
--- Highlight Methods |
||||||
|
------------------------------------------------------------- |
||||||
|
|
||||||
|
--- Set the row highlight color of a frame ( cell or row ) |
||||||
|
--- @usage st:SetHighLightColor(rowFrame, color) |
||||||
|
SetHighLightColor = function(self, frame, color) |
||||||
|
if not frame.highlight then |
||||||
|
frame.highlight = frame:CreateTexture(nil, 'OVERLAY'); |
||||||
|
frame.highlight:SetAllPoints(frame); |
||||||
|
end |
||||||
|
|
||||||
|
if not color then |
||||||
|
frame.highlight:SetColorTexture(0, 0, 0, 0); |
||||||
|
else |
||||||
|
frame.highlight:SetColorTexture(color.r, color.g, color.b, color.a); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
ClearHighlightedRows = function(self) |
||||||
|
self.highlightedRows = {}; |
||||||
|
self:Refresh(); |
||||||
|
end, |
||||||
|
|
||||||
|
HighlightRows = function(self, rowIndexes) |
||||||
|
self.highlightedRows = rowIndexes; |
||||||
|
self:Refresh(); |
||||||
|
end, |
||||||
|
|
||||||
|
------------------------------------------------------------- |
||||||
|
--- Selection 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) |
||||||
|
self.selectionEnabled = flag; |
||||||
|
end, |
||||||
|
|
||||||
|
--- Clear the currently selected row. You should not need to refresh the table. |
||||||
|
--- @usage st:ClearSelection() |
||||||
|
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) |
||||||
|
self.selected = rowIndex; |
||||||
|
self:Refresh(); |
||||||
|
end, |
||||||
|
|
||||||
|
--- Gets the currently selected row. |
||||||
|
--- Return will be the unaltered index of the data row that is selected. |
||||||
|
--- @usage st:GetSelection() |
||||||
|
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) |
||||||
|
return self:GetRow(self.selected); |
||||||
|
end, |
||||||
|
|
||||||
|
------------------------------------------------------------- |
||||||
|
--- Data Methods |
||||||
|
------------------------------------------------------------- |
||||||
|
|
||||||
|
--- Sets the data for the scrolling table |
||||||
|
--- @usage st:SetData(datatable) |
||||||
|
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) |
||||||
|
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) |
||||||
|
local rowData = row; |
||||||
|
if type(row) == 'number' then |
||||||
|
rowData = self:GetRow(row); |
||||||
|
end |
||||||
|
|
||||||
|
return rowData[col]; |
||||||
|
end, |
||||||
|
|
||||||
|
--- Checks if a row is currently being shown |
||||||
|
--- @usage st:IsRowVisible(realrow) |
||||||
|
--- @thanks sapu94 |
||||||
|
IsRowVisible = function(self, rowIndex) |
||||||
|
return (rowIndex > self.offset and rowIndex <= (self.numberOfRows + self.offset)); |
||||||
|
end, |
||||||
|
|
||||||
|
------------------------------------------------------------- |
||||||
|
--- Update Internal 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) |
||||||
|
if shouldShow then |
||||||
|
local format = columnData.format; |
||||||
|
|
||||||
|
if type(format) == 'function' then |
||||||
|
cellFrame.text:SetText(format(value, rowData, columnData)); |
||||||
|
elseif format == 'money' then |
||||||
|
value = table.stdUi.Util.formatMoney(value); |
||||||
|
cellFrame.text:SetText(value); |
||||||
|
elseif format == 'moneyShort' then |
||||||
|
value = table.stdUi.Util.formatMoney(value, true); |
||||||
|
cellFrame.text:SetText(value); |
||||||
|
elseif format == 'time' then |
||||||
|
value = table.stdUi.Util.formatTime(value); |
||||||
|
cellFrame.text:SetText(value); |
||||||
|
elseif format == 'number' then |
||||||
|
value = tostring(value); |
||||||
|
cellFrame.text:SetText(value); |
||||||
|
elseif format == 'icon' then |
||||||
|
if cellFrame.texture then |
||||||
|
cellFrame.texture:SetTexture(value); |
||||||
|
else |
||||||
|
local iconSize = columnData.iconSize or table.rowHeight; |
||||||
|
cellFrame.texture = table.stdUi:Texture(cellFrame, iconSize, iconSize, value); |
||||||
|
cellFrame.texture:SetPoint('CENTER', 0, 0); |
||||||
|
end |
||||||
|
elseif format == 'custom' then |
||||||
|
columnData.renderer(cellFrame, value, rowData, columnData); |
||||||
|
else |
||||||
|
cellFrame.text:SetText(value); |
||||||
|
end |
||||||
|
|
||||||
|
local color; |
||||||
|
if rowData.color then |
||||||
|
color = rowData.color; |
||||||
|
elseif columnData.color then |
||||||
|
color = columnData.color; |
||||||
|
end |
||||||
|
|
||||||
|
if type(color) == 'function' then |
||||||
|
color = color(table, value, rowData, columnData); |
||||||
|
end |
||||||
|
|
||||||
|
if color then |
||||||
|
cellFrame.text:SetTextColor(color.r, color.g, color.b, color.a); |
||||||
|
else |
||||||
|
table.stdUi:SetTextColor(cellFrame.text, 'normal'); |
||||||
|
end |
||||||
|
|
||||||
|
if table.selectionEnabled then |
||||||
|
if table.selected == rowIndex then |
||||||
|
table:SetHighLightColor(rowFrame, table.stdUi.config.highlight.color); |
||||||
|
else |
||||||
|
table:SetHighLightColor(rowFrame, nil); |
||||||
|
end |
||||||
|
else |
||||||
|
if tContains(table.highlightedRows, rowIndex) then |
||||||
|
table:SetHighLightColor(rowFrame, table.stdUi.config.highlight.color); |
||||||
|
else |
||||||
|
table:SetHighLightColor(rowFrame, nil); |
||||||
|
end |
||||||
|
end |
||||||
|
else |
||||||
|
cellFrame.text:SetText(''); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
Refresh = function(self) |
||||||
|
self:Update(#self.filtered, self.numberOfRows, self.rowHeight); |
||||||
|
|
||||||
|
local o = self:GetOffset(); |
||||||
|
self.offset = o; |
||||||
|
|
||||||
|
for i = 1, self.numberOfRows do |
||||||
|
local row = i + o; |
||||||
|
|
||||||
|
if self.rows then |
||||||
|
local rowFrame = self.rows[i]; |
||||||
|
|
||||||
|
local rowIndex = self.filtered[row]; |
||||||
|
local rowData = self:GetRow(rowIndex); |
||||||
|
local shouldShow = true; |
||||||
|
|
||||||
|
for col = 1, #self.columns do |
||||||
|
local cellFrame = rowFrame.columns[col]; |
||||||
|
local columnData = self.columns[col]; |
||||||
|
local fnDoCellUpdate = self.DoCellUpdate; |
||||||
|
local value; |
||||||
|
|
||||||
|
if rowData then |
||||||
|
value = rowData[columnData.index]; |
||||||
|
|
||||||
|
self.rows[i]:Show(); |
||||||
|
|
||||||
|
if rowData.doCellUpdate then |
||||||
|
fnDoCellUpdate = rowData.doCellUpdate; |
||||||
|
elseif columnData.doCellUpdate then |
||||||
|
fnDoCellUpdate = columnData.doCellUpdate; |
||||||
|
end |
||||||
|
else |
||||||
|
self.rows[i]:Hide(); |
||||||
|
shouldShow = false; |
||||||
|
end |
||||||
|
|
||||||
|
fnDoCellUpdate(self, shouldShow, rowFrame, cellFrame, value, columnData, rowData, rowIndex); |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
------------------------------------------------------------- |
||||||
|
--- Private Methods |
||||||
|
------------------------------------------------------------- |
||||||
|
|
||||||
|
UpdateSortArrows = function(self, sortBy) |
||||||
|
if not self.head then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
for i = 1, #self.columns do |
||||||
|
local col = self.head.columns[i]; |
||||||
|
if col then |
||||||
|
if i == sortBy then |
||||||
|
local column = self.columns[sortBy]; |
||||||
|
local direction = column.sort or column.defaultSort or 'asc'; |
||||||
|
if direction == 'asc' then |
||||||
|
col.arrow:SetTexCoord(0, 0.5625, 0, 1); |
||||||
|
else |
||||||
|
col.arrow:SetTexCoord(0, 0.5625, 1, 0); |
||||||
|
end |
||||||
|
|
||||||
|
col.arrow:Show(); |
||||||
|
else |
||||||
|
col.arrow:Hide(); |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
FireCellEvent = function(self, event, handler, ...) |
||||||
|
if not handler(self, ...) then |
||||||
|
if self.cellEvents[event] then |
||||||
|
self.cellEvents[event](self, ...); |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
FireHeaderEvent = function(self, event, handler, ...) |
||||||
|
if not handler(self, ...) then |
||||||
|
if self.headerEvents[event] then |
||||||
|
self.headerEvents[event](self, ...); |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
--- Set the event handlers for various ui events for each cell. |
||||||
|
--- @usage st:RegisterEvents(events, true) |
||||||
|
RegisterEvents = function(self, cellEvents, headerEvents, removeOldEvents) |
||||||
|
local table = self; -- save for closure later |
||||||
|
|
||||||
|
if cellEvents then |
||||||
|
-- Register events for each cell |
||||||
|
for i, rowFrame in ipairs(self.rows) do |
||||||
|
for j, cell in ipairs(rowFrame.columns) do |
||||||
|
|
||||||
|
local columnData = self.columns[j]; |
||||||
|
|
||||||
|
-- unregister old events. |
||||||
|
if removeOldEvents and self.cellEvents then |
||||||
|
for event, handler in pairs(self.cellEvents) do |
||||||
|
cell:SetScript(event, nil); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- register new ones. |
||||||
|
for event, handler in pairs(cellEvents) do |
||||||
|
cell:SetScript(event, function(cellFrame, ...) |
||||||
|
local rowIndex = table.filtered[i + table.offset]; |
||||||
|
local rowData = table:GetRow(rowIndex); |
||||||
|
table:FireCellEvent(event, handler, cellFrame, rowFrame, rowData, columnData, |
||||||
|
rowIndex, ...); |
||||||
|
end); |
||||||
|
end |
||||||
|
|
||||||
|
-- override a column based events |
||||||
|
if columnData.events then |
||||||
|
for event, handler in pairs(self.columns[j].events) do |
||||||
|
cell:SetScript(event, function(cellFrame, ...) |
||||||
|
if table.offset then |
||||||
|
local rowIndex = table.filtered[i + table.offset]; |
||||||
|
local rowData = table:GetRow(rowIndex); |
||||||
|
table:FireCellEvent(event, handler, cellFrame, rowFrame, rowData, columnData, |
||||||
|
rowIndex, ...); |
||||||
|
end |
||||||
|
end); |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
if headerEvents then |
||||||
|
-- Register events on column headers |
||||||
|
for columnIndex, columnFrame in ipairs(self.head.columns) do |
||||||
|
-- unregister old events. |
||||||
|
if removeOldEvents and self.headerEvents then |
||||||
|
for event, _ in pairs(self.headerEvents) do |
||||||
|
columnFrame:SetScript(event, nil); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- register new ones. |
||||||
|
for event, handler in pairs(headerEvents) do |
||||||
|
columnFrame:SetScript(event, function(cellFrame, ...) |
||||||
|
table:FireHeaderEvent(event, handler, columnFrame, self.head, columnIndex, ...); |
||||||
|
end); |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
}; |
||||||
|
|
||||||
|
local cellEvents = { |
||||||
|
OnEnter = function(table, cellFrame, rowFrame, rowData, columnData, rowIndex) |
||||||
|
table:SetHighLightColor(rowFrame, table.stdUi.config.highlight.color); |
||||||
|
return true; |
||||||
|
end, |
||||||
|
|
||||||
|
OnLeave = function(table, cellFrame, rowFrame, rowData, columnData, rowIndex) |
||||||
|
if rowIndex ~= table.selected or not table.selectionEnabled then |
||||||
|
table:SetHighLightColor(rowFrame, nil); |
||||||
|
end |
||||||
|
|
||||||
|
return true; |
||||||
|
end, |
||||||
|
|
||||||
|
OnClick = function(table, cellFrame, rowFrame, rowData, columnData, rowIndex, button) |
||||||
|
if button == 'LeftButton' then |
||||||
|
if table:GetSelection() == rowIndex then |
||||||
|
table:ClearSelection(); |
||||||
|
else |
||||||
|
table:SetSelection(rowIndex); |
||||||
|
end |
||||||
|
|
||||||
|
return true; |
||||||
|
end |
||||||
|
end, |
||||||
|
}; |
||||||
|
|
||||||
|
local headerEvents = { |
||||||
|
OnClick = function(table, columnFrame, columnHeadFrame, columnIndex, button, ...) |
||||||
|
if button == 'LeftButton' then |
||||||
|
|
||||||
|
local columns = table.columns; |
||||||
|
local column = columns[columnIndex]; |
||||||
|
|
||||||
|
-- clear sort for other columns |
||||||
|
for i, _ in ipairs(columnHeadFrame.columns) do |
||||||
|
if i ~= columnIndex then |
||||||
|
columns[i].sort = nil; |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
local sortOrder = 'asc'; |
||||||
|
|
||||||
|
if not column.sort and column.defaultSort then |
||||||
|
-- sort by columns default sort first; |
||||||
|
sortOrder = column.defaultSort; |
||||||
|
elseif column.sort and column.sort:lower() == 'asc' then |
||||||
|
sortOrder = 'dsc'; |
||||||
|
end |
||||||
|
|
||||||
|
column.sort = sortOrder; |
||||||
|
table:SortData(); |
||||||
|
|
||||||
|
return true; |
||||||
|
end |
||||||
|
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 = self:FauxScrollFrame(parent, 100, 100, rowHeight or 15); |
||||||
|
local scrollFrame = scrollTable.scrollFrame; |
||||||
|
|
||||||
|
scrollTable.stdUi = self; |
||||||
|
scrollTable.numberOfRows = numRows or 12; |
||||||
|
scrollTable.rowHeight = rowHeight or 15; |
||||||
|
scrollTable.columns = columns; |
||||||
|
scrollTable.data = {}; |
||||||
|
scrollTable.cellEvents = cellEvents; |
||||||
|
scrollTable.headerEvents = headerEvents; |
||||||
|
scrollTable.highlightedRows = {}; |
||||||
|
|
||||||
|
-- Add all methods |
||||||
|
for methodName, method in pairs(methods) do |
||||||
|
scrollTable[methodName] = method; |
||||||
|
end |
||||||
|
|
||||||
|
scrollFrame:SetScript('OnVerticalScroll', ScrollTableOnVerticalScroll); |
||||||
|
scrollTable:SortData(); |
||||||
|
scrollTable:SetColumns(scrollTable.columns); |
||||||
|
scrollTable:UpdateSortArrows(); |
||||||
|
scrollTable:RegisterEvents(scrollTable.cellEvents, scrollTable.headerEvents); |
||||||
|
-- no need to assign it once again and override all column events |
||||||
|
|
||||||
|
return scrollTable; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,295 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Slider', 7; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then return end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- SliderButton |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
function StdUi:SliderButton(parent, width, height, direction) |
||||||
|
local button = self:Button(parent, width, height); |
||||||
|
|
||||||
|
local texture = self:ArrowTexture(button, direction); |
||||||
|
texture:SetPoint('CENTER'); |
||||||
|
|
||||||
|
local textureDisabled = self:ArrowTexture(button, direction); |
||||||
|
textureDisabled:SetPoint('CENTER'); |
||||||
|
textureDisabled:SetDesaturated(0); |
||||||
|
|
||||||
|
button:SetNormalTexture(texture); |
||||||
|
button:SetDisabledTexture(textureDisabled); |
||||||
|
|
||||||
|
return button; |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- StyleScrollBar |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
--- This is only useful for scrollBars not created using StdUi |
||||||
|
function StdUi:StyleScrollBar(scrollBar) |
||||||
|
local buttonUp, buttonDown = scrollBar:GetChildren(); |
||||||
|
|
||||||
|
scrollBar.background = StdUi:Panel(scrollBar); |
||||||
|
scrollBar.background:SetFrameLevel(scrollBar:GetFrameLevel() - 1); |
||||||
|
scrollBar.background:SetWidth(scrollBar:GetWidth()); |
||||||
|
self:GlueAcross(scrollBar.background, scrollBar, 0, 1, 0, -1); |
||||||
|
|
||||||
|
self:StripTextures(buttonUp); |
||||||
|
self:StripTextures(buttonDown); |
||||||
|
|
||||||
|
self:ApplyBackdrop(buttonUp, 'button'); |
||||||
|
self:ApplyBackdrop(buttonDown, 'button'); |
||||||
|
|
||||||
|
buttonUp:SetWidth(scrollBar:GetWidth()); |
||||||
|
buttonDown:SetWidth(scrollBar:GetWidth()); |
||||||
|
|
||||||
|
local upTex = self:ArrowTexture(buttonUp, 'UP'); |
||||||
|
upTex:SetPoint('CENTER'); |
||||||
|
|
||||||
|
local upTexDisabled = self:ArrowTexture(buttonUp, 'UP'); |
||||||
|
upTexDisabled:SetPoint('CENTER'); |
||||||
|
upTexDisabled:SetDesaturated(0); |
||||||
|
|
||||||
|
buttonUp:SetNormalTexture(upTex); |
||||||
|
buttonUp:SetDisabledTexture(upTexDisabled); |
||||||
|
|
||||||
|
local downTex = self:ArrowTexture(buttonDown, 'DOWN'); |
||||||
|
downTex:SetPoint('CENTER'); |
||||||
|
|
||||||
|
local downTexDisabled = self:ArrowTexture(buttonDown, 'DOWN'); |
||||||
|
downTexDisabled:SetPoint('CENTER'); |
||||||
|
downTexDisabled:SetDesaturated(0); |
||||||
|
|
||||||
|
buttonDown:SetNormalTexture(downTex); |
||||||
|
buttonDown:SetDisabledTexture(downTexDisabled); |
||||||
|
|
||||||
|
local thumbSize = scrollBar:GetWidth(); |
||||||
|
scrollBar:GetThumbTexture():SetWidth(thumbSize); |
||||||
|
|
||||||
|
self:StripTextures(scrollBar); |
||||||
|
|
||||||
|
scrollBar.thumb = self:Panel(scrollBar); |
||||||
|
scrollBar.thumb:SetAllPoints(scrollBar:GetThumbTexture()); |
||||||
|
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(StdUi.Util.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); |
||||||
|
self:ApplyBackdrop(slider, 'panel'); |
||||||
|
self:SetObjSize(slider, width, height); |
||||||
|
|
||||||
|
slider.vertical = vertical; |
||||||
|
slider.precision = 1; |
||||||
|
|
||||||
|
local thumbWidth = vertical and width or 20; |
||||||
|
local thumbHeight = vertical and 20 or height; |
||||||
|
|
||||||
|
slider.ThumbTexture = self:Texture(slider, thumbWidth, thumbHeight, self.config.backdrop.texture); |
||||||
|
slider.ThumbTexture:SetVertexColor( |
||||||
|
self.config.backdrop.slider.r, |
||||||
|
self.config.backdrop.slider.g, |
||||||
|
self.config.backdrop.slider.b, |
||||||
|
self.config.backdrop.slider.a |
||||||
|
); |
||||||
|
slider:SetThumbTexture(slider.ThumbTexture); |
||||||
|
|
||||||
|
slider.thumb = self:Frame(slider); |
||||||
|
slider.thumb:SetAllPoints(slider:GetThumbTexture()); |
||||||
|
self:ApplyBackdrop(slider.thumb, 'button'); |
||||||
|
|
||||||
|
if vertical then |
||||||
|
slider:SetOrientation('VERTICAL'); |
||||||
|
slider.ThumbTexture:SetPoint('LEFT'); |
||||||
|
slider.ThumbTexture:SetPoint('RIGHT'); |
||||||
|
else |
||||||
|
slider:SetOrientation('HORIZONTAL'); |
||||||
|
slider.ThumbTexture:SetPoint('TOP'); |
||||||
|
slider.ThumbTexture:SetPoint('BOTTOM'); |
||||||
|
end |
||||||
|
|
||||||
|
slider.OriginalGetValue = slider.GetValue; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
for k, v in pairs(SliderEvents) do |
||||||
|
slider:HookScript(k, v); |
||||||
|
end |
||||||
|
|
||||||
|
return slider; |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- SliderWithBox |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
local SliderWithBoxMethods = { |
||||||
|
SetValue = function(self, v) |
||||||
|
self.lock = true; |
||||||
|
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, v); |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
GetValue = function(self) |
||||||
|
return self.value; |
||||||
|
end, |
||||||
|
|
||||||
|
SetValueStep = function(self, step) |
||||||
|
self.slider:SetValueStep(step); |
||||||
|
end, |
||||||
|
|
||||||
|
SetPrecision = function(self, numberOfDecimals) |
||||||
|
self.slider.precision = numberOfDecimals; |
||||||
|
end, |
||||||
|
|
||||||
|
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 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; |
||||||
|
|
||||||
|
for k, v in pairs(SliderWithBoxMethods) do |
||||||
|
widget[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
if min and max then |
||||||
|
widget:SetMinMaxValues(min, max); |
||||||
|
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); |
||||||
|
self:GlueBelow(widget.editBox, widget.slider, 0, -5, 'CENTER'); |
||||||
|
widget.leftLabel:SetPoint('TOPLEFT', widget.slider, 'BOTTOMLEFT', 0, 0); |
||||||
|
widget.rightLabel:SetPoint('TOPRIGHT', widget.slider, 'BOTTOMRIGHT', 0, 0); |
||||||
|
|
||||||
|
return widget; |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- 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); |
||||||
|
|
||||||
|
scrollBar.ScrollDownButton = self:SliderButton(parent, width, 16, 'DOWN'); |
||||||
|
scrollBar.ScrollUpButton = self:SliderButton(parent, width, 16, 'UP'); |
||||||
|
scrollBar.panel = panel; |
||||||
|
|
||||||
|
scrollBar.ScrollUpButton.scrollBar = scrollBar; |
||||||
|
scrollBar.ScrollDownButton.scrollBar = scrollBar; |
||||||
|
|
||||||
|
if horizontal then |
||||||
|
--@TODO do this |
||||||
|
--scrollBar.ScrollUpButton:SetPoint('TOPLEFT', panel, 'TOPLEFT', 0, 0); |
||||||
|
--scrollBar.ScrollUpButton:SetPoint('TOPRIGHT', panel, 'TOPRIGHT', 0, 0); |
||||||
|
-- |
||||||
|
--scrollBar.ScrollDownButton:SetPoint('BOTTOMLEFT', panel, 'BOTTOMLEFT', 0, 0); |
||||||
|
--scrollBar.ScrollDownButton:SetPoint('BOTTOMRIGHT', panel, 'BOTTOMRIGHT', 0, 0); |
||||||
|
-- |
||||||
|
--scrollBar:SetPoint('TOPLEFT', scrollBar.ScrollUpButton, 'TOPLEFT', 0, 1); |
||||||
|
--scrollBar:SetPoint('TOPRIGHT', scrollBar.ScrollUpButton, 'TOPRIGHT', 0, 1); |
||||||
|
--scrollBar:SetPoint('BOTTOMLEFT', scrollBar.ScrollDownButton, 'BOTTOMLEFT', 0, -1); |
||||||
|
--scrollBar:SetPoint('BOTTOMRIGHT', scrollBar.ScrollDownButton, 'BOTTOMRIGHT', 0, -1); |
||||||
|
else |
||||||
|
scrollBar.ScrollUpButton:SetPoint('TOPLEFT', panel, 'TOPLEFT', 0, 0); |
||||||
|
scrollBar.ScrollUpButton:SetPoint('TOPRIGHT', panel, 'TOPRIGHT', 0, 0); |
||||||
|
|
||||||
|
scrollBar.ScrollDownButton:SetPoint('BOTTOMLEFT', panel, 'BOTTOMLEFT', 0, 0); |
||||||
|
scrollBar.ScrollDownButton:SetPoint('BOTTOMRIGHT', panel, 'BOTTOMRIGHT', 0, 0); |
||||||
|
|
||||||
|
scrollBar:SetPoint('TOPLEFT', scrollBar.ScrollUpButton, 'BOTTOMLEFT', 0, 1); |
||||||
|
scrollBar:SetPoint('TOPRIGHT', scrollBar.ScrollUpButton, 'BOTTOMRIGHT', 0, 1); |
||||||
|
scrollBar:SetPoint('BOTTOMLEFT', scrollBar.ScrollDownButton, 'TOPLEFT', 0, -1); |
||||||
|
scrollBar:SetPoint('BOTTOMRIGHT', scrollBar.ScrollDownButton, 'TOPRIGHT', 0, -1); |
||||||
|
end |
||||||
|
|
||||||
|
return scrollBar, panel; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,173 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Spell', 2; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then |
||||||
|
return |
||||||
|
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; |
||||||
|
local editBox = self:EditBox(parent, width, height, '', spellValidator or self.Util.spellValidator); |
||||||
|
editBox:SetTextInsets(iconSize + 7, 3, 3, 3); |
||||||
|
|
||||||
|
local iconFrame = self:Panel(editBox, iconSize, iconSize); |
||||||
|
self:GlueLeft(iconFrame, editBox, 2, 0, true); |
||||||
|
|
||||||
|
local icon = self:Texture(iconFrame, iconSize, iconSize, 134400); |
||||||
|
icon:SetAllPoints(); |
||||||
|
|
||||||
|
editBox.icon = icon; |
||||||
|
iconFrame.editBox = editBox; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
local iconFrame = self:Panel(frame, iconSize, iconSize); |
||||||
|
self:GlueLeft(iconFrame, frame, 2, 0, true); |
||||||
|
|
||||||
|
local icon = self:Texture(iconFrame, iconSize, iconSize); |
||||||
|
icon:SetAllPoints(); |
||||||
|
|
||||||
|
local btn = self:SquareButton(frame, iconSize, iconSize, 'DELETE'); |
||||||
|
StdUi:GlueRight(btn, frame, -3, 0, true); |
||||||
|
|
||||||
|
local text = self:Label(frame); |
||||||
|
text:SetPoint('LEFT', icon, 'RIGHT', 3, 0); |
||||||
|
text:SetPoint('RIGHT', btn, 'RIGHT', -3, 0); |
||||||
|
|
||||||
|
frame.removeBtn = btn; |
||||||
|
frame.icon = icon; |
||||||
|
frame.text = text; |
||||||
|
|
||||||
|
btn.parent = frame; |
||||||
|
|
||||||
|
iconFrame.widget = frame; |
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- SpellCheckbox |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
local SpellCheckboxMethods = { |
||||||
|
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 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; |
||||||
|
local checkbox = self:Checkbox(parent, '', width, height); |
||||||
|
checkbox.spellId = nil; |
||||||
|
checkbox.spellName = ''; |
||||||
|
|
||||||
|
local iconFrame = self:Panel(checkbox, iconSize, iconSize); |
||||||
|
iconFrame:SetPoint('LEFT', checkbox.target, 'RIGHT', 5, 0); |
||||||
|
|
||||||
|
local icon = self:Texture(iconFrame, iconSize, iconSize); |
||||||
|
icon:SetAllPoints(); |
||||||
|
|
||||||
|
checkbox.icon = icon; |
||||||
|
|
||||||
|
checkbox.text:SetPoint('LEFT', iconFrame, 'RIGHT', 5, 0); |
||||||
|
|
||||||
|
for k, v in pairs(SpellCheckboxMethods) do |
||||||
|
checkbox[k] = v; |
||||||
|
end |
||||||
|
|
||||||
|
for k, v in pairs(SpellCheckboxEvents) do |
||||||
|
checkbox:SetScript(k, v); |
||||||
|
end |
||||||
|
|
||||||
|
return checkbox; |
||||||
|
end; |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,223 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Tab', 4; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
--- TabPanel |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
local TabPanelMethods = { |
||||||
|
--- Runs callback thru all tabs, if callback returns truthy value, enumeration stops and function returns result |
||||||
|
EnumerateTabs = function(self, callback, ...) |
||||||
|
local result; |
||||||
|
|
||||||
|
for i = 1, #self.tabs do |
||||||
|
local tab = self.tabs[i]; |
||||||
|
result = callback(tab, self, i, ...); |
||||||
|
if result then |
||||||
|
break |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return result; |
||||||
|
end, |
||||||
|
|
||||||
|
HideAllFrames = function(self) |
||||||
|
for _, tab in pairs(self.tabs) do |
||||||
|
if tab.frame then |
||||||
|
tab.frame:Hide(); |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
DrawButtons = function(self) |
||||||
|
local prevBtn; |
||||||
|
for _, tab in pairs(self.tabs) do |
||||||
|
if tab.button then |
||||||
|
tab.button:Hide(); |
||||||
|
end |
||||||
|
|
||||||
|
local btn = tab.button; |
||||||
|
local btnContainer = self.buttonContainer; |
||||||
|
|
||||||
|
if not btn then |
||||||
|
btn = self.stdUi:Button(btnContainer, nil, self.buttonHeight); |
||||||
|
tab.button = btn; |
||||||
|
btn.tabFrame = self; |
||||||
|
|
||||||
|
btn:SetScript('OnClick', function(bt) |
||||||
|
bt.tabFrame:SelectTab(bt.tab.name); |
||||||
|
end); |
||||||
|
end |
||||||
|
|
||||||
|
btn.tab = tab; |
||||||
|
btn:SetText(tab.title); |
||||||
|
btn:ClearAllPoints(); |
||||||
|
|
||||||
|
if self.vertical then |
||||||
|
btn:SetWidth(self.buttonWidth); |
||||||
|
else |
||||||
|
self.stdUi:ButtonAutoWidth(btn); |
||||||
|
end |
||||||
|
|
||||||
|
if self.vertical then |
||||||
|
if not prevBtn then |
||||||
|
self.stdUi:GlueTop(btn, btnContainer, 0, 0, 'CENTER'); |
||||||
|
else |
||||||
|
self.stdUi:GlueBelow(btn, prevBtn, 0, -1); |
||||||
|
end |
||||||
|
else |
||||||
|
if not prevBtn then |
||||||
|
self.stdUi:GlueTop(btn, btnContainer, 0, 0, 'LEFT'); |
||||||
|
else |
||||||
|
self.stdUi:GlueRight(btn, prevBtn, 5, 0); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
btn:Show(); |
||||||
|
prevBtn = btn; |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
DrawFrames = function(self) |
||||||
|
for _, tab in pairs(self.tabs) do |
||||||
|
if not tab.frame then |
||||||
|
tab.frame = self.stdUi:Frame(self.container); |
||||||
|
end |
||||||
|
|
||||||
|
tab.frame:ClearAllPoints(); |
||||||
|
tab.frame:SetAllPoints(); |
||||||
|
|
||||||
|
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, |
||||||
|
|
||||||
|
GetTabByName = function(self, name) |
||||||
|
for _, tab in pairs(self.tabs) do |
||||||
|
if tab.name == name then |
||||||
|
return tab; |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
SelectTab = function(self, name) |
||||||
|
self.selected = name; |
||||||
|
if self.selectedTab then |
||||||
|
self.selectedTab.button:Enable(); |
||||||
|
end |
||||||
|
|
||||||
|
self:HideAllFrames(); |
||||||
|
local foundTab = self:GetTabByName(name); |
||||||
|
|
||||||
|
if foundTab.name == name and foundTab.frame then |
||||||
|
foundTab.button:Disable(); |
||||||
|
foundTab.frame:Show(); |
||||||
|
self.selectedTab = foundTab; |
||||||
|
return true; |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
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(); |
||||||
|
if #tabFrame.tabs > 0 then |
||||||
|
tabFrame:SelectTab(tabFrame.tabs[1].name); |
||||||
|
end |
||||||
|
|
||||||
|
return tabFrame; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,145 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi 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}, |
||||||
|
--- }; |
||||||
|
--- local data { |
||||||
|
--- {name = 'Item one', price = 12.22}, |
||||||
|
--- {name = 'Item two', price = 11.11}, |
||||||
|
--- {name = 'Item three', price = 10.12}, |
||||||
|
--- } |
||||||
|
|
||||||
|
local TableMethods = { |
||||||
|
SetColumns = function(self, columns) |
||||||
|
self.columns = columns; |
||||||
|
end, |
||||||
|
|
||||||
|
SetData = function(self, data) |
||||||
|
self.tableData = data; |
||||||
|
end, |
||||||
|
|
||||||
|
AddRow = function(self, row) |
||||||
|
if not self.tableData then |
||||||
|
self.tableData = {}; |
||||||
|
end |
||||||
|
|
||||||
|
TableInsert(self.tableData, row); |
||||||
|
end, |
||||||
|
|
||||||
|
DrawHeaders = function(self) |
||||||
|
if not self.headers then |
||||||
|
self.headers = {}; |
||||||
|
end |
||||||
|
|
||||||
|
local marginLeft = 0; |
||||||
|
for i = 1, #self.columns do |
||||||
|
local col = self.columns[i]; |
||||||
|
|
||||||
|
if col.header and StringLength(col.header) > 0 then |
||||||
|
if not self.headers[i] then |
||||||
|
self.headers[i] = { |
||||||
|
text = self.stdUi:FontString(self, ''), |
||||||
|
}; |
||||||
|
end |
||||||
|
|
||||||
|
local column = self.headers[i]; |
||||||
|
|
||||||
|
column.text:SetText(col.header); |
||||||
|
column.text:SetWidth(col.width); |
||||||
|
column.text:SetHeight(self.rowHeight); |
||||||
|
column.text:ClearAllPoints(); |
||||||
|
if col.align then |
||||||
|
column.text:SetJustifyH(col.align); |
||||||
|
end |
||||||
|
|
||||||
|
self.stdUi:GlueTop(column.text, self, marginLeft, 0, 'LEFT'); |
||||||
|
marginLeft = marginLeft + col.width; |
||||||
|
|
||||||
|
column.index = col.index |
||||||
|
column.width = col.width |
||||||
|
end |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
DrawData = function(self) |
||||||
|
if not self.rows then |
||||||
|
self.rows = {}; |
||||||
|
end |
||||||
|
|
||||||
|
local marginTop = -self.rowHeight; |
||||||
|
for y = 1, #self.tableData do |
||||||
|
local row = self.tableData[y]; |
||||||
|
|
||||||
|
local marginLeft = 0; |
||||||
|
for x = 1, #self.columns do |
||||||
|
local col = self.columns[x]; |
||||||
|
|
||||||
|
if not self.rows[y] then |
||||||
|
self.rows[y] = {}; |
||||||
|
end |
||||||
|
|
||||||
|
if not self.rows[y][x] then |
||||||
|
self.rows[y][x] = { |
||||||
|
text = self.stdUi:FontString(self, ''); |
||||||
|
}; |
||||||
|
end |
||||||
|
|
||||||
|
local cell = self.rows[y][x]; |
||||||
|
|
||||||
|
cell.text:SetText(row[col.index]); |
||||||
|
cell.text:SetWidth(col.width); |
||||||
|
cell.text:SetHeight(self.rowHeight); |
||||||
|
cell.text:ClearAllPoints(); |
||||||
|
if col.align then |
||||||
|
cell.text:SetJustifyH(col.align); |
||||||
|
end |
||||||
|
|
||||||
|
self.stdUi:GlueTop(cell.text, self, marginLeft, marginTop, 'LEFT'); |
||||||
|
marginLeft = marginLeft + col.width; |
||||||
|
end |
||||||
|
|
||||||
|
marginTop = marginTop - self.rowHeight; |
||||||
|
end |
||||||
|
end, |
||||||
|
|
||||||
|
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); |
||||||
|
panel:DrawTable(); |
||||||
|
|
||||||
|
return panel; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,174 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi 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, |
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
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 |
||||||
|
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; |
||||||
|
|
||||||
|
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'); |
||||||
|
|
||||||
|
tip.padding = self.config.tooltip.padding; |
||||||
|
|
||||||
|
tip.text = self:FontString(tip, ''); |
||||||
|
self:GlueTop(tip.text, tip, tip.padding, -tip.padding, 'LEFT'); |
||||||
|
|
||||||
|
for k, v in pairs(FrameTooltipMethods) do |
||||||
|
tip[k] = v; |
||||||
|
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 |
||||||
|
text(tip); |
||||||
|
end |
||||||
|
|
||||||
|
if automatic then |
||||||
|
for k, v in pairs(FrameTooltipEvents) do |
||||||
|
owner:HookScript(k, v); |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return tip; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,121 @@ |
|||||||
|
--- @type StdUi |
||||||
|
local StdUi = LibStub and LibStub('StdUi', true); |
||||||
|
if not StdUi then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local module, version = 'Window', 5; |
||||||
|
if not StdUi:UpgradeNeeded(module, version) then return end; |
||||||
|
|
||||||
|
--- @return Frame |
||||||
|
function StdUi:Window(parent, width, height, title) |
||||||
|
parent = parent or UIParent; |
||||||
|
local frame = self:PanelWithTitle(parent, width, height, title); |
||||||
|
frame:SetClampedToScreen(true); |
||||||
|
frame.titlePanel.isWidget = false; |
||||||
|
self:MakeDraggable(frame); -- , frame.titlePanel |
||||||
|
|
||||||
|
local closeBtn = self:Button(frame, 16, 16, 'X'); |
||||||
|
closeBtn.text:SetFontSize(12); |
||||||
|
closeBtn.isWidget = false; |
||||||
|
self:GlueTop(closeBtn, frame, -10, -10, 'RIGHT'); |
||||||
|
|
||||||
|
closeBtn:SetScript('OnClick', function(self) |
||||||
|
self:GetParent():Hide(); |
||||||
|
end); |
||||||
|
|
||||||
|
frame.closeBtn = closeBtn; |
||||||
|
|
||||||
|
function frame:SetWindowTitle(t) |
||||||
|
self.titlePanel.label:SetText(t); |
||||||
|
end |
||||||
|
|
||||||
|
-- Resizable window shortcut |
||||||
|
function frame:MakeResizable(direction) |
||||||
|
StdUi:MakeResizable(frame, direction); |
||||||
|
return frame; |
||||||
|
end |
||||||
|
|
||||||
|
return frame; |
||||||
|
end |
||||||
|
|
||||||
|
-- Reusing dialogs |
||||||
|
StdUi.dialogs = {}; |
||||||
|
--- @return Frame |
||||||
|
function StdUi:Dialog(title, message, dialogId) |
||||||
|
local window; |
||||||
|
if dialogId and self.dialogs[dialogId] then |
||||||
|
window = self.dialogs[dialogId]; |
||||||
|
else |
||||||
|
window = self:Window(nil, self.config.dialog.width, self.config.dialog.height, title); |
||||||
|
window:SetPoint('CENTER'); |
||||||
|
window:SetFrameStrata('DIALOG'); |
||||||
|
end |
||||||
|
|
||||||
|
if window.messageLabel then |
||||||
|
window.messageLabel:SetText(message); |
||||||
|
else |
||||||
|
window.messageLabel = self:Label(window, message); |
||||||
|
window.messageLabel:SetJustifyH('MIDDLE'); |
||||||
|
self:GlueAcross(window.messageLabel, window, 5, -10, -5, 5); |
||||||
|
end |
||||||
|
|
||||||
|
window:Show(); |
||||||
|
|
||||||
|
if dialogId then |
||||||
|
self.dialogs[dialogId] = window; |
||||||
|
end |
||||||
|
|
||||||
|
return window; |
||||||
|
end |
||||||
|
|
||||||
|
--- Dialog with additional buttons, buttons can be like this |
||||||
|
--- local btn = { |
||||||
|
--- ok = { |
||||||
|
--- text = 'OK', |
||||||
|
--- onClick = function() end |
||||||
|
--- }, |
||||||
|
--- cancel = { |
||||||
|
--- text = 'Cancel', |
||||||
|
--- onClick = function() end |
||||||
|
--- } |
||||||
|
--- } |
||||||
|
--- @return Frame |
||||||
|
function StdUi:Confirm(title, message, buttons, dialogId) |
||||||
|
local window = self:Dialog(title, message, dialogId); |
||||||
|
|
||||||
|
if buttons and not window.buttons then |
||||||
|
window.buttons = {}; |
||||||
|
|
||||||
|
local btnCount = self.Util.tableCount(buttons); |
||||||
|
|
||||||
|
local btnMargin = self.config.dialog.button.margin; |
||||||
|
local btnWidth = self.config.dialog.button.width; |
||||||
|
local btnHeight = self.config.dialog.button.height; |
||||||
|
|
||||||
|
local totalWidth = btnCount * btnWidth + (btnCount - 1) * btnMargin; |
||||||
|
local leftMargin = math.floor((self.config.dialog.width - totalWidth) / 2); |
||||||
|
|
||||||
|
local i = 0; |
||||||
|
for k, btnDefinition in pairs(buttons) do |
||||||
|
local btn = self:Button(window, btnWidth, btnHeight, btnDefinition.text); |
||||||
|
btn.window = window; |
||||||
|
|
||||||
|
self:GlueBottom(btn, window, leftMargin + (i * (btnWidth + btnMargin)), 10, 'LEFT'); |
||||||
|
|
||||||
|
if btnDefinition.onClick then |
||||||
|
btn:SetScript('OnClick', btnDefinition.onClick); |
||||||
|
end |
||||||
|
|
||||||
|
window.buttons[k] = btn; |
||||||
|
i = i + 1; |
||||||
|
end |
||||||
|
|
||||||
|
window.messageLabel:ClearAllPoints(); |
||||||
|
self:GlueAcross(window.messageLabel, window, 5, -10, -5, 5 + btnHeight + 5); |
||||||
|
end |
||||||
|
|
||||||
|
return window; |
||||||
|
end |
||||||
|
|
||||||
|
StdUi:RegisterModule(module, version); |
@ -0,0 +1,417 @@ |
|||||||
|
local Tinkr, Bastion = ... |
||||||
|
|
||||||
|
Tinkr:require("scripts.bastion.ui", Bastion) |
||||||
|
|
||||||
|
local ShadowModule = Bastion.Module:New('shadow') |
||||||
|
local Evaluator = Tinkr.Util.Evaluator |
||||||
|
local Player = Bastion.UnitManager:Get('player') |
||||||
|
local None = Bastion.UnitManager:Get('none') |
||||||
|
local Target = Bastion.UnitManager:Get('target') |
||||||
|
|
||||||
|
local myconf = Tinkr.Util.Config:New('shadow_priest') -- for saving variables |
||||||
|
|
||||||
|
local ArcaneTorrent = Bastion.SpellBook:GetSpell(232633) |
||||||
|
local AutoAttack = Bastion.SpellBook:GetSpell(6603) |
||||||
|
local AngelicFeather = Bastion.SpellBook:GetSpell(121536) |
||||||
|
local DesperatePrayer = Bastion.SpellBook:GetSpell(19236) |
||||||
|
local DispelMagic = Bastion.SpellBook:GetSpell(528) |
||||||
|
local DominateMind = Bastion.SpellBook:GetSpell(205364) |
||||||
|
local Fade = Bastion.SpellBook:GetSpell(586) |
||||||
|
local FlashHeal = Bastion.SpellBook:GetSpell(2061) |
||||||
|
local LeapofFaith = Bastion.SpellBook:GetSpell(73325) |
||||||
|
local Levitate = Bastion.SpellBook:GetSpell(1706) |
||||||
|
local MassDispel = Bastion.SpellBook:GetSpell(32375) |
||||||
|
local MindBlast = Bastion.SpellBook:GetSpell(8092) |
||||||
|
local MindSoothe = Bastion.SpellBook:GetSpell(453) |
||||||
|
local MindVision = Bastion.SpellBook:GetSpell(2096) |
||||||
|
local Mindgames = Bastion.SpellBook:GetSpell(375901) |
||||||
|
local PowerInfusion = Bastion.SpellBook:GetSpell(10060) |
||||||
|
local PowerWordFortitude = Bastion.SpellBook:GetSpell(21562) |
||||||
|
local PowerWordShield = Bastion.SpellBook:GetSpell(17) |
||||||
|
local PsychicScream = Bastion.SpellBook:GetSpell(8122) |
||||||
|
local Resurrection = Bastion.SpellBook:GetSpell(2006) |
||||||
|
local ShadowWordDeath = Bastion.SpellBook:GetSpell(32379) |
||||||
|
local ShadowWordPain = Bastion.SpellBook:GetSpell(589) |
||||||
|
local VampiricEmbrace = Bastion.SpellBook:GetSpell(15286) |
||||||
|
local DarkAscension = Bastion.SpellBook:GetSpell(391109) |
||||||
|
local DevouringPlague = Bastion.SpellBook:GetSpell(335467) |
||||||
|
local Dispersion = Bastion.SpellBook:GetSpell(47585) |
||||||
|
local Halo = Bastion.SpellBook:GetSpell(120644) |
||||||
|
local MindFlay = Bastion.SpellBook:GetSpell(15407) |
||||||
|
local MindFlayInsanity = Bastion.SpellBook:GetSpell(391399) |
||||||
|
local MindSpike = Bastion.SpellBook:GetSpell(73510) |
||||||
|
local Mindbender = Bastion.SpellBook:GetSpell(200174) |
||||||
|
local ShadowCrash = Bastion.SpellBook:GetSpell(205385) |
||||||
|
local Shadowform = Bastion.SpellBook:GetSpell(232698) |
||||||
|
local Silence = Bastion.SpellBook:GetSpell(15487) |
||||||
|
local SurgeofDarkness = Bastion.SpellBook:GetSpell(87160) |
||||||
|
local VampiricTouch = Bastion.SpellBook:GetSpell(34914) |
||||||
|
local VoidTorrent = Bastion.SpellBook:GetSpell(263165) |
||||||
|
|
||||||
|
local function checktotem(totemname) |
||||||
|
for index = 1, MAX_TOTEMS do |
||||||
|
local haveTotem, totemName, startTime, duration, icon = GetTotemInfo(index) |
||||||
|
if not totemName then return false end |
||||||
|
if string.find(totemName, totemname) then |
||||||
|
return true |
||||||
|
end |
||||||
|
end |
||||||
|
return false |
||||||
|
end |
||||||
|
|
||||||
|
local InterruptTarget = Bastion.UnitManager:CreateCustomUnit('silence', function(unit) |
||||||
|
local interrupt = nil |
||||||
|
Bastion.UnitManager:EnumEnemies(function(unit) |
||||||
|
if unit:IsDead() then return false end |
||||||
|
if not Player:CanSee(unit) then return false end |
||||||
|
if Player:GetDistance(unit) > 40 then return false end |
||||||
|
if Player:InMelee(unit) and unit:IsInterruptible(5) and Player:IsFacing(unit) then |
||||||
|
interrupt = unit |
||||||
|
return true |
||||||
|
end |
||||||
|
end) |
||||||
|
if interrupt == nil then |
||||||
|
interrupt = None |
||||||
|
end |
||||||
|
return interrupt |
||||||
|
end) |
||||||
|
|
||||||
|
local Tank = Bastion.UnitManager:CreateCustomUnit('tank', function(unit) |
||||||
|
local tank = nil |
||||||
|
Bastion.UnitManager:EnumFriends(function(unit) |
||||||
|
if Player:GetDistance(unit) > 40 then return false end |
||||||
|
if not Player:CanSee(unit) then return false end |
||||||
|
if unit:IsDead() then return false end |
||||||
|
if unit:IsTank() then |
||||||
|
tank = unit |
||||||
|
return true |
||||||
|
end |
||||||
|
return false |
||||||
|
end) |
||||||
|
if tank == nil then |
||||||
|
tank = None |
||||||
|
end |
||||||
|
return tank |
||||||
|
end) |
||||||
|
|
||||||
|
local Explosive = Bastion.UnitManager:CreateCustomUnit('explosive', function(unit) |
||||||
|
local explosive = nil |
||||||
|
Bastion.UnitManager:EnumEnemies(function(unit) |
||||||
|
if unit:IsDead() then return false end |
||||||
|
if not Player:CanSee(unit) then return false end |
||||||
|
if Player:GetDistance(unit) > 40 then return false end |
||||||
|
if Player:InMelee(unit) and unit:GetID() == 120651 and Player:IsFacing(unit) then |
||||||
|
explosive = unit |
||||||
|
return true |
||||||
|
end |
||||||
|
end) |
||||||
|
if explosive == nil then |
||||||
|
explosive = None |
||||||
|
end |
||||||
|
return explosive |
||||||
|
end) |
||||||
|
|
||||||
|
local RestingAPL = Bastion.APL:New('resting') |
||||||
|
local OpenerAPL = Bastion.APL:New('opener') |
||||||
|
local STAPL = Bastion.APL:New('st') |
||||||
|
local AOEAPL = Bastion.APL:New('aoe') |
||||||
|
local UtilityAPL = Bastion.APL:New('utility') |
||||||
|
|
||||||
|
-- Resting - Out of Combat Actions |
||||||
|
local usepwf = myconf:Read('pwf') |
||||||
|
local useshadowform = myconf:Read('shadowform') |
||||||
|
RestingAPL:AddSpell( |
||||||
|
PowerWordFortitude:CastableIf(function(self) |
||||||
|
return usepwf == 'yes' and self:IsKnownAndUsable() and |
||||||
|
not Player:IsCastingOrChanneling() and not Player:GetAuras():FindMy(PowerWordFortitude):IsUp() and not IsMounted() |
||||||
|
end):SetTarget(Player) |
||||||
|
) |
||||||
|
RestingAPL:AddSpell( |
||||||
|
Shadowform:CastableIf(function(self) |
||||||
|
return useshadowform == 'yes' and self:IsKnownAndUsable() and |
||||||
|
not Player:IsCastingOrChanneling() and not Player:GetAuras():FindMy(Shadowform):IsUp() and not IsMounted() |
||||||
|
end):SetTarget(Player) |
||||||
|
) |
||||||
|
|
||||||
|
-- Opener spells |
||||||
|
local opener = myconf:Read('opener') |
||||||
|
OpenerAPL:AddSpell( |
||||||
|
ShadowCrash:CastableIf(function(self) |
||||||
|
return opener == 'crash' and Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(None):OnCast(function(self) |
||||||
|
local loc = Target:GetPosition() |
||||||
|
self:Click(loc) |
||||||
|
end) |
||||||
|
) |
||||||
|
OpenerAPL:AddSpell( |
||||||
|
ShadowWordPain:CastableIf(function(self) |
||||||
|
return opener == 'swp' and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and not Target:GetAuras():FindMy(ShadowWordPain):IsUp() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
|
||||||
|
-- Utility spells |
||||||
|
UtilityAPL:AddSpell( |
||||||
|
Silence:CastableIf(function(self) |
||||||
|
return InterruptTarget:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(InterruptTarget) |
||||||
|
) |
||||||
|
|
||||||
|
-- Single Target Action Priority List |
||||||
|
-- Make sure your target has both Shadow Word: Pain and Vampiric Touch active. Prioritise Shadow Crash over Vampiric Touch for this. |
||||||
|
STAPL:AddSpell( |
||||||
|
ShadowWordPain:CastableIf(function(self) |
||||||
|
return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and not Target:GetAuras():FindMy(ShadowWordPain):IsUp() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Mindbender on cooldown |
||||||
|
STAPL:AddSpell( |
||||||
|
Mindbender:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Dark Ascension on cooldown |
||||||
|
STAPL:AddSpell( |
||||||
|
DarkAscension:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(Player) |
||||||
|
) |
||||||
|
-- Cast Power Infusion if Dark Ascension is active |
||||||
|
STAPL:AddSpell( |
||||||
|
PowerInfusion:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and Player:GetAuras():FindMy(DarkAscension):IsUp() |
||||||
|
end):SetTarget(Player) |
||||||
|
) |
||||||
|
-- Use offensive trinkets and potions. |
||||||
|
-- Cast Mind Blast if you have two charges |
||||||
|
STAPL:AddSpell( |
||||||
|
MindBlast:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and MindBlast:GetCharges() == 2 |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Shadow Word: Death if Mindbender is active |
||||||
|
STAPL:AddSpell( |
||||||
|
ShadowWordDeath:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and checktotem("Mindbender") |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Devouring Plague if you're close to capping Insanity or the debuff will expire |
||||||
|
STAPL:AddSpell( |
||||||
|
DevouringPlague:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and Player:GetPower(Enum.PowerType.Insanity) >= 80 |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Vampiric Touch if you are within pandemic range to maintain uptime |
||||||
|
STAPL:AddSpell( |
||||||
|
VampiricTouch:CastableIf(function(self) |
||||||
|
return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
and (not Target:GetAuras():FindMy(VampiricTouch):IsUp() or Target:GetAuras():FindMy(VampiricTouch):GetRemainingTime() <= 6.3) |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Shadow Word: Death if the target is below 20% hp |
||||||
|
STAPL:AddSpell( |
||||||
|
ShadowWordDeath:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and Target:GetHealthPercent() <= 20 |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Mind Blast |
||||||
|
STAPL:AddSpell( |
||||||
|
MindBlast:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Mindgames |
||||||
|
STAPL:AddSpell( |
||||||
|
Mindgames:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Shadow Crash, even if your DoTs are not close to expiring |
||||||
|
STAPL:AddSpell( |
||||||
|
ShadowCrash:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(None):OnCast(function(self) |
||||||
|
local loc = Target:GetPosition() |
||||||
|
self:Click(loc) |
||||||
|
end) |
||||||
|
) |
||||||
|
-- Cast Void Torrent |
||||||
|
STAPL:AddSpell( |
||||||
|
VoidTorrent:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Mind Spike if you have a Surge of Darkness proc |
||||||
|
STAPL:AddSpell( |
||||||
|
MindSpike:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and Player:GetAuras():FindMy(SurgeofDarkness):IsUp() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Mind Flay: Insanity |
||||||
|
STAPL:AddSpell( |
||||||
|
MindFlayInsanity:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Mind Spike |
||||||
|
STAPL:AddSpell( |
||||||
|
MindSpike:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Mind Flay |
||||||
|
STAPL:AddSpell( |
||||||
|
MindFlay:CastableIf(function(self) |
||||||
|
return Target:Exists() and self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
-- Cast Shadow Word: Pain if you're moving and nothing else is available |
||||||
|
STAPL:AddSpell( |
||||||
|
ShadowWordPain:CastableIf(function(self) |
||||||
|
return self:IsKnownAndUsable() and not Player:IsCastingOrChanneling() and not Player:IsMoving() |
||||||
|
end):SetTarget(Target) |
||||||
|
) |
||||||
|
|
||||||
|
-- Module that dictates APL flow |
||||||
|
ShadowModule:Sync(function() |
||||||
|
if not Player:IsAffectingCombat() then |
||||||
|
RestingAPL:Execute() |
||||||
|
end |
||||||
|
if not Player:IsAffectingCombat() and Target:Exists() then |
||||||
|
OpenerAPL:Execute() |
||||||
|
end |
||||||
|
if Player:IsAffectingCombat() and Target:Exists() then |
||||||
|
STAPL:Execute() |
||||||
|
end |
||||||
|
end) |
||||||
|
Bastion:Register(ShadowModule) |
||||||
|
|
||||||
|
local tab1 = |
||||||
|
{ |
||||||
|
layoutConfig = { padding = { top = 40 } }, |
||||||
|
rows = { |
||||||
|
[1] = { shadpr = { type = 'header', label = 'Open Combat' } }, |
||||||
|
|
||||||
|
[2] = { opener = { type = 'dropdown', label = 'Opener', column = 6, order = 1, |
||||||
|
options = { |
||||||
|
{ text = 'Shadow Word: Pain', value = 'swp'}, |
||||||
|
{ text = 'Shadow Crash', value = 'crash'}, |
||||||
|
{ text = 'None', value = 'none'}, |
||||||
|
}, |
||||||
|
initialValue = myconf:Read('opener', 'shield'), |
||||||
|
onValueChanged = function(_, value) myconf:Write('opener', value) end }, }, |
||||||
|
|
||||||
|
[3] = { swp1 = { type = 'checkbox', label = 'Spread SWP', column = 6, order = 1, |
||||||
|
initialValue = myconf:Read('swpspread', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('swpspread', flag) end }, |
||||||
|
|
||||||
|
swp2 = { type = 'slider', label = 'Max Targets', column = 6, order = 2, |
||||||
|
min = 1, max = 10, precision = 0, |
||||||
|
initialValue = myconf:Read('swptargets', 3), |
||||||
|
onValueChanged = function(_, value) myconf:Write('swptargets', value) end }, }, |
||||||
|
|
||||||
|
[4] = { shadpr = { type = 'header', label = 'Defensives' } }, |
||||||
|
|
||||||
|
[5] = { shield1 = { type = 'checkbox', label = 'Power Word: Shield', column = 6, order = 1, |
||||||
|
initialValue = myconf:Read('pws', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('pws', flag) end }, |
||||||
|
|
||||||
|
shield2 = { type = 'slider', label = 'Player Health', column = 6, order = 2, |
||||||
|
min = 1, max = 100, precision = 0, |
||||||
|
initialValue = myconf:Read('pwspercent', 35), |
||||||
|
onValueChanged = function(_, value) myconf:Write('pwspercent', value) end }, }, |
||||||
|
|
||||||
|
[6] = { dispersion1 = { type = 'checkbox', label = 'Dispersion', column = 6, order = 1, |
||||||
|
initialValue = myconf:Read('dispersion', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('dispersion', flag) end }, |
||||||
|
|
||||||
|
dispersion2 = { type = 'slider', label = 'Player Health', column = 6, order = 2, |
||||||
|
min = 1, max = 100, precision = 0, |
||||||
|
initialValue = myconf:Read('dispersionpercent', 35), |
||||||
|
onValueChanged = function(_, value) myconf:Write('dispersionpercent', value) end }, }, |
||||||
|
|
||||||
|
[7] = { shadpr = { type = 'header', label = 'Interrupts' } }, |
||||||
|
|
||||||
|
[8] = { silence = { type = 'checkbox', label = 'Silence on CD', column = 6, order = 1, |
||||||
|
initialValue = myconf:Read('silence', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('silence', flag) end }, |
||||||
|
|
||||||
|
horror = { type = 'checkbox', label = 'Psychic Horror on CD', column = 6, order = 2, |
||||||
|
initialValue = myconf:Read('horror', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('horror', flag) end }, }, |
||||||
|
|
||||||
|
[9] = { shadpr = { type = 'header', label = 'Purify Disease' } }, |
||||||
|
|
||||||
|
[10] = { incombat = { type = 'checkbox', label = 'In Combat', column = 6, order = 1, |
||||||
|
initialValue = myconf:Read('purifyic', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('purifyic', flag) end }, |
||||||
|
|
||||||
|
outcombat = { type = 'checkbox', label = 'Out of Combat', column = 6, order = 2, |
||||||
|
initialValue = myconf:Read('purifyooc', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('purifyooc', flag) end }, }, |
||||||
|
|
||||||
|
[11] = { shadpr = { type = 'header', label = 'Dispel Magic' } }, |
||||||
|
|
||||||
|
[12] = { incombat = { type = 'checkbox', label = 'In Combat', column = 12, order = 1, |
||||||
|
initialValue = myconf:Read('dispelic', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('dispelic', flag) end }, }, |
||||||
|
|
||||||
|
[13] = { shadpr = { type = 'header', label = 'Dispel Delay Time' } }, |
||||||
|
|
||||||
|
[14] = { delay = { type = 'slider', label = 'In seconds', column = 6, order = 1, |
||||||
|
min = 0.5, max = 2, precision = 1, |
||||||
|
initialValue = myconf:Read('dispeldelay', 1), |
||||||
|
onValueChanged = function(_, value) myconf:Write('dispeldelay', value) end }, }, |
||||||
|
|
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
local tab2 = |
||||||
|
{ |
||||||
|
layoutConfig = { padding = { top = 40 } }, |
||||||
|
rows = { |
||||||
|
[1] = { shadpr = { type = 'header', label = 'Player Healing' } }, |
||||||
|
|
||||||
|
[2] = { incombat = { type = 'checkbox', label = 'In Combat Healing', column = 6, order = 1, |
||||||
|
initialValue = myconf:Read('playericheal', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('playericheal', flag) end }, |
||||||
|
|
||||||
|
outcombat = { type = 'checkbox', label = 'Out of Combat Healing', column = 6, order = 2, |
||||||
|
initialValue = myconf:Read('playeroocheal', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('playeroocheal', flag) end }, }, |
||||||
|
|
||||||
|
[3] = { shadowmend1 = { type = 'checkbox', label = 'Shadow Mend', column = 6, order = 1, |
||||||
|
initialValue = myconf:Read('shadowmend', false), |
||||||
|
onValueChanged = function(_, flag) myconf:Write('shadowmend', flag) end }, |
||||||
|
|
||||||
|
shadowmend2 = { type = 'slider', label = 'Player Health', column = 6, order = 2, |
||||||
|
min = 1, max = 100, precision = 0, |
||||||
|
initialValue = myconf:Read('shadowmendpercent', 35), |
||||||
|
onValueChanged = function(_, value) myconf:Write('shadowmendpercent', value) end }, }, |
||||||
|
|
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
local shadpriestconfig = { |
||||||
|
layoutConfig = { padding = { top = 30 } }, |
||||||
|
rows = { |
||||||
|
[1] = { |
||||||
|
container = { |
||||||
|
type = 'tab', |
||||||
|
fullSize = true, |
||||||
|
tabs = { |
||||||
|
{ |
||||||
|
name = 'player', |
||||||
|
title = 'Player Settings', |
||||||
|
layout = tab1 |
||||||
|
}, |
||||||
|
{ |
||||||
|
name = 'group', |
||||||
|
title = 'Healing', |
||||||
|
layout = tab2 |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
Bastion.setupsettingsframe(shadpriestconfig, 'Rex Shadow Priest', 400, 600, 1.00, 1.00, 1.00) --, 0.00, 0.44, 0.87, 'enhsha') --Title of Settings Frame, Width, Height, ClassRGB, Rotation Name |
@ -0,0 +1,301 @@ |
|||||||
|
local Tinkr, Bastion = ... |
||||||
|
--local Common = Tinkr.Common |
||||||
|
--local Exports = Tinkr:require('Routine.Modules.Exports') |
||||||
|
local Command = Tinkr.Util.Commands:New('rex') |
||||||
|
--local OM = Tinkr.Util.ObjectManager |
||||||
|
|
||||||
|
--UI Co-ordinates |
||||||
|
local GUI = { points = {"CENTER"}, } |
||||||
|
local SETTINGS = { points = {"CENTER"}, } |
||||||
|
local ESP = { points = {"CENTER"}, } |
||||||
|
local BUTTONS = { points = {"CENTER"}, } |
||||||
|
|
||||||
|
Tinkr:require('scripts.bastion.libs.LibStub.LibStub', Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.StdUi", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.StdUiConfig", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.StdUiPosition", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.StdUiUtil", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.StdUiLayout", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.StdUiGrid", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.StdUiBuilder", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.StdUiBuilder", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Basic", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Window", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Button", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.EditBox", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.CheckBox", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Dropdown", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Autocomplete", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Label", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Scroll", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.ScrollTable", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Slider", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Tooltip", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Table", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.ProgressBar", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.ColorPicker", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Tab", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.Spell", Bastion) |
||||||
|
Tinkr:require("scripts.bastion.libs.StdUi.widgets.ContextMenu", Bastion) |
||||||
|
|
||||||
|
-- Class colours |
||||||
|
-- Death Knight 0.77 0.12 0.23 Red |
||||||
|
-- Demon Hunter 0.64 0.19 0.79 Dark Magenta |
||||||
|
-- Druid 1.00 0.49 0.04 Orange |
||||||
|
-- Hunter 0.67 0.83 0.45 Green |
||||||
|
-- Mage 0.25 0.78 0.92 Light Blue |
||||||
|
-- Monk 0.00 1.00 0.60 Spring Green |
||||||
|
-- Paladin 0.96 0.55 0.73 Pink |
||||||
|
-- Priest 1.00 1.00 1.00 White |
||||||
|
-- Rogue 1.00 0.96 0.41 Yellow |
||||||
|
-- Shaman 0.00 0.44 0.87 Blue |
||||||
|
-- Warlock 0.53 0.53 0.93 Purple |
||||||
|
-- Warrior 0.78 0.61 0.43 Tan |
||||||
|
|
||||||
|
--Initial Frame Setup |
||||||
|
function Bastion.setupsettingsframe(config, title, width, height, newr, newg, newb) |
||||||
|
local StdUi = LibStub('StdUi'):NewInstance() |
||||||
|
StdUi.config = { |
||||||
|
font = { |
||||||
|
family = 'GameFontNormal', |
||||||
|
size = 10, |
||||||
|
titleSize = 12, |
||||||
|
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 = newr, g = newg, b = newb, a = 1 }, |
||||||
|
} |
||||||
|
}, |
||||||
|
backdrop = { |
||||||
|
texture = [[Interface\Buttons\WHITE8X8]], |
||||||
|
panel = { r = 0.0588, g = 0.0588, b = 0, a = 0.8 }, |
||||||
|
slider = { r = newr, g = newg, b = newb, a = 1 }, |
||||||
|
|
||||||
|
highlight = { r = 0.40, g = 0.40, b = 0.40, 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 }, |
||||||
|
|
||||||
|
border = { r = 0.00, g = 0.00, b = 0.00, a = 1 }, |
||||||
|
borderDisabled = { r = 0.40, g = 0.40, b = 0.40, a = 1 } |
||||||
|
}, |
||||||
|
progressBar = { color = { r = newr, g = newg, b = newb, a = 0.5 }, }, |
||||||
|
highlight = { |
||||||
|
color = { r = newr, g = newg, b = newb, a = 0.4 }, |
||||||
|
blank = { r = 0, g = 0, b = 0, a = 0 } |
||||||
|
}, |
||||||
|
dialog = { |
||||||
|
width = 400, |
||||||
|
height = 100, |
||||||
|
button = { |
||||||
|
width = 100, |
||||||
|
height = 20, |
||||||
|
margin = 5 |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltip = { padding = 10 } |
||||||
|
} |
||||||
|
|
||||||
|
local settingsframe = StdUi:Window(UIParent, width, height, title) |
||||||
|
settingsframe:Hide() |
||||||
|
settingsframe:SetPoint(SETTINGS.points[1], SETTINGS.points[2], SETTINGS.points[3], SETTINGS.points[4], SETTINGS.points[5]) |
||||||
|
--settingsframe:SetFont(StdUi.config.font.family,StdUi.config.font.titleSize) |
||||||
|
StdUi:BuildWindow(settingsframe, config) |
||||||
|
StdUi:EasyLayout(settingsframe, { padding = { top = 40 } }) |
||||||
|
settingsframe:SetScript("OnMouseUp", function(self) |
||||||
|
self:StopMovingOrSizing() |
||||||
|
local a,b,c,d,e = self:GetPoint() |
||||||
|
SETTINGS.points = {a, nil, c, d, e} |
||||||
|
end) |
||||||
|
|
||||||
|
--Main Menu (Settings, Enable Rotation, ESP, Buttons) |
||||||
|
_G.buttons = {} |
||||||
|
local enabled = false |
||||||
|
local espenabled = false |
||||||
|
local espunitsenabled = false |
||||||
|
local espobjectsenabled = false |
||||||
|
buttons.aoeenabled = false |
||||||
|
buttons.buffsenabled = false |
||||||
|
buttons.cooldownsenabled = false |
||||||
|
buttons.dispelenabled = false |
||||||
|
buttons.dpsenabled = false |
||||||
|
buttons.healenabled = false |
||||||
|
buttons.rotationenabled = false |
||||||
|
local mainmenu = StdUi:Window(nil, 150, 310, title) |
||||||
|
mainmenu:Hide() |
||||||
|
mainmenu:SetPoint(GUI.points[1], GUI.points[2], GUI.points[3], GUI.points[4], GUI.points[5]) |
||||||
|
--mainmenu:SetFont(StdUi.config.font.family,StdUi.config.font.titleSize) |
||||||
|
mainmenu:SetUserPlaced(true) |
||||||
|
local aoebutton = StdUi:HighlightButton(mainmenu, 130, 20, 'AOE') |
||||||
|
StdUi:GlueTop(aoebutton, mainmenu, 10, -40, 'LEFT') |
||||||
|
local buffsbutton = StdUi:HighlightButton(mainmenu, 130, 20, 'Buffs') |
||||||
|
StdUi:GlueTop(buffsbutton, aoebutton, 0, -30, 'LEFT') |
||||||
|
local cooldownsbutton = StdUi:HighlightButton(mainmenu, 130, 20, 'Cooldowns') |
||||||
|
StdUi:GlueTop(cooldownsbutton, buffsbutton, 0, -30, 'LEFT') |
||||||
|
local dispelbutton = StdUi:HighlightButton(mainmenu, 130, 20, 'Dispel') |
||||||
|
StdUi:GlueTop(dispelbutton, cooldownsbutton, 0, -30, 'LEFT') |
||||||
|
local dpsbutton = StdUi:HighlightButton(mainmenu, 130, 20, 'DPS') |
||||||
|
StdUi:GlueTop(dpsbutton, dispelbutton, 0, -30, 'LEFT') |
||||||
|
local healbutton = StdUi:HighlightButton(mainmenu, 130, 20, 'Heal') |
||||||
|
StdUi:GlueTop(healbutton, dpsbutton, 0, -30, 'LEFT') |
||||||
|
local enablebutton = StdUi:HighlightButton(mainmenu, 130, 20, 'Rotation') |
||||||
|
StdUi:GlueTop(enablebutton, healbutton, 0, -30, 'LEFT') |
||||||
|
local espbutton = StdUi:Button(mainmenu, 130, 20, 'Open ESP') |
||||||
|
StdUi:GlueTop(espbutton, enablebutton, 0, -30, 'LEFT') |
||||||
|
local settingsbutton = StdUi:Button(mainmenu, 130, 20, 'Settings') |
||||||
|
StdUi:GlueTop(settingsbutton, espbutton, 0, -30, 'LEFT') |
||||||
|
mainmenu:SetScript("OnMouseUp", function(self) |
||||||
|
self:StopMovingOrSizing() |
||||||
|
local a,b,c,d,e = self:GetPoint() |
||||||
|
GUI.points = {a, nil, c, d, e} |
||||||
|
end) |
||||||
|
--ESP Menu |
||||||
|
local espmenu = StdUi:Window(nil, 200, 140, 'ESP Settings') |
||||||
|
espmenu:Hide() |
||||||
|
espmenu:SetPoint(ESP.points[1], ESP.points[2], ESP.points[3], ESP.points[4], ESP.points[5]) |
||||||
|
--espmenu:SetFont(StdUi.config.font.family,StdUi.config.font.titleSize) |
||||||
|
espmenu:SetUserPlaced(true) |
||||||
|
local enableespcheck = StdUi:Button(espmenu, 180, 20, 'Enable/Disable ESP'); |
||||||
|
StdUi:GlueTop(enableespcheck, espmenu, 10, -40, 'LEFT'); |
||||||
|
local espunitscheck = StdUi:Button(espmenu, 180, 20, 'Show Units'); |
||||||
|
StdUi:GlueTop(espunitscheck, enableespcheck, 0, -30, 'LEFT'); |
||||||
|
local espobjectscheck = StdUi:Button(espmenu, 180, 20, 'Show Objects'); |
||||||
|
StdUi:GlueTop(espobjectscheck, espunitscheck, 0, -30, 'LEFT'); |
||||||
|
espmenu:SetScript("OnMouseUp", function(self) |
||||||
|
self:StopMovingOrSizing() |
||||||
|
local a,b,c,d,e = self:GetPoint() |
||||||
|
ESP.points = {a, nil, c, d, e} |
||||||
|
end) |
||||||
|
--Open/Hide Settings Window OnClick |
||||||
|
settingsbutton:SetScript('OnClick', function () |
||||||
|
if settingsframe:IsShown() then |
||||||
|
settingsframe:Hide() |
||||||
|
else |
||||||
|
settingsframe:Show() |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable Rotation OnClick |
||||||
|
enablebutton:SetScript('OnClick', function () |
||||||
|
if enabled == false then |
||||||
|
Eval('RunMacroText("/bastion toggle")', 'bastion') |
||||||
|
enabled = true |
||||||
|
elseif enabled == true then |
||||||
|
Eval('RunMacroText("/bastion toggle")', 'bastion') |
||||||
|
enabled = false |
||||||
|
end |
||||||
|
end) |
||||||
|
--Open/Hide ESP Window OnClick |
||||||
|
espbutton:SetScript('OnClick', function () |
||||||
|
if espmenu:IsShown() then |
||||||
|
espmenu:Hide() |
||||||
|
else |
||||||
|
espmenu:Show() |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable ESP Draw OnClick |
||||||
|
enableespcheck:SetScript('OnClick', function () |
||||||
|
if espenabled == false then |
||||||
|
rexesp('enable', nil, nil) |
||||||
|
espenabled = true |
||||||
|
elseif espenabled == true then |
||||||
|
rexesp('disable', nil, nil) |
||||||
|
espenabled = false |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable ESP Units OnClick |
||||||
|
espunitscheck:SetScript('OnClick', function () |
||||||
|
if espunitsenabled == false then |
||||||
|
rexesp(nil, 'unitenable', nil) |
||||||
|
espunitsenabled = true |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable ESP Objects OnClick |
||||||
|
espobjectscheck:SetScript('OnClick', function () |
||||||
|
if espobjectsenabled == false then |
||||||
|
rexesp(nil, nil, 'objectenable') |
||||||
|
espobjectsenabled = true |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable AOE Button OnClick |
||||||
|
aoebutton:SetScript('OnClick', function() |
||||||
|
if buttons.aoeenabled == false then |
||||||
|
cooldownsbutton:LockHighlight() |
||||||
|
buttons.aoeenabled = true |
||||||
|
elseif buttons.aoeenabled == true then |
||||||
|
cooldownsbutton:UnlockHighlight() |
||||||
|
buttons.aoeenabled = false |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable Buffs Button OnClick |
||||||
|
buffsbutton:SetScript('OnClick', function() |
||||||
|
if buttons.buffsenabled == false then |
||||||
|
buffsbutton:LockHighlight() |
||||||
|
buttons.buffsenabled = true |
||||||
|
elseif buttons.buffsenabled == true then |
||||||
|
buffsbutton:UnlockHighlight() |
||||||
|
buttons.buffsenabled = false |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable Cooldown Button OnClick |
||||||
|
cooldownsbutton:SetScript('OnClick', function() |
||||||
|
if buttons.cooldownsenabled == false then |
||||||
|
cooldownsbutton:LockHighlight() |
||||||
|
buttons.cooldownsenabled = true |
||||||
|
elseif buttons.cooldownsenabled == true then |
||||||
|
cooldownsbutton:UnlockHighlight() |
||||||
|
buttons.cooldownsenabled = false |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable Dispel Button OnClick |
||||||
|
dispelbutton:SetScript('OnClick', function() |
||||||
|
if buttons.dispelenabled == false then |
||||||
|
dispelbutton:LockHighlight() |
||||||
|
buttons.dispelenabled = true |
||||||
|
elseif buttons.dispelenabled == true then |
||||||
|
dispelbutton:UnlockHighlight() |
||||||
|
buttons.dispelenabled = false |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable DPS Button OnClick |
||||||
|
dpsbutton:SetScript('OnClick', function() |
||||||
|
if buttons.dpsenabled == false then |
||||||
|
dpsbutton:LockHighlight() |
||||||
|
buttons.dpsenabled = true |
||||||
|
elseif buttons.dpsenabled == true then |
||||||
|
dpsbutton:UnlockHighlight() |
||||||
|
buttons.dpsenabled = false |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable Heal Button OnClick |
||||||
|
healbutton:SetScript('OnClick', function() |
||||||
|
if buttons.healenabled == false then |
||||||
|
healbutton:LockHighlight() |
||||||
|
buttons.healenabled = true |
||||||
|
elseif buttons.healenabled == true then |
||||||
|
healbutton:UnlockHighlight() |
||||||
|
buttons.healenabled = false |
||||||
|
end |
||||||
|
end) |
||||||
|
--Enable/Disable Rotation Button OnClick HIGHLIGHT ONLY |
||||||
|
enablebutton:SetScript('OnClick', function() |
||||||
|
if buttons.rotationenabled == false then |
||||||
|
enablebutton:LockHighlight() |
||||||
|
Eval('RunMacroText("/bastion toggle")', 'bastion') |
||||||
|
buttons.rotationenabled = true |
||||||
|
elseif buttons.rotationenabled == true then |
||||||
|
enablebutton:UnlockHighlight() |
||||||
|
Eval('RunMacroText("/bastion toggle")', 'bastion') |
||||||
|
buttons.rotationenabled = false |
||||||
|
end |
||||||
|
end) |
||||||
|
--Toggle Main Menu |
||||||
|
Command:Register({'open'}, function() |
||||||
|
if mainmenu:IsShown() then |
||||||
|
mainmenu:Hide() |
||||||
|
else |
||||||
|
mainmenu:Show() |
||||||
|
end |
||||||
|
end) |
||||||
|
|
||||||
|
end |
Loading…
Reference in new issue