usage: require("gui")
this is probably the most useful library in SComputers
it makes it easy to create graphical interfaces and even windows
the library supports automatic calculation of the size and position of elements
the library supports only the basic objects, but you are not restricted by them! you can create your own objects and add them to the GUI
there is also an objs library in the mod that contains ready-made additional GUI objects
there is also a styles library in mod that contains styles for objects that can be applied to any object and its drawer callback will be replaced with a style


methods:


constants:


guiinstance methods:


guiscene methods:


any object callbacks (you can implement this in the object after creation):


scene callbacks (you can implement this in the object after creation):


window callbacks (you can implement this in the object after creation):


scene/window callbacks (you can implement this in the object after creation):


button callbacks (you can implement this in the object after creation):


scene/window methods:


window object methods:


button object methods:


image object methods:


label object methods:


text object methods:


textbox object methods:


any object methods (these methods are relevant for any type of object, including custom and window):


functions for specifying the position of objects:


you can place objects not by absolute positions, but relative to others using the functions listed above
you can also set a rule function (use: guiobject:set() or guiscene/guiwindow:setDefaultSet()) for your objects that will contain instructions for automatic placement and size setting
next, set a rule for the placement of elements inside this function

if you are changing the position of an object in real time, you will need to call obj:updateParent() to update the scene or window where it is located
alternatively, you can first call obj:clear() to fill in the space behind the object, and after changing the position, call obj:update() to update only the object itself, but this method will not always work (for example, if the object is located on an object where the background is a image)
i recommend trying both of these methods and comparing the performance in your situation

by default, the gui library has enabled object intersection checking (if at least one window has been created) to redraw the objects that are above automatically
this allows you to change the contents of the objects below the windows without worrying that it will overlap the content above
the intersection check can work in three modes:
if you don't use object overlay, then it makes sense to disable this feature to save performance. use to do this: scene:setObjectIntersectionMode(false) or scene:setObjectIntersectionMode(0)
also, if you use gui:drawForce(), then you don't need the intersection check function to make everything work correctly.

gui tabs:

            --example for display 128x128
local display = getComponents("display")[1]
display.reset()
display.clearClicks()
display.setClicksAllowed(true)
local rx, ry = display.getWidth(), display.getHeight()

local gui = require("gui").new(display)
local styles = require("styles")
local objs = require("objs")
local scene = gui:createScene("777777")

local horizontalTabBar = scene:createCustom(0, 0, rx, 8, objs.tabbar, 0x444444, false, nil, 3)

for ix = 1, 4 do
    local window = horizontalTabBar:createOtherspaceWindow()
    local verticleTabBar = window:createCustom(0, 0, 26, ry - horizontalTabBar.sizeY, objs.tabbar, 0x444444, true, nil, 3)
    for iy = 1, 4 do
        local window2 = verticleTabBar:createOtherspaceWindow()
        verticleTabBar:addTab("VTAB" .. iy, window2)

        window2:createLabel(0, 0, window2.sizeX, window2.sizeY, "HTAB: " .. ix .. "\n" .. "VTAB: " .. iy, 0x000088, 0xffffff)
    end
    horizontalTabBar:addTab("HTAB" .. ix, window)
end

function callback_loop()
    if _endtick then
        display.clear()
        display.flush()
        return
    end

    gui:tick()
    if gui:needFlush() then
        gui:draw()
        display.flush()
    end
end
        

