• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Lua] UI - FrameList

FrameList is a UI-Frame that contains 1 row or col of Frames. The user can view a part of the Frames added to the FrameList. With a bigger Size the FrameList can display more Frames at the same time. The part that can be seen is changed using a slider. Theoretically any Frame can be part of FrameList through some might need extra turning or not work well. FrameList does not have a background or border on it own.

Lua:
--[[
    FrameList V1.0 by Tasyen
    A Frame that contains 1 Row or Col of Frames, a fraction of the added Frames is displayed at once. The user can change the shown frames by scrolling a slider.
    FrameLists can also contain a FrameList.
    The displayed amount of Frames depends on the FrameList size, this Size only changes when changed with BlzFrameSetSize or better the special version added into this.

    function FrameList.create([horizontal, parent, createContext])
        creates a new FrameList
        horizontal (true) <-> (false/nil) ^v

    function FrameList.setSize(frameListTable, xSize, ySize)
        a custom Width seter, it makes the slider more accurate without the slider can not be clicked correctly.

    function FrameList.add(frameListTable, frame)
        adds frame to as last element of frameListTable

    function FrameList.remove(frameListTable, frame, noUpdate)
        removes frame (can be a number) from frameListTable, skip noUpdate that is only used from FrameList.destory

    function FrameList.destory(frameListTable)
        Destroys the frameListTable control Frames and hides and clears all points Frames in the FrameList

    function FrameList.setContentPoints(frameListTable)
        update the shown content, should be done automatic
--]]

BlzLoadTOCFile("war3mapimported\\FrameList.toc")
FrameList = {}

FrameList.Horizontal = {
    --1. to slider
    FirstActivePoint = FRAMEPOINT_BOTTOMLEFT,
    FirstPassivePoint = FRAMEPOINT_TOPLEFT,
    FirstOffsetX = 0,
    FirstOffsetY = 0,
    --2. to 1.
    SecondActivePoint = FRAMEPOINT_BOTTOMLEFT,
    SecondPassivePoint = FRAMEPOINT_BOTTOMRIGHT,
    SecondOffsetX = 0,
    SecondOffsetY = 0,
    --function to get size
    GetSize = BlzFrameGetWidth,
}

FrameList.Vertical = {
    FirstActivePoint = FRAMEPOINT_TOPRIGHT,
    FirstPassivePoint = FRAMEPOINT_TOPLEFT,
    FirstOffsetX = 0,
    FirstOffsetY = 0,
    SecondActivePoint = FRAMEPOINT_TOPRIGHT,
    SecondPassivePoint = FRAMEPOINT_BOTTOMRIGHT,
    SecondOffsetX = 0,
    SecondOffsetY = 0,
    GetSize = BlzFrameGetHeight,
}
function FrameList.create(horizontal, parent, createContext)
    local frameListTable = {}
    if not createContext then createContext = 0 end
    if not parent then parent = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0) end
    if not horizontal then
        frameListTable.Frame = BlzCreateFrame("FrameListV", parent, 0, createContext)
        frameListTable.Slider = BlzGetFrameByName("FrameListSliderV", createContext)
        frameListTable.Mode = FrameList.Vertical
    else
        frameListTable.Frame = BlzCreateFrame("FrameListH", parent, 0, createContext)
        frameListTable.Slider = BlzGetFrameByName("FrameListSliderH", createContext)
        frameListTable.Mode = FrameList.Horizontal
    end
    frameListTable.Content = {}
    FrameList[frameListTable.Slider] = frameListTable
    FrameList[frameListTable.Frame] = frameListTable
    frameListTable.SliderTrigger = CreateTrigger()
    frameListTable.SliderTriggerAction = TriggerAddAction(frameListTable.SliderTrigger, FrameList.SliderAction)
    BlzTriggerRegisterFrameEvent(frameListTable.SliderTrigger, frameListTable.Slider , FRAMEEVENT_SLIDER_VALUE_CHANGED)
    BlzTriggerRegisterFrameEvent(frameListTable.SliderTrigger, frameListTable.Slider , FRAMEEVENT_MOUSE_WHEEL)
    return frameListTable
