Drag and Drop GUI Framework

Share your scripts and packages with other Mudlet users.
Post Reply
Jor'Mox
Posts: 1084
Joined: Wed Apr 03, 2013 2:19 am

Drag and Drop GUI Framework

Post by Jor'Mox » Mon Sep 17, 2018 4:45 pm

This script makes a GUI framework that allows the user to add Geyser windows/containers to any of 6 existing spots, 4 of which are tab switched to allow multiple windows/containers in them. The containers can be resized and windows can be moved around on the fly by the user (right click and drag on the resize labels or tabs to move them around). Important note, this script works entirely through the use of Geyser, and will not work with GUI objects from another GUI management system.

The script configures itself and creates all its necessary elements the first time GUIframe.addWindow is called. It is also at that point that it captures values from GUIframe.configs, changes to configs after this point will not be reflected in the behavior of the script. If you want to have your GUI remember how it has been adjusted by the user, it is necessary for your script to call GUIframe.saveSettings when you want to save the current configuration, and then GUIframe.loadSettings when you want to load those settings from the saved file.

Key Functions:
GUIframe.addWindow(window, name [this is the text shown on the tab], target container [top, bottom, topleft, bottomleft, topright, bottomright])
GUIframe.removeWindow(name [as given in GUIframe.addWindow call])
GUIframe.enable(side [left, right, top, bottom])
GUIframe.disable(side, hide [optional, if false only disables resizing of that side])
GUIframe.saveSettings()
GUIframe.loadSettings(redraw screen [this changes the background color from and back to black])

Key Other Info:
GUIframe.configs - this is a table which a dependent script can write to in order to change how the GUI created by this script looks when initially setup. All options seen in GUIframe.defaults can be set in GUIframe.configs.

The attached .png files are a set of the arrow images I used with the resize labels, with varying levels of transparency, with the 20t version being the most transparent, 50t being the least transparent, and blue_arrows being fully opaque (intended for when the label is moused over).
Code: [show] | [select all] lua
-- Jor'Mox's GUIframe Script
-- 10/02/2018
-- v1.3.3

GUIframe = GUIframe or {}

local mainW, mainH = getMainWindowSize()
local halfW, halfH = math.floor(mainW/2), math.floor(mainH/2)

GUIframe.configs = GUIframe.configs or {}

GUIframe.defaults = {
    tabHeight = 20,
    tabStyle = [[
        background-color: green;
        border-width: 2px;
        border-style: outset;
        border-color: limegreen;
        border-top-left-radius: 10px;
        border-top-right-radius: 10px;
        margin-right: 1px;
        margin-left: 1px;
        qproperty-alignment: 'AlignCenter | AlignCenter';]],
    tabEchoStyle = '<center><p style="font-size:14px; color = white">',
    leftStartWidth = 0,
    leftStartHeight = halfH,
    rightStartWidth = 0,
    rightStartHeight = halfH,
    topStartHeight = 0,
    bottomStartHeight = 0,
    resizeHeight = 30,
    resizeWidth = 30,
    resizeHoverImage = "/imgs/blue_arrows.png",
    resizeRestImage = "/imgs/blue_arrows_30t.png",
    borderOffset = 5,
}

GUIframe.windows = GUIframe.windows or {}
GUIframe.tabs = GUIframe.tabs or {}
GUIframe.tabCoords = GUIframe.tabCoords or {}
GUIframe.sides = GUIframe.sides or {left = 'enabled', right = 'enabled', top = 'enabled', bottom = 'enabled'}

local resize_style = "border-image: url(%s%s);"

