• 🏆 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 - Window

This is a Lua resource for UI-Frames. The idea of Window is to provide a basic Frame in which you just stick in a custom content Frame. Window manages the border the WindowTitle and it's close Button.

Lua:
--[[
    Window V1.1 by Tasyen
    The idea of Window is to have a standart Frame in which you only have to care about how to manage your content onto the ContentPane without bothering the border etc.   
   
function Window.create([title, hide, createContext, parent])
    creates a window using the local players race border.
    A Window consists of a Pane, a Head, a Title a closeButton and a Border.
    The Pane is mean to carry your custom Frame and contains only the space between the borders of the Window.
    The closeButton is part of the Head and has 2 modes:
        hide(true) close the window and head when clicked for the local Player. In such a case the user needs a way to show the window.WindowHead again, if that is wanted.
        hide (false or nil) The close Button toggles the visibility of the Window, the WindowHead remains visibile.
    createContext nil = 0, use a specific one, if you want to access frames over BlzGetFrameByName
    parent nil = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0).
    The window is posed over windowTable.WindowHead, all other frames of window are linked based on the heads position.
    returns the new created windowTable

function Window.setAbsPoint(windowTable, framepoint, x, y)
function Window.setSize(windowTable[, xSize, ySize, tempChange])
    without any arguments reset the size to the last non tempChange one
    without tempChange the window will change its current size and define a new default size
function Window.destroy(windowTable)

--]]


BlzLoadTOCFile("war3mapimported\\window.toc")
Window = {}
function Window.create(title, hide, createContext, parent)
    local windowTable = {}
    if not createContext then createContext = 0 end --user does not care use 0.
    if not parent then parent = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0) end
    windowTable.WindowHead = BlzCreateFrame("WindowHead", parent, 0, createContext)
    windowTable.WindowHeadText = BlzGetFrameByName("WindowHeadText", createContext) --styling
    windowTable.WindowHeadBackdrop = BlzGetFrameByName("WindowHeadBackdrop", createContext)
    windowTable.WindowHeadTrigger = CreateTrigger()
    windowTable.Window = BlzGetFrameByName("Window", createContext) --this is the background and border of the ContentPane   
    if title then
        BlzFrameSetText(windowTable.WindowHead, title)
    end
   
    windowTable.WindowPane = BlzGetFrameByName("WindowPane", createContext) --the container for the stuff user wants to display
    windowTable.WindowCloseButton = BlzGetFrameByName("WindowCloseButton", createContext) --press this to hide the window
    windowTable.WindowCloseButtonText = BlzGetFrameByName("WindowCloseButtonText", createContext) --styling
    windowTable.WindowCloseButtonTrigger = CreateTrigger()
    windowTable.WindowCloseButtonType = hide
    windowTable.WindowSizeX = BlzFrameGetWidth(windowTable.Window)
    windowTable.WindowSizeY = BlzFrameGetHeight(windowTable.Window)

    Window[windowTable.Window] = windowTable --the Window and the WindowHead know the table, the others know the Window by using BlzFrameGetParent
    Window[windowTable.WindowHead] = windowTable

    windowTable.WindowHeadTriggerAction = TriggerAddAction(windowTable.WindowHeadTrigger, Window.HeadAction)
    BlzTriggerRegisterFrameEvent(windowTable.WindowHeadTrigger, windowTable.WindowHead, FRAMEEVENT_CONTROL_CLICK)

    windowTable.WindowCloseButtonTriggerAction = TriggerAddAction(windowTable.WindowCloseButtonTrigger, Window.CloseButtonAction)
   
    BlzTriggerRegisterFrameEvent(windowTable.WindowCloseButtonTrigger, windowTable.WindowCloseButton, FRAMEEVENT_CONTROL_CLICK)

    return windowTable
end

function Window.setAbsPoint(windowTable, framepoint, x, y)
    BlzFrameSetAbsPoint(windowTable.WindowHead, framepoint, x, y)
end