end

function FrameList.setContentPoints(frameListTable)
    local sliderValue = math.tointeger( BlzFrameGetValue(frameListTable.Slider))
    local sizeFrameList = frameListTable.Mode.GetSize(frameListTable.Frame)
    local contentCount = #frameListTable.Content

    for index = 1, contentCount, 1 do
        local frame = frameListTable.Content[index]
        if index < sliderValue then
            --print("Hide Prev", index)
            BlzFrameSetVisible(frame, false)
        else
            local sizeFrame = frameListTable.Mode.GetSize(frame)
            sizeFrameList = sizeFrameList - sizeFrame
            BlzFrameClearAllPoints(frame)
            if index == sliderValue then           
                BlzFrameSetVisible(frame, true)
                BlzFrameSetPoint(frame, frameListTable.Mode.FirstActivePoint, frameListTable.Slider, frameListTable.Mode.FirstPassivePoint, frameListTable.Mode.FirstOffsetX, frameListTable.Mode.FirstOffsetY)
            else
                BlzFrameSetVisible(frame, sizeFrameList >= 0)
                BlzFrameSetPoint(frame, frameListTable.Mode.SecondActivePoint, frameListTable.Content[index - 1], frameListTable.Mode.SecondPassivePoint, frameListTable.Mode.SecondOffsetX, frameListTable.Mode.SecondOffsetY)
            end
        end
    end
end

function FrameList.setSize(frameListTable, xSize, ySize)
    BlzFrameSetSize(frameListTable.Frame, xSize, ySize)
    if frameListTable.Mode == FrameList.Horizontal then
        BlzFrameSetSize(frameListTable.Slider, xSize, 0.012) -- ySize of slider is constant in horizontal mode
    else
        BlzFrameSetSize(frameListTable.Slider, 0.012, ySize)
    end
end