custom object class example:

            {
    useWindow = false, --by default, false, if set to true, your object will be created as a window, which will allow you to create child objects on it
    layerMode = 1, --default layerMode. by default 1
    autoViewport = false, --the default is false, but you can set this to true to limit your object rendering to the border of your object
    handlerLocalPosition = false, --by default, this is false, but you can set this to true so that the values in the "handler" are local to the object
    handlerAllClicks = false, --by default, false. if set to true, your handler will receive all clicks in general, even if they are in no way related to your object
    handlerOutsideDrag = false, --by default, false. if set to true, you will receive drag events, including outside your object, if the first click was inside your object
    --It makes sense to enable this flag even if you are using handlerAllClicks, as this will tell the gui library that your object remains "grappable" even when the user removes the cursor but does not release the button.
    init = function(self, ...) --you can execute code when creating an object, and you can also receive arguments
        local args = {...}
        --carefully create parameters in yourself to avoid conflicts with the gui library itself.
        --for example, names like "state" and "touchX" are already occupied, which may not be obvious
        --to avoid conflicts for sure, it's better to create a separate table in self to store the parameters of your object
        self.myVars = {creationArgs = args, example = 1, color = 0xff0000}
    end,
    drawer = function(self) --implement the rendering of your object here
        self.display.fillRect(self.x, self.y, self.sizeX, self.sizeY, self.myVars.color) --example
    end,
    handler = function(self, x, y, clickType, button, nickname, inZone, elementCapture) --handles item touches. please note that by default you get the global coordinates of the display!
        --please note that you may receive parameters: -1, -1, "released", -1, "unknown"
        --this happens if the object was activated during scene switching
        --you can also get a position outside of your object during "released" in cases where the user clicked on the object and released the touch outside the object, you will still receive an event, but it will be outside the object
        --if you have the handlerOutsideDrag flag set, you will receive drag events outside your object if the first click was inside your object
        --inZone is set to true if the click occurs inside the object, it works independently of handlerLocalPosition and handlerAllClicks
        --elementCapture is a flag that is used if your custom object is running in useWindow mode, it is set to true if it is currently interacting with a child object
        if clickType == "pressed" then
            self.myVars.color = math.random(0, 0xffffff)
            self:update() --you can return true from this function instead of explicitly calling self:update()
        end
    end,
    destroyHandler = function(self) --called when your object is deleted
        
    end,
    calculateSize = function(self) --you can define this function to automatically calculate the size of your object. you can write it yourself or use a ready-made function from gui library
        return 16, 16
    end,
    methods = { --you can implement custom functions for your component here

    }
}
        

an example for autoViewport:

            local display = getComponent("display")
display.reset()
display.clearClicks()
display.setClicksAllowed(true)
local rx, ry = display.getWidth(), display.getHeight()

local gui = require("gui").new(display)
local scene = gui:createScene(0x0000ff)
local object = scene:createCustom(nil, nil, 64, 64, {
    autoViewport = true, --set the render to the size of the object
    handlerLocalPosition = true,
    init = function(self)
        self.myVars = {creationArgs = args, example = 1, color = 0xff0000}
    end,
    drawer = function(self)
        self.display.fillCircle(self.x, self.y, self.sizeX, self.myVars.color)
    end,
    handler = function(self, x, y, clickType, button, nickname)
        if clickType == "pressed" then
            self.myVars.color = math.random(0, 0xffffff)
            self:update()
        end
    end
})
object:setCenter()

function callback_loop()
    if _endtick then
        display.clear()
        display.flush()
        return
    end

    gui:tick()
    if gui:needFlush() then
        gui:draw()
        display.flush()
    end
end
        

demonstration of the intersection check:

            --the example was created for a 128x128 screen
local display = getComponents("display")[1]
display.reset()
display.clearClicks()
display.setClicksAllowed(true)
local rx, ry = display.getWidth(), display.getHeight()

local gui = require("gui").new(display)
local styles = require("styles")
local scene = gui:createScene("777777")

local function addWindow()
    local window = scene:createWindow(16, 16, 64, 64, "2d2d2d")
    window:upPanel("058db8", "ffffff", "test window", true)
    window:setDraggable(true)
    
    local closeButton = window:panelButton(7, false, "X", "00a2d5", "0054a1", "00c2ff", "0085ff")
    closeButton:attachCallback(function(self, state, inZone)
        if not state and inZone then
            window:destroy()
        end
    end)
    
    local oldText
    for i = 1, 4 do
        local text = window:createText(nil, nil, "switch " .. i .. ": ")
        if oldText then text:setDown(oldText) end
        local switch = window:createButton(nil, nil, 8, 4, true, nil, "444444", "ffffff", "44b300", "ffffff")
        switch:setCustomStyle(styles.switch)
        switch:setRight(text)
        oldText = text
    end
    
    local window2 = window:createWindow(nil, nil, 32, 16, "333377")
    window2:setDown(oldText)
    window2:upPanel("058db8", "ffffff", "test", true)
    window2:minimize(true)
    local switch = window2:createButton(nil, nil, 14, 8, true, nil, "444444", "ffffff", "44b300", "ffffff")
    switch:setCustomStyle(styles.switch)
    
    function switch:onTick()
        switch:setBgColor(math.random(0, 0xffffff))
    end
end

