StdUI libs and UI lua

Added StdUI libs and Ui.lua, also shadowpriest.lua to scripts
main
Ofrex 2 years ago
parent 98d46b6b42
commit 53679d748f
  1. BIN
      libs/LibStub/LibStub.luac
  2. BIN
      libs/StdUi.zip
  3. 2
      libs/StdUi/.gitignore
  4. 297
      libs/StdUi/StdUi.lua
  5. 30
      libs/StdUi/StdUi.xml
  6. 267
      libs/StdUi/StdUiBuilder.lua
  7. 95
      libs/StdUi/StdUiConfig.lua
  8. 139
      libs/StdUi/StdUiGrid.lua
  9. 211
      libs/StdUi/StdUiLayout.lua
  10. 133
      libs/StdUi/StdUiPosition.lua
  11. 318
      libs/StdUi/StdUiUtil.lua
  12. BIN
      libs/StdUi/media/Checkers.tga
  13. 285
      libs/StdUi/widgets/Autocomplete.lua
  14. 72
      libs/StdUi/widgets/Basic.lua
  15. 126
      libs/StdUi/widgets/Button.lua
  16. 288
      libs/StdUi/widgets/Checkbox.lua
  17. 323
      libs/StdUi/widgets/ColorPicker.lua
  18. 265
      libs/StdUi/widgets/ContextMenu.lua
  19. 283
      libs/StdUi/widgets/Dropdown.lua
  20. 390
      libs/StdUi/widgets/EditBox.lua
  21. 88
      libs/StdUi/widgets/Label.lua
  22. 77
      libs/StdUi/widgets/ProgressBar.lua
  23. 598
      libs/StdUi/widgets/Scroll.lua
  24. 778
      libs/StdUi/widgets/ScrollTable.lua
  25. 295
      libs/StdUi/widgets/Slider.lua
  26. 173
      libs/StdUi/widgets/Spell.lua
  27. 223
      libs/StdUi/widgets/Tab.lua
  28. 145
      libs/StdUi/widgets/Table.lua
  29. 174
      libs/StdUi/widgets/Tooltip.lua
  30. 121
      libs/StdUi/widgets/Window.lua
  31. 417
      scripts/shadowpriest.lua
  32. 301
      ui.lua

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

301
ui.lua

@ -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…
Cancel
Save