function FrameList.add(frameListTable, frame)
    table.insert(frameListTable.Content, frame)
    BlzFrameSetParent(frame, frameListTable.Frame)
    BlzFrameSetMinMaxValue(frameListTable.Slider, 1, #frameListTable.Content)
    FrameList.setContentPoints(frameListTable)
end

function FrameList.remove(frameListTable, frame, noUpdate)
    local removed = nil
    if not frameListTable or #frameListTable.Content == 0 then return false end
    if not frame then
        removed = table.remove(frameListTable.Content)
    elseif type(frame) == "number" then
        removed = table.remove( frameListTable.Content, frame)
    else
        for index, value in ipairs(frameListTable.Content)
        do
            if frame == value then
                removed = table.remove(frameListTable.Content, index)
                break
            end
        end
    end

    if removed then
        BlzFrameClearAllPoints(removed)
        BlzFrameSetVisible(removed, false)
        if not noUpdate then
            BlzFrameSetMinMaxValue(frameListTable.Slider, 1, #frameListTable.Content)
            BlzFrameSetValue(frameListTable.Slider, 1)
            FrameList.setContentPoints(frameListTable)
        end
    end
    return #frameListTable.Content
end

function FrameList.SliderAction()
    local frame = BlzGetTriggerFrame()
    if GetLocalPlayer() == GetTriggerPlayer() then
        if BlzGetTriggerFrameEvent() == FRAMEEVENT_MOUSE_WHEEL then
            if BlzGetTriggerFrameValue() > 0 then
                BlzFrameSetValue(frame, BlzFrameGetValue(frame) + 1)
            else
                BlzFrameSetValue(frame, BlzFrameGetValue(frame) - 1)
            end
        end
        FrameList.setContentPoints(FrameList[frame])
    end
end

function FrameList.destory(frameListTable)
    FrameList[frameListTable.Slider] = nil
    FrameList[frameListTable.Frame] = nil
    TriggerRemoveAction(frameListTable.SliderTrigger, frameListTable.SliderTriggerAction)
    DestroyTrigger(frameListTable.SliderTrigger)
    BlzDestroyFrame(frameListTable.Frame)
    BlzDestroyFrame(frameListTable.Slider)
    frameListTable.Mode = nil
    frameListTable.Frame = nil
    repeat until not FrameList.remove(frameListTable, nil, true)
    frameListTable.Content = nil
 
    frameListTable.SliderTrigger = nil
    frameListTable.SliderTriggerAction = nil

end

When Using FrameList and Window together make sure that the window is big enough to contain the FrameList a simple way is to add the FrameList as a Tab and defining a size for the tab that is big enough for the FrameList.
Lua:
    local window = Window.create("Window FrameList")
    local mainList = FrameList.create()
    local windowTabTable = Window.addTab(window, mainList.Frame, "test", false, 0.171, 0.171)
    BlzFrameSetAbsPoint(window.WindowHead, FRAMEPOINT_TOP, 0.4, 0.55)

FrameList uses a TOC and a fdf.

FrameList FrameLists 2.jpg FrameList Col 2.jpg Window + FrameList 2.jpg
 

Attachments

  • FrameList.w3x
    23.1 KB · Views: 142
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
BlzLoadTOCFile loads a TOC-file, which contains a listing of custom fdfs (frame definition files).

More on the file and the function, refer to this very short tutorial.
Thanks a lot! I think that tutorial shouldn't have been archived. It explains a fundamental concept, albeit niche.
 
Level 39
Joined
Feb 27, 2007
Messages
5,010
Swap the +1 and -1 in this function, I believe:
Lua:
function FrameList.SliderAction()
    local frame = BlzGetTriggerFrame()
    if GetLocalPlayer() == GetTriggerPlayer() then
        if BlzGetTriggerFrameEvent() == FRAMEEVENT_MOUSE_WHEEL then
            if BlzGetTriggerFrameValue() > 0 then
                BlzFrameSetValue(frame, BlzFrameGetValue(frame) + 1)
            else
                BlzFrameSetValue(frame, BlzFrameGetValue(frame) - 1)
            end
        end
        FrameList.setContentPoints(FrameList[frame])
    end
end
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
Swap the +1 and -1 in this function, I believe:
Lua:
function FrameList.SliderAction()
    local frame = BlzGetTriggerFrame()
    if GetLocalPlayer() == GetTriggerPlayer() then
        if BlzGetTriggerFrameEvent() == FRAMEEVENT_MOUSE_WHEEL then
            if BlzGetTriggerFrameValue() > 0 then
                BlzFrameSetValue(frame, BlzFrameGetValue(frame) + 1)
            else
                BlzFrameSetValue(frame, BlzFrameGetValue(frame) - 1)
            end
        end
        FrameList.setContentPoints(FrameList[frame])
    end
end
No, that will also reverse the list and not just the slider.
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
Very well, I think I made it, apart from revert the vertical slider, I made a improved version of this that includes DebugUtils and Total Initialization:
Lua:
if Debug then Debug.beginFile("FrameList") end
--[[
    FrameList V1.0 by Tasyen
    A Frame that contains 1 Row or Col of Frames, a fraction of the added Frames is displayed at once. The user can change the shown frames by scrolling a slider.
    FrameLists can also contain a FrameList.
    The displayed amount of Frames depends on the FrameList size, this Size only changes when changed with BlzFrameSetSize or better the special version added into this.
--]]
OnInit("FrameList", function ()
    BlzLoadTOCFile("war3mapimported\\FrameList.toc")

    ---@class FrameList
    ---@field public Frame framehandle
    ---@field public Slider framehandle
    ---@field public Mode FrameListMode
    ---@field public Horizontal FrameListMode
    ---@field public Vertical FrameListMode
    ---@field private Content framehandle[]
    local FrameList = {}
    FrameList.__index = FrameList

    ---@class FrameListMode
    ---@field public FirstActivePoint framepointtype
    ---@field public FirstPassivePoint framepointtype
    ---@field public FirstOffsetX number
    ---@field public FirstOffsetY number
    ---@field public SecondActivePoint framepointtype
    ---@field public SecondPassivePoint framepointtype
    ---@field public SecondOffsetX number
    ---@field public SecondOffsetY number
    ---@field protected GetSize fun(frame: framehandle): number

    FrameList.Horizontal = {
        --1. to slider
        FirstActivePoint = FRAMEPOINT_BOTTOMLEFT,
        FirstPassivePoint = FRAMEPOINT_TOPLEFT,
        FirstOffsetX = 0,
        FirstOffsetY = 0,
        --2. to 1.
        SecondActivePoint = FRAMEPOINT_BOTTOMLEFT,
        SecondPassivePoint = FRAMEPOINT_BOTTOMRIGHT,
        SecondOffsetX = 0,
        SecondOffsetY = 0,
        --function to get size
        GetSize = BlzFrameGetWidth,
    }

    FrameList.Vertical = {
        FirstActivePoint = FRAMEPOINT_TOPRIGHT,
        FirstPassivePoint = FRAMEPOINT_TOPLEFT,
        FirstOffsetX = 0,
        FirstOffsetY = 0,
        SecondActivePoint = FRAMEPOINT_TOPRIGHT,
        SecondPassivePoint = FRAMEPOINT_BOTTOMRIGHT,
        SecondOffsetX = 0,
        SecondOffsetY = 0,
        GetSize = BlzFrameGetHeight,
    }

    local SliderTrigger = CreateTrigger()
    TriggerAddAction(SliderTrigger, function ()
        local frame = BlzGetTriggerFrame()
        if GetLocalPlayer() == GetTriggerPlayer() then
            if BlzGetTriggerFrameEvent() == FRAMEEVENT_MOUSE_WHEEL then
                if BlzGetTriggerFrameValue() > 0 then
                    BlzFrameSetValue(frame, BlzFrameGetValue(frame) - 1)
                else
                    BlzFrameSetValue(frame, BlzFrameGetValue(frame) + 1)
                end
            end
            FrameList[frame]:setContentPoints()
        end
    end)

    ---Creates a new FrameList horizontal (true) <-> (false/nil) ^v
    ---@param horizontal? boolean
    ---@param parent? framehandle
    ---@param createContext? integer
    ---@return FrameList
    function FrameList.create(horizontal, parent, createContext)
        local frameListTable = setmetatable({}, FrameList) ---@type FrameList
        createContext = createContext or 0
        parent = parent or BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
        if not horizontal then
            frameListTable.Frame = BlzCreateFrame("FrameListV", parent, 0, createContext)
            frameListTable.Slider = BlzGetFrameByName("FrameListSliderV", createContext)
            frameListTable.Mode = FrameList.Vertical
        else
            frameListTable.Frame = BlzCreateFrame("FrameListH", parent, 0, createContext)
            frameListTable.Slider = BlzGetFrameByName("FrameListSliderH", createContext)
            frameListTable.Mode = FrameList.Horizontal
        end
        BlzFrameSetParent(frameListTable.Slider, parent)
        frameListTable.Content = {}
        FrameList[frameListTable.Slider] = frameListTable
        FrameList[frameListTable.Frame] = frameListTable
        BlzTriggerRegisterFrameEvent(SliderTrigger, frameListTable.Slider , FRAMEEVENT_SLIDER_VALUE_CHANGED)
        BlzTriggerRegisterFrameEvent(SliderTrigger, frameListTable.Slider , FRAMEEVENT_MOUSE_WHEEL)
        return frameListTable
    end

    ---Update the shown content, should be done automatic
    function FrameList:setContentPoints()
        local sizeFrameList = self.Mode.GetSize(self.Frame)
        local contentCount = #self.Content
        local sliderValue = contentCount - math.tointeger(BlzFrameGetValue(self.Slider))

        for index = 1, contentCount, 1 do
            local frame = self.Content[index]
            if index < sliderValue then
                --print("Hide Prev", index)
                BlzFrameSetVisible(frame, false)
            else
                local sizeFrame = self.Mode.GetSize(frame)
                sizeFrameList = sizeFrameList - sizeFrame
                BlzFrameClearAllPoints(frame)
                if index == sliderValue then
                    BlzFrameSetVisible(frame, true)
                    BlzFrameSetPoint(frame, self.Mode.FirstActivePoint, self.Slider, self.Mode.FirstPassivePoint, self.Mode.FirstOffsetX, self.Mode.FirstOffsetY)
                else
                    BlzFrameSetVisible(frame, sizeFrameList >= 0)
                    BlzFrameSetPoint(frame, self.Mode.SecondActivePoint, self.Content[index - 1], self.Mode.SecondPassivePoint, self.Mode.SecondOffsetX, self.Mode.SecondOffsetY)
                end
            end
        end
    end

    ---A custom Width seter, it makes the slider more accurate without the slider can not be clicked correctly.
    ---@param xSize number
    ---@param ySize number
    function FrameList:setSize(xSize, ySize)
        BlzFrameSetSize(self.Frame, xSize, ySize)
        if self.Mode == FrameList.Horizontal then
            BlzFrameSetSize(self.Slider, xSize, 0.012) -- ySize of slider is constant in horizontal mode
        else
            BlzFrameSetSize(self.Slider, 0.012, ySize)
        end
    end

    ---Adds frame to as last element of frameListTable
    ---@param frame framehandle
    function FrameList:add(frame)
        table.insert(self.Content, frame)
        --BlzFrameSetParent(frame, self.Frame)
        BlzFrameSetMinMaxValue(self.Slider, 1, #self.Content)
        BlzFrameSetValue(frame, 1)
        self:setContentPoints()
    end

    ---Removes frame (can be a number) from frameListTable, skip noUpdate that is only used from FrameList.destory
    ---@param frame framehandle | integer
    ---@param noUpdate? boolean
    ---@return number | boolean
    function FrameList:remove(frame, noUpdate)
        local removed ---@type framehandle
        if #self.Content == 0 then return false end
        if not frame then
            removed = table.remove(self.Content)
        elseif type(frame) == "number" then
            removed = table.remove(self.Content, frame)
        else
            for index, value in ipairs(self.Content) do
                if frame == value then
                    removed = table.remove(self.Content, index)
                    break
                end
            end
        end

        if removed then
            BlzFrameClearAllPoints(removed)
            BlzFrameSetVisible(removed, false)
            if not noUpdate then
                BlzFrameSetMinMaxValue(self.Slider, 1, #self.Content)
                BlzFrameSetValue(self.Slider, #self.Content)
                self:setContentPoints()
            end
        end
        return #self.Content
    end

    ---Destroys the frameListTable control Frames and hides and clears all points Frames in the FrameList
    function FrameList:destroy()
        FrameList[self.Slider] = nil
        FrameList[self.Frame] = nil
        BlzDestroyFrame(self.Frame)
        BlzDestroyFrame(self.Slider)
        self.Mode = nil
        self.Frame = nil
        repeat until not FrameList:remove(nil, true)
        self.Content = nil
    end

    return FrameList
end)
if Debug then Debug.endFile() end
And I think if I remove the set parent part and I pre-create the buttons that would be added to the list, the FrameList functions can be called in a if player == GetLocalPlayer() then block, so they could be different for each player (I tested once in multiplayer and didn't get a desync yet).
 
Most FrameValues can differ for users, parent is one of them.
When you don't change the parent. Then the content is not in a layer started by the framelist-Frame. Which makes them z-Wise not above the Framelist. But it is fine if you use GAME_UI anyway.
But This resource is meant that you give a new FRAME as parent which contains the content that way it is easier to be a child of something else & hide/show using default Frame api.

I don't know oop Lua that well but shouldn't FrameList be a global?

To revert the sliders you would take the value for displaying as (max - current) then top is 0% and bottom 100% and you would set value to max after creation or after adding is done.


This system is old otherwise it would use TasFrameAction, as all my newer Lua UI systems do.

Edit: You could add your name into the Creators List.
 
Level 24
Joined
Jun 26, 2020
Messages
1,852
Most FrameValues can differ for users, parent is one of them.
If that is true so one problem is solved.
To revert the sliders you would take the value for displaying as (max - current) then top is 0% and bottom 100% and you would set value to max after creation or after adding is done.
I already reverted it, thank you.
You could add your name into the Creators List.
:O
 
Top