local addWindowButton = scene:createButton(nil, nil, 32, 32, false, "WINDOW")
addWindowButton:attachCallback(function(self, state, inZone)
    if state then
        addWindow()
    end
end)

function scene:onTick()
    addWindowButton:setBgColor(math.random(0, 0xffffff))
end

function callback_loop()
    if _endtick then
        display.clear()
        display.flush()
        out(false)
        return
    end
    
    gui:tick()
    if gui:needFlush() then
        gui:draw()
        display.flush()
    end
end
        

custom object example:

            --example for display 64x64

local display = getComponent("display")

local width, height = display.getSize()
display.reset()
display.clearClicks()
display.setClicksAllowed(true)
display.clear()
display.flush()

local gui = require("gui").new(display)
local scene = gui:createScene(0x444444)

local rgbButtons = {
    init = function(self, partsX, partsY)
        self.colors = {}
        self.partsX = partsX
        self.partsY = partsY
        self.partSizeX = math.floor((self.sizeX / partsX) + 0.5)
        self.partSizeY = math.floor((self.sizeY / partsY) + 0.5)
        for ix = 0, partsX - 1 do
            self.colors[ix] = {}
            for iy = 0, partsY - 1 do
                self.colors[ix][iy] = 0
            end
        end
    end,
    drawer = function(self)
        self:clear(0x000000)
        for ix = 0, self.partsX - 1 do
            for iy = 0, self.partsY - 1 do
                local color
                local colorIdx = self.colors[ix][iy]
                if colorIdx == 0 then
                    color = 0xff0000
                elseif colorIdx == 1 then
                    color = 0x00ff00
                elseif colorIdx == 2 then
                    color = 0x0000ff
                end
                self.display.fillRect(self.x + (ix * self.partSizeX) + 1, self.y + (iy * self.partSizeY) + 1, self.partSizeX - 1, self.partSizeY - 1, color)
            end
        end
    end,
    handlerLocalPosition = true, --tells the library that you would like to get the click position relative to your object
    handler = function(self, x, y, action, button) -- if the object was clicked and then the scene switched, the method will be called with the parameters: self, -1, -1, "released", -1
        if action == "pressed" then
            self:togglePartIndex(math.floor(x / self.partSizeX), math.floor(y / self.partSizeY))
            self:update()
        end
    end,
    methods = { --here you can implement your own methods that the user of the component can call
        togglePartIndex = function(self, x, y)
            local colorIdx = self:getPartIndex(x, y)
            if not colorIdx then return end
            colorIdx = colorIdx + 1
            if colorIdx > 2 then
                colorIdx = 0
            end
            self:setPartIndex(x, y, colorIdx)
        end,
        setPartIndex = function(self, x, y, colorIdx)
            self.colors[x][y] = colorIdx
        end,
        getPartIndex = function(self, x, y)
            if not self.colors[x] then
                return
            end
            return self.colors[x][y]
        end
    }
}

--creating 4 instances of custom "rgbButtons" objects
local sizeX, sizeY = (width / 2) - 3, (height / 2) - 3
local rgbButtons1 = scene:createCustom(2, 2, sizeX, sizeY, rgbButtons, 4, 4)
local rgbButtons2 = scene:createCustom(nil, nil, sizeX, sizeY, rgbButtons, 4, 4)
rgbButtons2:setRight(rgbButtons1, 2)
local rgbButtons3 = scene:createCustom(nil, nil, sizeX, sizeY, rgbButtons, 4, 4)
rgbButtons3:setDown(rgbButtons1, 2)
local rgbButtons4 = scene:createCustom(nil, nil, sizeX, sizeY, rgbButtons, 4, 4)
rgbButtons4:setRight(rgbButtons3, 2)

rgbButtons2:setPartIndex(2, 2, 1)
rgbButtons4:setPartIndex(1, 1, 2)

scene:select()

function callback_loop()
    if _endtick then
        display.clear()
        display.flush()
        return
    end

    gui:tick()

    if gui:needFlush() then
        gui:draw()
        display.flush()
    end
end
        

gui & camera example:

            --example for display 128x128

local display = getComponent("display")
local camera = getComponent("camera")

local width, height = display.getSize()
display.reset()
display.clearClicks()
display.setClicksAllowed(true)
display.clear()
display.flush()

local gui = require("gui").new(display)
local scene = gui:createScene(function()
    camera.drawAdvanced(display, true)
end)