local configs = table.update(GUIframe.defaults, GUIframe.configs)
local tabsInfo = {
    topLeftTabs = {name = 'topLeftTabs', x = 0, y = 0, width = configs.leftStartWidth,
        height = configs.tabHeight},
    bottomLeftTabs = {name = 'bottomLeftTabs', x = 0, y = configs.leftStartHeight,
        width = configs.leftStartWidth, height = configs.tabHeight},
    topRightTabs = {name = 'topRightTabs', x = mainW - configs.rightStartWidth, y = 0,
        width = configs.rightStartWidth, height = configs.tabHeight},
    bottomRightTabs = {name = 'bottomRightTabs', x = mainW - configs.rightStartWidth,
        y = configs.rightStartHeight, width = configs.rightStartWidth, height = configs.tabHeight},
}
local containerInfo = {
    topLeftContainer = {name = 'topLeftContainer', x = 0, y = configs.tabHeight,
        width = configs.leftStartWidth, height = configs.leftStartHeight - configs.tabHeight},
    bottomLeftContainer = {name = 'bottomLeftContainer', x = 0, y = configs.leftStartHeight + configs.tabHeight,
        width = configs.leftStartWidth, height = configs.leftStartHeight - configs.tabHeight},
    topRightContainer = {name = 'topRightContainer', x = mainW - configs.rightStartWidth,
        y = configs.tabHeight, width = configs.rightStartWidth,
        height = configs.rightStartHeight - configs.tabHeight},
    bottomRightContainer = {name = 'bottomRightContainer', x = mainW - configs.rightStartWidth,
        y = configs.rightStartHeight + configs.tabHeight, width = configs.rightStartWidth,
        height = configs.rightStartHeight - configs.tabHeight},
    bottomContainer = {name = 'bottomContainer', x = configs.leftStartWidth,
        y = mainH - configs.bottomStartHeight, height = configs.bottomStartHeight,
        width = mainW - configs.leftStartWidth - configs.rightStartWidth},
    topContainer = {name = 'topContainer', x = configs.leftStartWidth, y = 0, height = configs.topStartHeight,
        width = mainW - configs.leftStartWidth - configs.rightStartWidth}
}
local resizeInfo = {
    resizeLeft = {name = 'resizeLeft', x = configs.leftStartWidth,
        y = configs.leftStartHeight - configs.resizeHeight / 2, height = configs.resizeHeight,
        width = configs.resizeWidth},
    resizeRight = {name = 'resizeRight', x = configs.rightStartWidth - configs.resizeWidth,
        y = configs.rightStartHeight - configs.resizeHeight / 2, height = configs.resizeHeight,
        width = configs.resizeWidth},
    resizeTop = {name = 'resizeTop', x = halfW - configs.resizeWidth / 2,
        y = configs.topStartHeight, height = configs.resizeHeight, width = configs.resizeWidth},
    resizeBottom = {name = 'resizeBottom', x = halfW - configs.resizeWidth / 2,
        y = mainH - configs.bottomStartHeight - configs.resizeHeight, height = configs.resizeHeight,
        width = configs.resizeWidth}
}

local container_names = {'topLeftContainer', 'bottomLeftContainer', 'topRightContainer',
    'bottomRightContainer', 'bottomContainer', 'topContainer'}
local tab_names = {'topLeftTabs', 'topRightTabs', 'bottomLeftTabs', 'bottomRightTabs'}
local resizeLabels = {'resizeLeft', 'resizeRight', 'resizeTop', 'resizeBottom'}
local sides = {"top","bottom","left","right"}
local side_containers = {
    left = {"topLeftContainer","bottomLeftContainer","topLeftTabs","bottomLeftTabs"},
    right = {"topRightContainer","bottomRightContainer","topRightTabs","bottomRightTabs"},
    top = {"topContainer"},
    bottom = {"bottomContainer"}
}

local function get_window_coords(win, update) -- gets coords for window, stores data in tabCoords table as needed
    local x, y = win:get_x(), win:get_y()
    local w, h = win:get_width(), win:get_height()
    if update then
        GUIframe.tabCoords[win.name]  = {x = x, y = y, w = w, h = h}
    end
    return x, y, w, h
end

local function check_overlap(tab, x, y) -- checks to see if given coords overlap tab or tab container
    if type(tab) == "string" then tab = GUIframe[tab] or GUIframe.tabs[tab] end
    if tab.hidden or tab.auto_hidden then return false end
    local info = GUIframe.tabCoords[tab.name]
    local x1, y1 = info.x, info.y
    local x2, y2 = x1 + info.w, y1 + info.h
    return (x >= x1 and x <= x2 and y >= y1 and y <= y2)
end

local function update_tab(tab, x, y, w, h) -- resizes and moves tab and updates tab coords table
    tab:move(x, y)
    tab:resize(w,h)
    local info = GUIframe.tabCoords[tab.name] or {}
    info.x, info.y = tab:get_x(), tab:get_y()
    info.w, info.h = tab:get_width(), tab:get_height()
    if table.contains(tab_names, tab.name) then
        info.container = true
    end
    GUIframe.tabCoords[tab.name] = info
end

local function get_containers(pos)
    if type(pos) == "table" then pos = pos.name end
    for _,w in ipairs({'right','left','container','tabs'}) do
        pos = pos:gsub(w,w:title())
    end
    local con, tab
    if string.find(pos,"Container") then
        con = GUIframe[pos]
        if not con then return end
        tabs = con.tabs
    elseif string.find(pos,"Tabs") then
        tabs = GUIframe[pos]
        if not tab then return end
        con = tabs.con
    else
        con = GUIframe[pos.."Container"]
        tabs = GUIframe[pos.."Tabs"]
    end
    return con, tabs
