You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

327 lines
12 KiB

--========================================================--
-- Scorpio Element Panel --
-- --
-- Author : kurapica125@outlook.com --
-- Create Date : 2020/11/12 --
--========================================================--
--========================================================--
Scorpio "Scorpio.UI.ElementPanel" "1.0.0"
--========================================================--
__Sealed__() class "ElementPanel" (function(_ENV)
inherit "Frame" extend "ICountable"
export{ max = math.max }
local function onSizeChanged(self)
return self:Refresh()
end
local function adjustElement(element, self)
local id = element:GetID()
if not id then return end
element:SetSize(self.ElementWidth, self.ElementHeight)
local posX = (self.Orientation == Orientation.HORIZONTAL and (id - 1) % self.ColumnCount or floor((id - 1) / self.RowCount)) * (self.ElementWidth + self.HSpacing)
local posY = (self.Orientation == Orientation.HORIZONTAL and floor((id - 1) / self.ColumnCount) or (id - 1) % self.RowCount) * (self.ElementHeight + self.VSpacing)
element:ClearAllPoints()
if self.TopToBottom then
element:SetPoint("TOP", 0, - posY - self.MarginTop)
else
element:SetPoint("BOTTOM", 0, posY + self.MarginBottom)
end
if self.LeftToRight then
element:SetPoint("LEFT", posX + self.MarginLeft, 0)
else
element:SetPoint("RIGHT", - posX - self.MarginRight, 0)
end
end
local function adjustPanel(self)
if self.KeepMaxSize then
if not self.FixedWidth then
self:SetWidth(self.ColumnCount * self.ElementWidth + (self.ColumnCount - 1) * self.HSpacing + self.MarginLeft + self.MarginRight)
end
if not self.FixedHeight then
self:SetHeight(self.RowCount * self.ElementHeight + (self.RowCount - 1) * self.VSpacing + self.MarginTop + self.MarginBottom)
end
else
local i = self.Count
while i > 0 do
if self[i]:IsShown() then break end
i = i - 1
end
local row
local column
if self.Orientation == Orientation.HORIZONTAL then
row = ceil(i / self.ColumnCount)
column = row == 1 and i or self.ColumnCount
else
column = ceil(i / self.RowCount)
row = column == 1 and i or self.RowCount
end
if self.KeepColumnSize then
column = self.ColumnCount
if row == 0 then row = 1 end
end
if self.KeepRowSize then
row = self.RowCount
if column == 0 then column = 1 end
end
if row > 0 and column > 0 then
if not self.FixedWidth then
self:SetWidth(column * self.ElementWidth + (column - 1) * self.HSpacing + self.MarginLeft + self.MarginRight)
end
if not self.FixedHeight then
self:SetHeight(row * self.ElementHeight + (row - 1) * self.VSpacing + self.MarginTop + self.MarginBottom)
end
else
if not self.FixedWidth then
self:SetWidth(1)
end
if not self.FixedHeight then
self:SetHeight(1)
end
end
end
end
local function reduce(self, index)
index = index or self.RowCount * self.ColumnCount
if index < self.Count then
for i = self.Count, index + 1, -1 do
local ele = self[i]
-- still keep them to be re-use
ele:ClearAllPoints()
ele:Hide()
OnElementRemove(self, ele)
self.__ElementPanel_Count = i - 1
end
if self.FixedHeight or self.FixedWidth then
return self:Refresh()
else
return adjustPanel(self)
end
end
end
local function generate(self, index)
if self.ElementType and index > self.Count then
local ele
for i = self.Count + 1, index do
local ele = self[i]
if not ele then
ele = self.ElementType(self.ElementPrefix .. i, self)
self[i] = ele
ele:SetID(i)
OnElementCreated(self, ele)
end
ele:Show()
adjustElement(ele, self)
OnElementAdd(self, ele)
self.__ElementPanel_Count = i
end
if self.FixedHeight or self.FixedWidth then
return self:Refresh()
else
return adjustPanel(self)
end
end
end
local function nextItem(self, index)
index = index + 1
local ele = self[index]
if ele then return index, ele end
end
------------------------------------------------------
-- Event
------------------------------------------------------
-- Fired when an element is added
event "OnElementAdd"
-- Fired when an element is removed
event "OnElementRemove"
-- Fired when an element is created
event "OnElementCreated"
------------------------------------------------------
-- Method
------------------------------------------------------
function GetIterator(self, key)
return nextItem, self, tonumber(key) or 0
end
__AsyncSingle__()
function Refresh(self)
Next()
reduce(self)
if self.FixedHeight or self.FixedWidth then
self:SetAttribute("__ElementPanel_FixedSize", true)
self.OnSizeChanged = self.OnSizeChanged + onSizeChanged
local row
local column
if self.Orientation == Orientation.HORIZONTAL then
row = ceil(self.Count / self.ColumnCount)
column = row == 1 and self.Count or self.ColumnCount
else
column = ceil(self.Count / self.RowCount)
row = column == 1 and self.Count or self.RowCount
end
if self.FixedWidth then
local total = self:GetWidth()
self.ElementWidth = max(1, (total - self.MarginLeft - self.MarginRight - (column - 1) * self.HSpacing) / column)
end
if self.FixedHeight then
local total = self:GetHeight()
self.ElementHeight = max(1, (total - self.MarginTop - self.MarginBottom - (row - 1) * self.VSpacing) / row)
end
elseif self:GetAttribute("__ElementPanel_FixedSize") then
self:SetAttribute("__ElementPanel_FixedSize", nil)
self.OnSizeChanged = self.OnSizeChanged - onSizeChanged
end
self:Each(adjustElement, self)
adjustPanel(self)
end
------------------------------------------------------
-- Property
------------------------------------------------------
-- The Element accessor, used like obj.Element[i].
__Indexer__(NaturalNumber)
property "Elements" {
get = function(self, index)
if index >= 1 and index <= self.ColumnCount * self.RowCount then
if self[index] then return self[index] end
if self.ElementType then
generate(self, index)
return self[index]
else
return nil
end
end
end,
}
-- The columns's count
property "ColumnCount" { type = PositiveNumber, default = 8, handler = Refresh }
-- The row's count
property "RowCount" { type = PositiveNumber, default = 8, handler = Refresh }
-- The elements's max count
property "MaxCount" { Get = function(self) return self.ColumnCount * self.RowCount end }
-- The element's width
property "ElementWidth" { type = PositiveNumber, default = 16, handler = Refresh }
-- The element's height
property "ElementHeight" { type = PositiveNumber, default = 16, handler = Refresh }
-- The element's count
property "Count" {
type = NaturalNumber,
field = "__ElementPanel_Count",
set = function(self, cnt)
if cnt > self.RowCount * self.ColumnCount then
error("Count can't be more than "..self.RowCount * self.ColumnCount, 2)
end
if cnt > self.Count then
if self.ElementType then
generate(self, cnt)
else
error("ElementType not set.", 2)
end
elseif cnt < self.Count then
reduce(self, cnt)
end
end,
default = 0,
}
-- The orientation for elements
property "Orientation" { type = Orientation, default = Orientation.HORIZONTAL, handler = Refresh }
-- Whether the elements start from left to right
property "LeftToRight" { type = Boolean, default = true, handler = Refresh }
-- Whether the elements start from top to bottom
property "TopToBottom" { type = Boolean, default = true, handler = Refresh }
-- The element's type
property "ElementType" { type = ClassType }
-- The horizontal spacing
property "HSpacing" { type = Number, default = 0, handler = Refresh }
-- The vertical spacing
property "VSpacing" { type = Number, default = 0, handler = Refresh }
-- The top margin
property "MarginTop" { type = Number, default = 0, handler = Refresh }
-- The bottom margin
property "MarginBottom" { type = Number, default = 0, handler = Refresh }
-- The left margin
property "MarginLeft" { type = Number, default = 0, handler = Refresh }
-- The right margin
property "MarginRight" { type = Number, default = 0, handler = Refresh }
-- The prefix for the element's name
property "ElementPrefix" { type = String, default = "Element" }
-- Whether the elementPanel should keep it's max size
property "KeepMaxSize" { type = Boolean, handler = Refresh }
-- Whether keep the max size for columns
property "KeepColumnSize" { type = Boolean, handler = Refresh }
-- Whether keep the max size for rows
property "KeepRowSize" { type = Boolean, handler = Refresh }
-- Whether the panel's height is fixed, so the element height will be modified
property "FixedHeight" { type = Boolean, handler = Refresh }
-- Whether the panel's width is fixed, so the element width will be modified
property "FixedWidth" { type = Boolean, handler = Refresh }
end)