local rgbButtons = {
    init = function(self, partsX, partsY)
        self.colors = {}
        self.partsX = partsX
        self.partsY = partsY
        self.partSizeX = math.floor((self.sizeX / partsX) + 0.5)
        self.partSizeY = math.floor((self.sizeY / partsY) + 0.5)
        for ix = 0, partsX - 1 do
            self.colors[ix] = {}
            for iy = 0, partsY - 1 do
                self.colors[ix][iy] = 0
            end
        end
    end,
    drawer = function(self)
        self:clear(0x000000)
        for ix = 0, self.partsX - 1 do
            for iy = 0, self.partsY - 1 do
                local color
                local colorIdx = self.colors[ix][iy]
                if colorIdx == 0 then
                    color = 0xff0000
                elseif colorIdx == 1 then
                    color = 0x00ff00
                elseif colorIdx == 2 then
                    color = 0x0000ff
                end
                self.display.fillRect(self.x + (ix * self.partSizeX) + 1, self.y + (iy * self.partSizeY) + 1, self.partSizeX - 1, self.partSizeY - 1, color)
            end
        end
    end,
    handlerLocalPosition = true, --tells the library that you would like to get the click position relative to your object
    handler = function(self, x, y, action, button) -- if the object was clicked and then the scene switched, the method will be called with the parameters: self, -1, -1, "released", -1
        if action == "pressed" then
            self:togglePartIndex(math.floor(x / self.partSizeX), math.floor(y / self.partSizeY))
            self:update()
        end
    end,
    methods = { --here you can implement your own methods that the user of the component can call
        togglePartIndex = function(self, x, y)
            local colorIdx = self:getPartIndex(x, y)
            if not colorIdx then return end
            colorIdx = colorIdx + 1
            if colorIdx > 2 then
                colorIdx = 0
            end
            self:setPartIndex(x, y, colorIdx)
        end,
        setPartIndex = function(self, x, y, colorIdx)
            self.colors[x][y] = colorIdx
        end,
        getPartIndex = function(self, x, y)
            if not self.colors[x] then
                return
            end
            return self.colors[x][y]
        end
    }
}

local rgbButtons1 = scene:createCustom(2, 2, 29, 29, rgbButtons, 4, 4)

scene:select()

function callback_loop()
    if _endtick then
        display.clear()
        display.flush()
        return
    end

    gui:tick()
    gui:drawForce()
    display.flush()
end
        

example of large fonts (world cleaner code):

            --example for a 256x256 display

local fonts = require("fonts")
local display = getComponent("display")

display.reset()
display.clearClicks()
display.setClicksAllowed(true)
display.setFont(fonts.impact_32)
local width, height = display.getWidth(), display.getHeight()
local fwidth, fheight = display.getFontWidth(), display.getFontHeight()

local gui = require("gui").new(display)

------------------------------------------

local acceptCode

local acceptScene = gui:createScene(sm.color.new("#e530ff"))

local acceptLabel = acceptScene:createLabel(4, 4, width - 8, fheight * 1.2, nil, 0x000000, 0xff0000)

local accept = acceptScene:createButton(nil, nil, width - 8, fheight * 1.4, false, "accept")
accept:setDown(acceptLabel, 4)
accept:attachCallback(function (_, newstate, isZone)
    if not newstate and isZone then
        acceptCode()
        mainScene:select()
    end
end)

local cancel = acceptScene:createButton(nil, nil, width - 8, fheight * 1.4, false, "cancel")
cancel:setDown(accept, 4)
cancel:attachCallback(function (_, newstate, isZone)
    if not newstate and isZone then
        mainScene:select()
    end
end)

------------------------------------------

mainScene = gui:createScene(sm.color.new("#03a4ff"))

local label = mainScene:createLabel(4, 4, width - 8, fheight * 1.2, "what to delete?", 0x000000, 0xff0000)

local action1 = mainScene:createButton(nil, nil, width - 8, fheight * 1.4, false, "all bodies")
action1:setDown(label, 4)
action1:attachCallback(function (_, newstate, isZone)
    if global and not newstate and isZone then
        acceptLabel.text = "del all bodies"
        acceptCode = function()
            for _, body in ipairs(sm.body.getAllBodies()) do
                for _, shape in ipairs(body:getShapes()) do
                    shape:destroyShape()
                end
            end
        end
        acceptScene:select()
    end
end)