end

local function config()
    configs = table.update(GUIframe.defaults, GUIframe.configs)
    GUIframe.windows = {}
    GUIframe.tabCoords = {}
    for name, cons in pairs(containerInfo) do
        GUIframe[name] = Geyser.Container:new(cons)
    end
    for name, cons in pairs(tabsInfo) do
        GUIframe[name] = Geyser.Container:new(cons)
        local cname = name:gsub("Tabs","Container")
        GUIframe[cname].tabs = GUIframe[name]
        GUIframe[name].con = GUIframe[cname]
    end
    local style = resize_style
    local path = getMudletHomeDir()
    path = path:gsub("[\\/]","/")
    configs.resizeRestImage = configs.resizeRestImage:gsub("[\\/]","/")
    configs.resizeHoverImage = configs.resizeHoverImage:gsub("[\\/]","/")
    local no_image
    if not (io.exists(path .. configs.resizeHoverImage) and io.exists(path .. configs.resizeRestImage)) then
        debugc("GUIframe: config: resize image(s) not found")
        path = "255,20,147,"
        style = "background-color: rgba(%s%s);"
        no_image = true
    end
    for name, cons in pairs(resizeInfo) do
        GUIframe[name] = Geyser.Label:new(cons)
        GUIframe[name]:setColor(0,0,0,0)
        GUIframe[name]:setStyleSheet(string.format(style, path, (no_image and "100") or configs.resizeRestImage))
        GUIframe[name]:setOnEnter("GUIframe."..name..".setStyleSheet", GUIframe[name],
            string.format(style, path, (no_image and "255") or configs.resizeHoverImage))
        GUIframe[name]:setOnLeave("GUIframe."..name..".setStyleSheet", GUIframe[name],
            string.format(style, path, (no_image and "100") or configs.resizeRestImage))
        GUIframe[name]:setClickCallback("GUIframe.buttonClick", name)
        GUIframe[name]:setReleaseCallback("GUIframe.buttonRelease", name)
        GUIframe[name]:setMoveCallback("GUIframe.buttonMove", name)
    end
    setBorderLeft(configs.leftStartWidth + configs.borderOffset)
    setBorderRight(configs.rightStartWidth + configs.borderOffset)
    setBorderTop(configs.topStartHeight + configs.borderOffset)
    setBorderBottom(configs.bottomStartHeight + configs.borderOffset)
    GUIframe.initialized = true
end

local function deselectContainer(container, tabs)
    -- hide all windows in container
    for _, win in pairs(container.windowList) do
        win:hide()
        win.active = false
    end
    -- unhighlight all tabs in tabs container
    if tabs then
        for _, tab in pairs(tabs.windowList) do
	        tab:echo(configs.tabEchoStyle..tab.name:gsub("Tab",""))
	    end
	end
end