function Window.setSize(windowTable, xSize, ySize, tempChange)
    if xSize then
        BlzFrameSetSize(windowTable.Window, xSize, ySize)
        BlzFrameSetSize(windowTable.WindowPane, xSize - 0.02, ySize - 0.02)
        BlzFrameSetSize(windowTable.WindowHead, xSize - 0.025, 0.0275) -- -0.025 is the closebutton
        if not tempChange then
            windowTable.WindowSizeX = xSize
            windowTable.WindowSizeY = ySize
        end
    else
        BlzFrameSetSize(windowTable.Window, windowTable.WindowSizeX, windowTable.WindowSizeY)
        BlzFrameSetSize(windowTable.WindowPane, windowTable.WindowSizeX - 0.02, windowTable.WindowSizeY - 0.02)
        BlzFrameSetSize(windowTable.WindowHead, windowTable.WindowSizeX - 0.025 , 0.0275)
    end
end

function Window.destroy(windowTable)
    if windowTable.Tabs then
        Window.destroyTabs(windowTable)
    end
    Window[windowTable.Window] = nil
    TriggerRemoveAction(windowTable.WindowCloseButtonTrigger, windowTable.WindowCloseButtonTriggerAction)
    DestroyTrigger(windowTable.WindowCloseButtonTrigger)

    TriggerRemoveAction(windowTable.WindowHeadTrigger, windowTable.WindowHeadTriggerAction)
    DestroyTrigger(windowTable.WindowHeadTrigger)

    BlzDestroyFrame(windowTable.WindowCloseButtonText)
    BlzDestroyFrame(windowTable.WindowCloseButton)
    BlzDestroyFrame(windowTable.WindowPane)
    BlzDestroyFrame(windowTable.Window)
    BlzDestroyFrame(windowTable.WindowHeadBackdrop)
    BlzDestroyFrame(windowTable.WindowHeadText)
    BlzDestroyFrame(windowTable.WindowHead)
    windowTable.WindowPane = nil
    windowTable.Window = nil
    windowTable.WindowCloseButton = nil
    windowTable.WindowCloseButtonText = nil
    windowTable.WindowCloseButtonTrigger = nil
    windowTable.WindowCloseButtonTriggerAction = nil
    windowTable.WindowSizeX = nil
    windowTable.WindowSizeY = nil
    windowTable.WindowHeadText = nil
    windowTable.WindowHead = nil
    windowTable.WindowHeadBackdrop = nil
end


function Window.HeadAction()
   --when the title is clicked(released)
    local button = BlzGetTriggerFrame()
    BlzFrameSetEnable(button, false)
    BlzFrameSetEnable(button, true)
end

--more of an attribute hence CamelCase
function Window.CloseButtonAction()
    local windowTable = Window[BlzFrameGetParent(BlzGetTriggerFrame())]
    if GetLocalPlayer() == GetTriggerPlayer() then
        if not windowTable.WindowCloseButtonType then
            BlzFrameSetVisible(windowTable.Window, not BlzFrameIsVisible(windowTable.Window))
        else
            BlzFrameSetVisible(windowTable.WindowHead, false)
        end
    end
end

There is another resource WindowTab which automises the usage of Window to swap between multiple content-Frames of one Window.