local action2 = mainScene:createButton(nil, nil, width - 8, fheight * 1.4, false, "loose bodies")
action2:setDown(action1, 4)
action2:attachCallback(function (_, newstate, isZone)
    if global and not newstate and isZone then
        acceptLabel.text = "del loose bodies"
        acceptCode = function()
            for _, body in ipairs(sm.body.getAllBodies()) do
                if body:isDynamic() then
                    for _, shape in ipairs(body:getShapes()) do
                        shape:destroyShape()
                    end
                end
            end
        end
        acceptScene:select()
    end
end)

local action3 = mainScene:createButton(nil, nil, width - 8, fheight * 1.4, false, "units")
action3:setDown(action2, 4)
action3:attachCallback(function (_, newstate, isZone)
    if global and not newstate and isZone then
        acceptLabel.text = "del units"
        acceptCode = function()
            for _, unit in ipairs(sm.unit.getAllUnits()) do
                unit:destroy()
            end
        end
        acceptScene:select()
    end
end)

------------------------------------------

local needUnsafeMode = gui:createScene(sm.color.new("#ff9d03"))
needUnsafeMode:createLabel(4, 4, width - 8, height - 8, "to use it, you need to activate unsafe mode", 0x000000, 0xffffff)

local _globalState
local function checkUnsafeMode()
    local globalState = not not global
    if globalState ~= _globalState then
        if globalState then
            mainScene:select()
        else
            needUnsafeMode:select()
        end
    end
    _globalState = globalState
end

checkUnsafeMode()

function callback_loop()
    if _endtick then
        display.clear()
        display.flush()
        return
    end

    checkUnsafeMode()

    gui:tick()
    if gui:needFlush() then
        gui:draw()
        display.flush()
    end
end
        

switch example:

            local display = getComponent("display")
display.reset()
display.clearClicks()
display.setSkipAtLags(false)
display.setClicksAllowed(true)
local rx, ry = display.getWidth(), display.getHeight()

local gui = require("gui").new(display)
local styles = require("styles")

local scene = gui:createScene("0000ff")
local switch = scene:createButton(4, (ry / 2) - 16, rx - 8, 32, true, nil, "444444", "ffffff", "44b300", "ffffff")
switch:setCustomStyle(styles.switch)
scene:select()

function callback_loop()
    if _endtick then
        display.clear()
        display.flush()
        out(false)
        return
    end

    gui:tick()

    out(switch:getState())

    if gui:needFlush() then
        gui:draw()
        display.flush()
    end
end
        

window example:

            --the example was created for a 128x128 screen
local display = getComponent("display")
display.reset()
display.clearClicks()
display.setSkipAtLags(false)
display.setClicksAllowed(true)
local rx, ry = display.getWidth(), display.getHeight()

local gui = require("gui").new(display)
local styles = require("styles")
local scene = gui:createScene("777777")

local function addWindow()
    local window = scene:createWindow(16, 16, 64, 64, "2d2d2d")
    window:upPanel("058db8", "ffffff", "test window", true)
    window:setDraggable(true)

    local closeButton = window:panelButton(7, false, "X", "00a2d5", "0054a1", "00c2ff", "0085ff")
    closeButton:attachCallback(function(self, state, inZone)
        if not state and inZone then
            window:destroy()
        end
    end)

    local oldText
    for i = 1, 4 do
        local text = window:createText(nil, nil, "switch " .. i .. ": ")
        if oldText then text:setDown(oldText) end
        local switch = window:createButton(nil, nil, 8, 4, true, nil, "444444", "ffffff", "44b300", "ffffff")
        switch:setCustomStyle(styles.switch)
        switch:setRight(text)
        oldText = text
    end

    local window2 = window:createWindow(nil, nil, 32, 16, "333377")
    window2:setDown(oldText)
    window2:upPanel("058db8", "ffffff", "test", true)
    window2:minimize(true)
    local switch = window2:createButton(nil, nil, 14, 8, true, nil, "444444", "ffffff", "44b300", "ffffff")
    switch:setCustomStyle(styles.switch)
end

local addWindowButton = scene:createButton(nil, nil, 32, 32, false, "WINDOW")
addWindowButton:attachCallback(function(self, state, inZone)
    if state then
        addWindow()
    end
end)

function callback_loop()
    if _endtick then
        display.clear()
        display.flush()
        out(false)
        return
    end

    gui:tick()
    if gui:needFlush() then
        gui:draw()
        display.flush()
    end
end