local function adjustTabs(tabs)
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
    -- remove duplicated window names
    local found = {}
    for k,v in ipairs(tabs.windows) do
        if not table.contains(found,v) and tabs.windowList[v] and not tabs.windowList[v].isClicked then
            table.insert(found,v)
        end
    end
    -- calculate tab width and set height
    local w, h = math.floor(100 / #tabs.windows), configs.tabHeight
    local function wrap(num) return tostring(num) .. "%" end
    -- resize and reposition all tabs
    local shown, first
    for k,v in ipairs(found) do
        local tab = tabs.windowList[v]
        if not first then first = v:gsub("Tab","") end
        if not shown and tab.active then
            shown = v
        elseif tab.active then
            tab.active = false
        end
        update_tab(tab, wrap(w * (k-1)), 0, wrap(w), h)
    end
    if first and not shown and GUIframe.windows[first] then GUIframe.windows[first]:show() end
    tabs.space_pos = nil
end

local function reorderTabs(tabs, name, pos)
    local windows = tabs.windows
    while table.contains(windows, name) do
        table.remove(windows, table.index_of(windows, name))
    end
    table.insert(windows, pos, name)
end

local function makeSpace(tabs, tab, pos)
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
    local windows = table.deepcopy(tabs.windows)
    local space_pos = tabs.space_pos
    local tab_pos = table.index_of(windows, tab.name)
    -- calculate tab width and set height
    local num_tabs = #windows + 1
    if tab_pos then
        num_tabs = num_tabs - 1
        if pos > tab_pos then pos = pos - 1 end
        if pos == space_pos then pos = pos + 1 end
    elseif space_pos and pos >= space_pos then
        pos = pos + 1
    end
    local w, h = math.floor(100 / num_tabs), configs.tabHeight
    local function wrap(num) return tostring(num) .. "%" end
    -- resize and reposition all tabs
    if tab_pos then table.remove(windows,tab_pos) end
    for k,v in ipairs(windows) do
        if k >= pos then
            update_tab(tabs.windowList[v], wrap(w * k), 0, wrap(w), h)
        else
            update_tab(tabs.windowList[v], wrap(w * (k-1)), 0, wrap(w), h)
        end
    end
    tabs.space_pos = pos
end

local function round(num,roundTo)
	local b, r = math.modf(num/roundTo)
	if r >= 0.5 then
		b = b + 1
	end
	return b * roundTo
end

local function setBorder(side, val)
    local funcs = {left = setBorderLeft, right = setBorderRight, top = setBorderTop, bottom = setBorderBottom}
    val = math.max(val,0)
	funcs[side](val)
end

local function resizeContainers(side, w, h)
    if table.contains({"left", "right"}, side) then
        local info = {
            left = {resize = "resizeLeft", cons = {"topLeftContainer","bottomLeftContainer"},
                tabs = {"topLeftTabs","bottomLeftTabs"}, x = 0, w = w},
            right = {resize = "resizeRight", cons = {"topRightContainer","bottomRightContainer"},
                tabs = {"topRightTabs","bottomRightTabs"}, x = w, w = mainW - w}
        }
        info = info[side]
        -- move and resize top, bottom and tab containers
        update_tab(GUIframe[info.tabs[1]], info.x, 0, info.w, configs.tabHeight)
        update_tab(GUIframe[info.tabs[2]], info.x, h, info.w, configs.tabHeight)
        GUIframe[info.cons[1]]:resize(info.w, h - configs.tabHeight)
        GUIframe[info.cons[1]]:move(info.x, configs.tabHeight)
        GUIframe[info.cons[2]]:resize(info.w, mainH - h - configs.tabHeight)
        GUIframe[info.cons[2]]:move(info.x, h + configs.tabHeight)
        -- adjust border size
        setBorder(side, info.w + configs.borderOffset)

        -- adjust width of top and bottom containers
        local x, y
        x = (GUIframe.sides.left ~= "hidden" and GUIframe.topLeftContainer:get_width()) or 0
        w = ((GUIframe.sides.right ~= "hidden" and GUIframe.topRightContainer:get_x()) or mainW) - x
        for _, con in ipairs({GUIframe.topContainer, GUIframe.bottomContainer}) do
            y, h = con:get_y(), con:get_height()
            con:resize(w, h)
            con:move(x, y)
        end
    elseif table.contains({"top", "bottom"}, side) then
        local x = 0
        w = mainW
        if GUIframe.sides.left ~= "hidden" then
            w = w - GUIframe.topLeftContainer:get_width()
            x = GUIframe.topLeftContainer:get_width()
        end
        if GUIframe.sides.right ~= "hidden" then w = w - GUIframe.topRightContainer:get_width() end
        local info = {top = {con = "topContainer", y = 0, h = h}, bottom = {con = "bottomContainer", y = h, h = mainH - h}}
        local con = GUIframe[info[side].con]
        con:resize(w, info[side].h)
        con:move(x, info[side].y)
        setBorder(side, info[side].h + configs.borderOffset)
    end
end

local function refresh()
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
    mainW, mainH = getMainWindowSize()
    local rH, rW = configs.resizeHeight, configs.resizeWidth
    local x, y, w
    -- adjust bottom left and right container heights
    for _, C in ipairs({GUIframe.bottomLeftContainer, GUIframe.bottomRightContainer}) do
        C:resize(C:get_width(), mainH - C:get_y())
    end
    -- reposition right containers
    w = GUIframe.topRightContainer:get_width()
    for _, C in ipairs({GUIframe.topRightContainer, GUIframe.topRightTabs,
        GUIframe.bottomRightContainer, GUIframe.bottomRightTabs}) do
        C:move(mainW - w,C:get_y())
    end
    -- resize and reposition bottom and top containers
    w, x = mainW, 0
    if GUIframe.sides.left ~= "hidden" then
        w = w - GUIframe.topLeftContainer:get_width()
        x = GUIframe.topLeftContainer:get_width()
    end
    if GUIframe.sides.right ~= "hidden" then w = w - GUIframe.topRightContainer:get_width() end
    for _, C in ipairs({GUIframe.topContainer, GUIframe.bottomContainer}) do
        C:resize(w, C:get_height())
        C:move(x, C.name == "topContainer" and 0 or mainH - C:get_height())
    end
    -- reposition resize labels
    x, y = GUIframe.topLeftContainer:get_width(), GUIframe.bottomLeftTabs:get_y()
    GUIframe.resizeLeft:move(x, y - rH / 2)
    x, y = GUIframe.topRightContainer:get_x(), GUIframe.bottomRightTabs:get_y()
    GUIframe.resizeRight:move(x - rW, y - rH / 2)
    x = (GUIframe.topContainer:get_width() - rW) / 2
    if GUIframe.sides.left ~= "hidden" then x = x + GUIframe.topLeftContainer:get_width() end
    y = GUIframe.topContainer:get_height()
    GUIframe.resizeTop:move(x, y)
    y = GUIframe.bottomContainer:get_y()
    GUIframe.resizeBottom:move(x, y - rH)
end

-- enables the resize label for the given side and shows all associated containers if hidden
function GUIframe.enable(side)
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
    if not table.contains(sides,side) then error("GUIframe.enable: invalid side",2) end
    local cons = side_containers[side]
    for _, con in ipairs(cons) do
        GUIframe[con]:show()
        for _,win in pairs(GUIframe[con].windowList) do -- loop can be removed after Geyser fix comes in
            if win.active then win:show() end
        end
    end
    if table.contains({"left","right"}, side) then
        setBorder(side, GUIframe[cons[1]]:get_width() + configs.borderOffset)
    else
        setBorder(side, GUIframe[cons[1]]:get_height() + configs.borderOffset)
    end
    GUIframe["resize"..side:title()]:show()
    GUIframe.sides[side] = "enabled"
    refresh()
end

-- disables and hides the resize label for the given side, and hides all associated containers if indicated
function GUIframe.disable(side, hide)
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
    if not table.contains(sides,side) then error("GUIframe.disable: invalid side",2) end
    local cons = side_containers[side]
    GUIframe.sides[side] = "disabled"
    if hide then
        for _, con in ipairs(cons) do
            GUIframe[con]:hide()
            for _, win in pairs(GUIframe[con].windowList) do -- loop can be removed after Geyser fix comes in
                if win.type == "mapper" then win:hide() end
            end
        end
        local border = _G["setBorder"..side:title()]
        border(0)
        GUIframe.sides[side] = "hidden"
    end
    GUIframe["resize"..side:title()]:hide()
    refresh()
end

-- adds a Geyser window or container to the given container, with a tab showing the given name if applicable
function GUIframe.addWindow(window, name, container)
    if not GUIframe.initialized then config() end
    if type(container) == "table" then container = container.name end
    local con, tabs = get_containers(container)
    if not con then error("GUIframe.addWindow: invalid container name",2) end
    if not name then error("GUIframe.addWindow: name argument required",2) end
    -- remove window from any containers
    for _, tcon in ipairs(container_names) do
        if table.contains(GUIframe[tcon].windows, window.name) then
            GUIframe.removeWindow(name, tcon)
        end
    end
    deselectContainer(con,tabs)
    -- add tab for window, if applicable
    if tabs then
        local lbl = Geyser.Label:new({name = name.."Tab", x = 0, y = 0, width = 10, height = 10},tabs)
        lbl:setStyleSheet(configs.tabStyle)
        lbl:echo(configs.tabEchoStyle.."<b>"..name)
        lbl:setClickCallback("GUIframe.buttonClick", name)
        lbl:setReleaseCallback("GUIframe.buttonRelease", name)
        lbl:setMoveCallback("GUIframe.buttonMove", name)
        GUIframe.tabs[name] = lbl
        adjustTabs(tabs)
    end
    -- add window to container and set size and position
    con:add(window)
    window:resize("100%","100%")
    window:move(0,0)
    window:show()
    GUIframe.windows[name] = window
	raiseEvent("sysWindowResizeEvent")
end

-- removes a named Geyser window or container from the named container (using name given in GUIframe.addWindow)
function GUIframe.removeWindow(name, container)
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
    if not container then container = GUIframe.windows[name].container end
    local con, tabs = get_containers(container)

    if not con or not table.contains(container_names, con.name) then
        error("GUIframe.removeWindow: invalid container name",2)
    end
    if not name then error("GUIframe.removeWindow: name argument required",2) end
    if tabs then
        local lbl = tabs.windowList[name.."Tab"]
        if lbl then
            tabs:remove(lbl)
            adjustTabs(tabs)
            lbl:hide()
        end
    end
    local window = GUIframe.windows[name]
    con:remove(window)
    window:hide()
end

-- saves the current GUI setup, including the size of the different containers and what windows go in which container
function GUIframe.saveSettings()
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
    local saveTbl = {}
    local w, h = GUIframe.topLeftContainer:get_width(), GUIframe.bottomLeftTabs:get_y()
    saveTbl.left = {w = w, h = h}
    w, h = GUIframe.topRightContainer:get_width(), GUIframe.bottomRightTabs:get_y()
    saveTbl.right = {w = w, h = h}
    w, h = GUIframe.topContainer:get_width(), GUIframe.topContainer:get_height()
    saveTbl.top = {w = w, h = h}
    w, h = GUIframe.bottomContainer:get_width(), GUIframe.bottomContainer:get_height()
    saveTbl.bottom = {w = w, h = h}

    -- get added windows and containers they are assigned to
    local windows = {}
    for k,v in pairs(GUIframe.windows) do
        local con = v.container.name
        windows[con] = windows[con] or {}
        table.insert(windows[con], k)
    end
    -- reorder windows to match tab order for tabbed containers
    for con, wins in pairs(windows) do
        if con:find("Left") or con:find("Right") then
            local tabs = GUIframe[con].tabs.windows
            local new = {}
            for k,v in ipairs(tabs) do
                local wname = v:gsub("Tab","")
                table.insert(new, wname)
            end
            windows[con] = new
        end
    end
    saveTbl.windows = windows
    saveTbl.sides = GUIframe.sides
    table.save(getMudletHomeDir() .. "/GUIframeSave.lua", saveTbl)
end

-- loads GUI setup from a previous save
function GUIframe.loadSettings(redraw)
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
    local saveTbl = {}
    local path = getMudletHomeDir() .. "/GUIframeSave.lua"
    path = path:gsub("\\","/")
    mainW, mainH = getMainWindowSize()
    if not io.exists(path) then debugc("GUIframe.loadSettings: save file doesn't exist.") return end
    table.load(path, saveTbl)
    resizeContainers("left", saveTbl.left.w, saveTbl.left.h)
    resizeContainers("right", mainW - saveTbl.right.w, saveTbl.right.h)
    resizeContainers("top", saveTbl.top.w, saveTbl.top.h)
    resizeContainers("bottom", saveTbl.bottom.w, mainH - saveTbl.bottom.h)
    for con, wins in pairs(saveTbl.windows) do
        local tabs = {}
        for _,name in ipairs(wins) do
            table.insert(tabs, name.."Tab")
            GUIframe.addWindow(GUIframe.windows[name], name, con)
        end
        local tabCon = GUIframe[con].tabs
        if tabCon then
            tabCon.windows = tabs
            adjustTabs(tabCon)
        end
    end

    for side, state in pairs(saveTbl.sides) do
        if state == "enabled" then
            GUIframe.enable(side)
        elseif state == "disabled" then
            GUiframe.disable(side,false)
        elseif state == "hidden" then
            GUIframe.disable(side,true)
        end
    end
    -- force redraw of screen
    if redraw then
        setBackgroundColor(1,1,1)
        setBackgroundColor(0,0,0)
    end
end

-- can be called to force the script to run its config function again
function GUIframe.reinitialize()
    config()
end

-- internally used function to handle button click callbacks
function GUIframe.buttonClick(name, event)
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
	if table.contains(resizeLabels,name) then
	    if event.button == "RightButton" then
	        local lbl = GUIframe[name]
	        lbl.difX, lbl.difY = event.x, event.y
	        lbl.savedX, lbl.savedY = getMousePosition()
            GUIframe[name].isClicked = true
        end
	elseif event.button == "LeftButton" then
        local window = GUIframe.windows[name]
        local con, tabs = get_containers(window.container.name)
        -- hide and unhighlight other windows and tabs
        deselectContainer(con, tabs)
        -- show selected window
        window:show()
        window.active = true
        -- highlight selected tab
        GUIframe.tabs[name]:echo(configs.tabEchoStyle.."<b>"..name)
    elseif event.button == "RightButton" then
        local window, tab = GUIframe.windows[name], GUIframe.tabs[name]
        tab.savedX, tab.savedY = getMousePosition()
        tab.difX, tab.difY, tab.isClicked = event.x, event.y, true
        -- force update of coords for all tabs and tab containers
        GUIframe.tabCoords = {}
        for _, name in ipairs(tab_names) do
            get_window_coords(GUIframe[name], true)
            for tname, tab in pairs(GUIframe[name].windowList) do
                get_window_coords(tab, true)
            end
        end
    end
end

-- internally used function to handle button release callbacks
function GUIframe.buttonRelease(name, event)
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
	if table.contains(resizeLabels,name) then
	    if event.button == "RightButton"  then
            local lbl = GUIframe[name]
            lbl.savedX, lbl.savedY, lbl.difX, lbl.difY, lbl.isClicked = nil, nil, nil, nil, false
        end
	elseif event.button == "RightButton" then
	    local window, tab = GUIframe.windows[name], GUIframe.tabs[name]
	    local con, tabs = get_containers(window.container.name)
	    tab.difX, tab.difY, tab.savedX, tab.savedY, tab.isClicked = nil, nil, nil, nil, false
	    hideWindow("show_container")
	    for _, tname in ipairs(tab_names) do
	        local info = GUIframe[tname]
	        if info.mouse_over then
	            local pos = info.space_pos
	            info.mouse_over = nil
	            GUIframe.addWindow(window, name, tname:gsub("Tabs",""))
	            if pos then
    	            reorderTabs(info, tab.name, pos)
    	            adjustTabs(info)
    	        end
	        end
	    end
	    adjustTabs(tabs)
	end
end

-- internally used function to handle button move callbacks
function GUIframe.buttonMove(name, event)
    if not GUIframe.initialized then error("GUIframe not initialized",2) end
	if table.contains(resizeLabels,name) then
	    lbl = GUIframe[name]
	    if lbl.isClicked then
	        local w, h = getMousePosition()
	        w, h = round(w - lbl.difX, 10), round(h - lbl.difY, 10)
            mainW, mainH = getMainWindowSize()
            local side, cW, cH, rX, rY
            local minX = GUIframe.sides.left ~= "hidden" and GUIframe.topLeftContainer:get_width() or 0
            local maxX = GUIframe.sides.right ~= "hidden" and GUIframe.topRightContainer:get_x() or mainW
            local minY = GUIframe.sides.top ~= "hidden" and GUIframe.topContainer:get_height() or 0
            local maxY = GUIframe.sides.left ~= "hidden" and GUIframe.bottomContainer:get_y() or mainH
            local mid, min, max = GUIframe.topContainer:get_width(), math.min, math.max
            local tabH, rH, rW = configs.tabHeight, configs.resizeHeight, configs.resizeWidth
	        w, h = max(w, 0), max(h, 0)
            local info = { -- specify position of resize labels and size of containers
                resizeLeft = {side = "left", x = min(w, maxX - rW),
                    y = min(max(h + rH / 2,tabH), mainH - tabH) - rH / 2,
                    w = min(w, maxX - rW), h = min(max(h + rH / 2,tabH), mainH-tabH) },
                resizeRight = {side = "right", x = min(max(w, minX), mainW),
                    y = min(max(h + rH / 2, tabH), mainH - tabH) - rH / 2,
                    w = min(max(w, minX), mainW - rW) + rW, h = min(max(h + rH / 2, tabH), mainH - tabH) },
                resizeTop = {side = "top", x = minX + (mid - rW) / 2,
                    y = min(h, maxY - rH), w = maxX - minX, h = min(h, maxY - rH) },
                resizeBottom = {side = "bottom", x = minX + (mid - rW) / 2,
                    y = min(max(h, minY) - rH, mainH), w = maxX - minX, h = min(max(h, minY) + rH, mainH)} }
            info = info[name]
            lbl:move(info.x, info.y)
            resizeContainers(info.side, info.w, info.h)
        end
    else
        local window, tab = GUIframe.windows[name], GUIframe.tabs[name]
        local con, tabs = get_containers(window.container.name)
        local x, y = getMousePosition()
        local over_con, over_tab
        if tab and tab.isClicked then
            moveWindow(tab.name, x - tab.difX, y - tab.difY)
            -- check to see if mouse is over any tab containers
            for _, tcon in ipairs(tab_names) do
                if check_overlap(tcon, x, y) then
                    over_con = tcon
                    GUIframe[tcon].mouse_over = true
                    local info = GUIframe.tabCoords[tcon]
                    local tx, ty, tw, th = info.x, info.y, info.w, info.h
                    createLabel("show_container", 0, 0, 0, 0, 1)
                    moveWindow("show_container", tx, ty)
                    resizeWindow("show_container", tw, th)
                    setLabelStyleSheet("show_container",[[
                        background-color: black;
                        border: 2px solid white;]])
                    showWindow("show_container")
                    lowerWindow("show_container")
                    -- check to see if mouse is over any tabs
                    for tname, info in pairs(GUIframe.tabs) do
                        if tname ~= name and check_overlap(info, x, y) then
                            over_tab = info.name
                            local windows = GUIframe[tcon].windows
                            local index = table.index_of(windows,over_tab)
                            makeSpace(GUIframe[tcon],tab,index)
                            break
                        end
                    end
                    break
                end
            end
            -- remove any unnecessary spaces in tab containers
            for _, name in ipairs(tab_names) do
                if name ~= over_con then
                    adjustTabs(GUIframe[name])
                    GUIframe[name].mouse_over = nil
                end
            end
        end
	end
end

-- internally used function to handle sysWindowResizeEvent
function GUIframe.eventHandler(event,...)
    if event == "sysWindowResizeEvent" and GUIframe.initialized then
        refresh()
    end
end

registerAnonymousEventHandler("sysWindowResizeEvent","GUIframe.eventHandler")
Edit: Updated the script to fix a bug that popped up for someone using this on a fresh profile (because I clearly didn't do a thorough enough job checking for such things).

Edit2: Decided to do a significant rewrite of the script to make it remain in the background until called upon.

Edit3: Squashed a few more minor bugs. Also decided to try to streamline the code somewhat.

Edit4: Change to try to fix resize label image loading in Windows.

Edit5: Change to ensure frame sides remain the same when calling GUIframe.loadSettings regardless of the size of the current main window.

Edit6: Hopefully fixing path issues for Windows.
Attachments
blue_arrows_50t.png
blue_arrows_50t.png (8.35 KiB) Viewed 279 times
blue_arrows_40t.png
blue_arrows_40t.png (7.53 KiB) Viewed 279 times
blue_arrows_30t.png
blue_arrows_30t.png (6.73 KiB) Viewed 279 times
blue_arrows_20t.png
blue_arrows_20t.png (5.94 KiB) Viewed 279 times
blue_arrows.png
blue_arrows.png (8.93 KiB) Viewed 280 times
Last edited by Jor'Mox on Fri Oct 05, 2018 1:02 pm, edited 11 times in total.

Jor'Mox
Posts: 1084
Joined: Wed Apr 03, 2013 2:19 am

Re: Drag and Drop GUI Framework

Post by Jor'Mox » Mon Sep 17, 2018 9:57 pm

I forgot, here is a brief demo video of this script in action.
Mudlet Frame Demo.mp4
(4.9 MiB) Downloaded 28 times

edit: Zaphob: Watch on Youtube:
https://www.youtube.com/watch?v=VWbugxsCmNA

Jor'Mox
Posts: 1084
Joined: Wed Apr 03, 2013 2:19 am

Re: Drag and Drop GUI Framework

Post by Jor'Mox » Tue Sep 18, 2018 12:26 am

Someone asked on Discord that I share the setup I used for the demo video, so here it is. I had this in an alias and was using it while doing development and testing, so it is pretty basic. But I think it shows how little needs to be done to make use of this script.
Code: [show] | [select all] lua
mapper = Geyser.Mapper:new({name = "myMapper"})
chat = Geyser.MiniConsole:new({name = "myChat"})
redbox = Geyser.Label:new({name = "RedBox", color = "red", x = 0, y = 0, width = 0, height = 0})
bluebox = Geyser.Label:new({name = "BlueBox", color = "blue", x = 0, y = 0, width = 0, height = 0})
greenbox = Geyser.Label:new({name = "GreenBox", color = "green", x = 0, y = 0, width = 0, height = 0})
orangebox = Geyser.Label:new({name = "OrangeBox", color = "orange", x = 0, y = 0, width = 0, height = 0})
yellowbox = Geyser.Label:new({name = "YellowBox", color = "yellow", x = 0, y = 0, width = 0, height = 0})

GUIframe.addWindow(mapper,"Map","topright")
GUIframe.addWindow(chat,"Chat","topright")
GUIframe.addWindow(redbox,"Red","bottomright")
GUIframe.addWindow(bluebox,"Blue","bottomright")
GUIframe.addWindow(greenbox,"Green","bottomright")
GUIframe.addWindow(orangebox,"Orange","top")
GUIframe.addWindow(yellowbox,"Yellow","bottom")

Caled
Posts: 394
Joined: Thu Apr 09, 2009 4:45 am

Re: Drag and Drop GUI Framework

Post by Caled » Fri Sep 28, 2018 6:13 am

Vadi suggested a nice demo for people may be to combine Jor'Mox excellent work with Akaya's Geyser Template from this thread:
https://forums.mudlet.org/viewtopic.php?f=6&t=4098

Which is a simple beginning point for building your own UI. It's been around for a long time and even as someone relatively confident with Geyser, I've used this several times to build from over the years.

I had a look to see if the two could be combined, and it can be. I doubt I would ever use the result - using GUIframe is so easy, most of the 'laying out' part is bypassed because of the drag-and-drop nature of the script. But, it may be useful to some people wondering how to use GUIframe. Another 'demo' if you will.
Attachments
AkayasGeyserTemplate-with-JorMoxsGUIFrame.xml
(4.98 KiB) Downloaded 8 times

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest