--- @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);