Lua:
--[[
    WindowTab V1.1a
    plugin for Window by Tasyen.
    Instead of placing stuff onto the ContentPane one adds Frames as Tabs (pages). The current shown Tab is swaped by the user clicking TabButtons.
    First Create a Window then add a frame as Tab to the Window.
    Having a tab does not stopp one from placing something onto the ContentPane itself.

    function Window.addTab(windowTable, frame, [buttonText, doNotStretch, xSize, ySize])
        adds frame as Tab to windowTable
        buttonText is displayed on the Button to swap to this tab
        doNotStretch(true) does not alter the size of frame, also pos it with its center to the WindowPane center
        xSize and ySize when set, will alter the size of windowTable as long this tab is shown
        Does not support SimpleFrames, directly.
        frames parent, position, size (without doNotStretch) are altered in the process.
        returns the new created windowTabtable

    function Window.setTabButtonAction(windowTabTable, actionFunction)
        calls actionFunction when this button is clicked and shown.
        Beaware that the action is called async.
        Inside actionFunction you have windowTable, windowTabTable.
        Will instantly call the actionFunction if the tab is the current active tab

    function Window.removeTab(windowTable[, windowTabTable])
        windowTabTable can be an index, a table or nil. nil will remove the last added one
        returns if windowTable has further Tabs remaining

    function Window.showTab(windowTable, windowTabTable[, player])
        show windowTabTable of windowTable to player
        windowTabTable can be a index or a table, the table is expected to be a tabTable of the windowTable
        player can be nil to affect all players


doesn't work that well with LeaderBoard, Multiboard, TimerDialog
if you need to use SimpleFrames create a FRAME make it a Tab and create a SIMPLEFRAME put all your SIMPLEFRAMEs into SIMPLEFRAME and clone visiblity and position of FRAME with SIMPLEFRAME the visibility has to be update quite often like every 0.05s.
the frame will survive the destruction of the window, but needs to change parent and position to be visible again.
--]]
function Window.addTab(windowTable, frame, buttonText, doNotStretch, xSize, ySize)
    --each Tab is an own Table
    local windowTabTable = {}
   
    if not windowTable.Tabs then
        --this is the first time this window gets tabs
        windowTable.Tabs = {}
        windowTable.TabActive = windowTabTable
    end
    --add the new TabTable into the array of windowTable
    table.insert(windowTable.Tabs, windowTabTable)

    local tabButton = BlzCreateFrame("WindowTabButton", windowTable.WindowPane, 0, 0)
    if buttonText then
        BlzFrameSetText(tabButton, buttonText)
    else
        BlzFrameSetText(tabButton, #windowTable.Tabs)
    end
    BlzFrameSetSize(tabButton, 0.06, 0.025)

    Window[tabButton] = windowTabTable
    Window[windowTabTable] = windowTable
    --fit the given frame into the Content Pane?
    if not doNotStretch then
        BlzFrameSetAllPoints(frame, windowTable.WindowPane)
        BlzFrameSetSize(frame, BlzFrameGetWidth(windowTable.WindowPane), BlzFrameGetHeight(windowTable.WindowPane))
    else
        --only center it
        BlzFrameClearAllPoints(frame)
        BlzFrameSetPoint(frame, FRAMEPOINT_CENTER, windowTable.WindowPane, FRAMEPOINT_CENTER, 0, 0)
    end
    BlzFrameSetParent(frame, windowTable.WindowPane)
   
    windowTabTable.Button = tabButton
    windowTabTable.Frame = frame
    windowTabTable.SizeX = xSize
    windowTabTable.SizeY = ySize

    windowTabTable.ButtonTrigger = CreateTrigger()
    windowTabTable.ButtonTriggerAction = TriggerAddAction(windowTabTable.ButtonTrigger, Window.TabButtonAction)
    BlzTriggerRegisterFrameEvent(windowTabTable.ButtonTrigger, tabButton, FRAMEEVENT_CONTROL_CLICK)

    --pos the tab button
    if #windowTable.Tabs == 1 then
        --the first button is located at Top Left of the Window
        BlzFrameSetVisible(frame, true)
        BlzFrameSetPoint(tabButton, FRAMEPOINT_TOPRIGHT, windowTable.Window, FRAMEPOINT_TOPLEFT, 0, 0)
        --hide the new tab button, there is nothing to choose yet.
        BlzFrameSetVisible(windowTable.Tabs[1].Button, false)
        Window.showTab(windowTable, windowTabTable)
    else
        --further ones are located below to the previous one
        --this is not the first added one hide it.
       
        BlzFrameSetVisible(frame, false)
        BlzFrameSetPoint(tabButton, FRAMEPOINT_TOP, windowTable.Tabs[#windowTable.Tabs - 1].Button, FRAMEPOINT_BOTTOM, 0, 0.005)
        BlzFrameSetVisible(windowTable.Tabs[1].Button, true)
    end
    return windowTabTable
end

function Window.setTabButtonAction(windowTabTable, actionFunction)
    windowTabTable.ShowAction = actionFunction
    if Window[windowTabTable].TabActive == windowTabTable then
        actionFunction(Window[windowTabTable], windowTabTable)
    end
end

-- windowTabTable can be an index, a table or nil. nil will remove the last added one
-- returns true if there are tabs remaining
function Window.removeTab(windowTable, windowTabTable)
    if #windowTable.Tabs == 0 then return false end
    if not windowTabTable then
        Window.destroyTab(windowTable, #windowTable.Tabs, true)
    elseif type(windowTabTable) == "number" then
        Window.destroyTab(windowTable, windowTabTable, true)
       
    elseif type(windowTabTable) == "table" then
        for index, value in ipairs(windowTable.Tabs)
        do
            if value == windowTabTable then
                Window.destroyTab(windowTable, index, true)
                break
            end
        end
    end
    return #windowTable.Tabs > 0
end

function Window.showTab(windowTable, windowTabTable, player)
    if player and player ~= GetLocalPlayer() then return end

    if windowTable.TabActive then
        BlzFrameSetVisible(windowTable.TabActive.Frame, false)
    end
    if type(windowTabTable) == "number" then
        windowTabTable = windowTable.Tabs[windowTabTable]
    end
    BlzFrameSetVisible(windowTabTable.Frame, true)
    windowTable.TabActive = windowTabTable

    if windowTabTable.SizeX then
        Window.setSize(windowTable, windowTabTable.SizeX + 0.02, windowTabTable.SizeY + 0.02, true)
        BlzFrameSetSize(windowTabTable.Frame, BlzFrameGetWidth(windowTable.WindowPane), BlzFrameGetHeight(windowTable.WindowPane))
    else
        Window.setSize(windowTable)
    end

    --custom user action when showing this Tab
    if windowTabTable.ShowAction then
        windowTabTable.ShowAction(windowTable, windowTabTable)
    end
end

-- this is not meant to be called manualy
function Window.destroyTab(windowTable, index, reCalc)
    local windowTabTable = windowTable.Tabs[index]

    Window[windowTabTable.Button] = nil
    Window[windowTabTable] = nil
    TriggerRemoveAction(windowTabTable.ButtonTrigger, windowTabTable.ButtonTriggerAction)
    DestroyTrigger(windowTabTable.ButtonTrigger)
    BlzDestroyFrame(windowTabTable.Button)
   
    --wana repos tab buttons, the array and the shown Active Tab when needed?
    if reCalc then
        --remove 1 have 2 or more, attach 2. to the 1. slot
        if index == 1 and #windowTable.Tabs > 1 then
            BlzFrameSetPoint(windowTable.Tabs[index + 1].Button, FRAMEPOINT_TOPRIGHT, windowTable.Window, FRAMEPOINT_TOPLEFT, 0, 0)
        --remove 2. put 3. below 1.
        elseif index > 1 and #windowTable.Tabs > index then
            BlzFrameSetPoint(windowTable.Tabs[index + 1].Button, FRAMEPOINT_TOP, windowTable.Tabs[index - 1].Button, FRAMEPOINT_BOTTOM, 0, 0.005)
        end
       
        table.remove(windowTable.Tabs, index)

       
        if windowTable.TabActive == windowTabTable then
            BlzFrameSetVisible(windowTable.TabActive.Frame, false)
            if #windowTable.Tabs > 0 then           
                Window.showTab(windowTable, windowTable.Tabs[1])
            end
        end
    end

    --this has to be done after the new shown tab was updated.
    windowTabTable.Button = nil
    windowTabTable.Frame = nil
    windowTabTable.ButtonTrigger = nil
    windowTabTable.ButtonTriggerAction = nil
    windowTabTable.ShowAction = nil
    windowTabTable.SizeX = nil
    windowTabTable.SizeY = nil
end

--this is called from Window.destroy, if the Window has tabs.
function Window.destroyTabs(windowTable)
    for index in ipairs(windowTable.Tabs)
    do
        Window.destroyTab(windowTable, index, false)
    end
    windowTable.Tabs = nil
    windowTable.TabActive = nil
end

--more of an attribute hence CamelCase
function Window.TabButtonAction()
    local button = BlzGetTriggerFrame()
    local windowTable = Window[BlzFrameGetParent(BlzFrameGetParent(button))]
    local windowTabTable = Window[button]
    if GetLocalPlayer() == GetTriggerPlayer() then
        Window.showTab(windowTable, windowTabTable)
        BlzFrameSetEnable(button, false)
        BlzFrameSetEnable(button, true)
    end
end

Example
Lua:
onGameStart(function()
    local windowObject = Window.create("Test Tabed Window")
    Window.setSize(windowObject, 0.2, 0.2)
    Window.setAbsPoint(windowObject, FRAMEPOINT_CENTER, 0.2, 0.5)
    local gameUI = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
 
    local icon =  BlzCreateFrameByType("BACKDROP", "MYIcon", gameUI, "", 0)
    BlzFrameSetTexture(icon, "ReplaceableTextures\\CommandButtons\\BTNHeroPaladin",0, false)
    Window.addTab(windowObject, icon, "Paladin", false, 0.08, 0.08)

    icon =  BlzCreateFrameByType("BACKDROP", "MYIcon", gameUI, "", 0)
    BlzFrameSetTexture(icon, "ReplaceableTextures\\CommandButtons\\BTNHeroArchMage",0, false)
    Window.addTab(windowObject, icon, "Archmage")

    icon =  BlzCreateFrameByType("BACKDROP", "MYIcon", gameUI, "", 0)
    BlzFrameSetSize(icon, 0.1, 0.1)
    BlzFrameSetTexture(icon, "ReplaceableTextures\\CommandButtons\\BTNHeroMountainKing",0, false)

    Window.addTab(windowObject, icon, nil, true)
    icon =  BlzCreateFrameByType("BACKDROP", "MYIcon", gameUI, "", 0)
    BlzFrameSetTexture(icon, "ReplaceableTextures\\CommandButtons\\BTNHeroBloodElfPrince",0, false)
    Window.addTab(windowObject, icon)

    TimerStart(CreateTimer(), 3, false, function()      
        --Window.removeTab(windowObject, 3)
 local icon =  BlzCreateFrameByType("BACKDROP", "MYIcon", gameUI, "", 0)
 BlzFrameSetTexture(icon, "ReplaceableTextures\\CommandButtons\\BTNHeroFarseer",0, false)
   Window.addTab(windowObject, icon, "Orc Seer")
    end)
end)
To use Window one needs to import the toc and fdf file inside the map.

ChangeLog:
Window1.1)
Window.setSize will now also set the size of WindowPane.
Reduced the Inset of the background in fdf a bit.​
WindowTab 1.1a)
When activading a tab will copy the size of WindowPane.
The Size given is now the size of the WindowPane previously it was the size of the Window

WindowTab 1.1) Added a TabButton actionFunction which is called as soon that tab becomes active.

window 4.jpg window 3.jpg Window 2.jpg
 

Attachments

  • Window.w3x
    28.4 KB · Views: 119
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
For your demo, I'd prefer the top-right icon be a "<" or ">" or "v" to indicate that you're actually collapsing or expanding the tabbed window. An "x" indicates that clicking it will close the whole thing off the screen, which this seems to lack functionality for.

The text pops off the screen in some instances, and in others the UI feels a bit jarring because the window moves around so much with it being uneven between the various selections.

Nevertheless, I can see the "destroy" functionality is there in your code, and the stretching is intentionally done as per your demo script. Approved, because the resource works at its core.
 
Top