1439 lines
44 KiB
Lua
1439 lines
44 KiB
Lua
local utf8 = require("utf8")
|
|
local multi, thread = require("multi"):init()
|
|
local GLOBAL, THREAD = require("multi.integration.loveManager"):init()
|
|
local color = require("gui.core.color")
|
|
local gui = {}
|
|
local updater = multi:newProcessor("UpdateManager", true)
|
|
local drawer = multi:newProcessor("DrawManager", true)
|
|
local bit = require("bit")
|
|
local band, bor = bit.band, bit.bor
|
|
local cursor_hand = love.mouse.getSystemCursor("hand")
|
|
local clips = {}
|
|
local max, min, abs, rad, floor, ceil = math.max, math.min, math.abs, math.rad,
|
|
math.floor, math.ceil
|
|
local frame, image, text, box, video, button, anim = 0, 1, 2, 4, 8, 16, 32
|
|
local global_drag
|
|
local object_focus = gui
|
|
local first_loop = false
|
|
|
|
-- Types
|
|
gui.TYPE_FRAME = frame
|
|
gui.TYPE_IMAGE = image
|
|
gui.TYPE_TEXT = text
|
|
gui.TYPE_BOX = box
|
|
gui.TYPE_VIDEO = video
|
|
gui.TYPE_BUTTON = button
|
|
gui.TYPE_ANIM = anim
|
|
|
|
--
|
|
|
|
gui.__index = gui
|
|
gui.MOUSE_PRIMARY = 1
|
|
gui.MOUSE_SECONDARY = 2
|
|
gui.MOUSE_MIDDLE = 3
|
|
|
|
gui.ALIGN_CENTER = 0
|
|
gui.ALIGN_LEFT = 1
|
|
gui.ALIGN_RIGHT = 2
|
|
|
|
-- Connections
|
|
gui.Events = {} -- We are using fastmode for all connection objects.
|
|
gui.Events.OnQuit = multi:newConnection()
|
|
gui.Events.OnDirectoryDropped = multi:newConnection()
|
|
gui.Events.OnDisplayRotated = multi:newConnection()
|
|
gui.Events.OnFilesDropped = multi:newConnection()
|
|
gui.Events.OnFocus = multi:newConnection()
|
|
gui.Events.OnMouseFocus = multi:newConnection()
|
|
gui.Events.OnResized = multi:newConnection()
|
|
gui.Events.OnVisible = multi:newConnection()
|
|
gui.Events.OnKeyPressed = multi:newConnection()
|
|
gui.Events.OnKeyReleased = multi:newConnection()
|
|
gui.Events.OnTextEdited = multi:newConnection()
|
|
gui.Events.OnTextInputed = multi:newConnection()
|
|
gui.Events.OnMouseMoved = multi:newConnection()
|
|
gui.Events.OnMousePressed = multi:newConnection()
|
|
gui.Events.OnMouseReleased = multi:newConnection()
|
|
gui.Events.OnWheelMoved = multi:newConnection()
|
|
gui.Events.OnTouchMoved = multi:newConnection()
|
|
gui.Events.OnTouchPressed = multi:newConnection()
|
|
gui.Events.OnTouchReleased = multi:newConnection()
|
|
|
|
-- Non Love Events
|
|
|
|
gui.Events.OnThemeChanged = multi:newConnection()
|
|
|
|
-- Virtual gui init
|
|
gui.virtual = {}
|
|
|
|
-- Internal Connections
|
|
gui.Events.OnObjectFocusChanged = multi:newConnection()
|
|
|
|
-- Hooks
|
|
|
|
local function Hook(funcname, func)
|
|
if love[funcname] then
|
|
local cache = love[funcname]
|
|
love[funcname] = function(...)
|
|
cache(...)
|
|
func({}, ...)
|
|
end
|
|
else
|
|
love[funcname] = function(...) func({}, ...) end
|
|
end
|
|
end
|
|
|
|
-- This will run the hooks after everything has loaded
|
|
updater:newTask(function()
|
|
Hook("quit", gui.Events.OnQuit.Fire)
|
|
Hook("directorydropped", gui.Events.OnDirectoryDropped.Fire)
|
|
Hook("displayrotated", gui.Events.OnDisplayRotated.Fire)
|
|
Hook("filedropped", gui.Events.OnFilesDropped.Fire)
|
|
Hook("focus", gui.Events.OnFocus.Fire)
|
|
Hook("mousefocus", gui.Events.OnMouseFocus.Fire)
|
|
Hook("resize", gui.Events.OnResized.Fire)
|
|
Hook("visible", gui.Events.OnVisible.Fire)
|
|
Hook("keypressed", gui.Events.OnKeyPressed.Fire)
|
|
Hook("keyreleased", gui.Events.OnKeyReleased.Fire)
|
|
Hook("textedited", gui.Events.OnTextEdited.Fire)
|
|
Hook("textinput", gui.Events.OnTextInputed.Fire)
|
|
Hook("mousemoved", gui.Events.OnMouseMoved.Fire)
|
|
Hook("mousepressed", gui.Events.OnMousePressed.Fire)
|
|
Hook("mousereleased", gui.Events.OnMouseReleased.Fire)
|
|
Hook("wheelmoved", gui.Events.OnWheelMoved.Fire)
|
|
Hook("touchmoved", gui.Events.OnTouchMoved.Fire)
|
|
Hook("touchpressed", gui.Events.OnTouchPressed.Fire)
|
|
Hook("touchreleased", gui.Events.OnTouchReleased.Fire)
|
|
end)
|
|
|
|
-- Hotkeys
|
|
|
|
local has_hotkey = false
|
|
local hot_keys = {}
|
|
|
|
-- Wait for keys to release to reset
|
|
local unPress = updater:newFunction(function(keys)
|
|
thread.hold(function()
|
|
for key = 1, #keys["Keys"] do
|
|
if not love.keyboard.isDown(keys["Keys"][key]) then
|
|
keys.isBusy = false
|
|
return true
|
|
end
|
|
end
|
|
end)
|
|
end)
|
|
|
|
updater:newThread("GUI Hotkey Manager", function()
|
|
while true do
|
|
thread.hold(function() return has_hotkey end)
|
|
for i = 1, #hot_keys do
|
|
local good = true
|
|
for key = 1, #hot_keys[i]["Keys"] do
|
|
if not love.keyboard.isDown(hot_keys[i]["Keys"][key]) then
|
|
good = false
|
|
break
|
|
end
|
|
end
|
|
if good and not hot_keys[i].isBusy then
|
|
hot_keys[i]["Connection"]:Fire(hot_keys[i]["Ref"])
|
|
hot_keys[i].isBusy = true
|
|
unPress(hot_keys[i])
|
|
end
|
|
end
|
|
thread.sleep(.001)
|
|
end
|
|
end)
|
|
|
|
function gui:setHotKey(keys, conn)
|
|
has_hotkey = true
|
|
local conn = conn or multi:newConnection()
|
|
table.insert(hot_keys,
|
|
{Ref = self, Connection = conn, Keys = {unpack(keys)}})
|
|
return conn
|
|
end
|
|
|
|
-- Default HotKeys
|
|
gui.HotKeys = {}
|
|
|
|
-- Connections can be added together to create an OR logic to them, they can be multiplied together to create an AND logic to them
|
|
gui.HotKeys.OnSelectAll = gui:setHotKey({"lctrl", "a"}) +
|
|
gui:setHotKey({"rctrl", "a"})
|
|
|
|
gui.HotKeys.OnCopy = gui:setHotKey({"lctrl", "c"}) +
|
|
gui:setHotKey({"rctrl", "c"})
|
|
|
|
gui.HotKeys.OnPaste = gui:setHotKey({"lctrl", "v"}) +
|
|
gui:setHotKey({"rctrl", "v"})
|
|
|
|
gui.HotKeys.OnCut = gui:setHotKey({"lctrl", "x"}) +
|
|
gui:setHotKey({"rctrl", "x"})
|
|
|
|
gui.HotKeys.OnUndo = gui:setHotKey({"lctrl", "z"}) +
|
|
gui:setHotKey({"rctrl", "z"})
|
|
|
|
gui.HotKeys.OnRedo = gui:setHotKey({"lctrl", "y"}) +
|
|
gui:setHotKey({"rctrl", "y"}) +
|
|
gui:setHotKey({"lctrl", "lshift", "z"}) +
|
|
gui:setHotKey({"rctrl", "lshift", "z"}) +
|
|
gui:setHotKey({"lctrl", "rshift", "z"}) +
|
|
gui:setHotKey({"rctrl", "rshift", "z"})
|
|
|
|
-- Utils
|
|
|
|
function gui:getObjectFocus() return object_focus end
|
|
|
|
function gui:hasType(t) return band(self.type, t) == t end
|
|
|
|
function gui:move(x, y)
|
|
self.dualDim.offset.pos.x = self.dualDim.offset.pos.x + x
|
|
self.dualDim.offset.pos.y = self.dualDim.offset.pos.y + y
|
|
end
|
|
|
|
function gui:moveInBounds(dx, dy)
|
|
local x, y, w, h = self:getAbsolutes()
|
|
local x1, y1, w1, h1 = self.parent:getAbsolutes()
|
|
if (x + dx >= x1 or dx > 0) and (x + w + dx <= x1 + w1 or dx < 0) and
|
|
(y + dy >= y1 or dy > 0) and (y + h + dy <= y1 + h1 or dy < 0) then
|
|
self:move(dx, dy)
|
|
end
|
|
end
|
|
|
|
local function intersecpt(x1, y1, x2, y2, x3, y3, x4, y4)
|
|
|
|
local x5 = max(x1, x3)
|
|
local y5 = max(y1, y3)
|
|
local x6 = min(x2, x4)
|
|
local y6 = min(y2, y4)
|
|
|
|
-- no intersection
|
|
if x5 > x6 or y5 > y6 then
|
|
return 0, 0, 0, 0 -- Return a no
|
|
end
|
|
|
|
local x7 = x5
|
|
local y7 = y6
|
|
local x8 = x6
|
|
local y8 = y5
|
|
|
|
return x7, y7, abs(x7 - x8), abs(y7 - y8)
|
|
end
|
|
|
|
local function toCoordPoints(x, y, w, h) return x, y, x + w, y + h end
|
|
|
|
function gui:intersecpt(x, y, w, h)
|
|
local x1, y1, x2, y2 = toCoordPoints(self:getAbsolutes())
|
|
local x3, y3, x4, y4 = toCoordPoints(x, y, w, h)
|
|
|
|
return intersecpt(x1, y1, x2, y2, x3, y3, x4, y4)
|
|
end
|
|
|
|
function gui:isDescendantOf(obj)
|
|
local parent = self.parent
|
|
while parent ~= gui do
|
|
if parent == obj then return true end
|
|
parent = parent.parent
|
|
end
|
|
return false
|
|
end
|
|
|
|
function gui:getChildren() return self.children end
|
|
|
|
function gui:getAbsolutes() -- returns x, y, w, h
|
|
return (self.parent.w * self.dualDim.scale.pos.x) +
|
|
self.dualDim.offset.pos.x + self.parent.x,
|
|
(self.parent.h * self.dualDim.scale.pos.y) +
|
|
self.dualDim.offset.pos.y + self.parent.y, (self.parent.w *
|
|
self.dualDim.scale.size.x) + self.dualDim.offset.size.x,
|
|
(self.parent.h * self.dualDim.scale.size.y) +
|
|
self.dualDim.offset.size.y
|
|
end
|
|
|
|
function gui:getAllChildren(vis)
|
|
local children = self:getChildren()
|
|
local allChildren = {}
|
|
for i, child in ipairs(children) do
|
|
if not (vis) and child.visible == true then
|
|
allChildren[#allChildren + 1] = child
|
|
local grandChildren = child:getAllChildren()
|
|
for j, grandChild in ipairs(grandChildren) do
|
|
allChildren[#allChildren + 1] = grandChild
|
|
end
|
|
end
|
|
end
|
|
return allChildren
|
|
end
|
|
|
|
function gui:newThread(func)
|
|
return updater:newThread("ThreadHandler<" .. self.type .. ">", func, self,
|
|
thread)
|
|
end
|
|
|
|
function gui:setDualDim(x, y, w, h, sx, sy, sw, sh)
|
|
self.dualDim.offset = {
|
|
pos = {
|
|
x = x or self.dualDim.offset.pos.x,
|
|
y = y or self.dualDim.offset.pos.y
|
|
},
|
|
size = {
|
|
x = w or self.dualDim.offset.size.x,
|
|
y = h or self.dualDim.offset.size.y
|
|
}
|
|
}
|
|
self.dualDim.scale = {
|
|
pos = {
|
|
x = sx or self.dualDim.scale.pos.x,
|
|
y = sy or self.dualDim.scale.pos.y
|
|
},
|
|
size = {
|
|
x = sw or self.dualDim.scale.size.x,
|
|
y = sh or self.dualDim.scale.size.y
|
|
}
|
|
}
|
|
end
|
|
|
|
local image_cache = {}
|
|
function gui:getTile(i, x, y, w, h) -- returns imagedata
|
|
local tw, wh
|
|
if i == nil then return end
|
|
if type(i) == "string" then i = image_cache[i] or i end
|
|
if type(i) == "string" then
|
|
i = love.image.newImageData(i)
|
|
image_cache[i] = i
|
|
elseif type(i) == "userdata" then
|
|
-- do nothing
|
|
elseif self:hasType(image) then
|
|
i, x, y, w, h = self.image, i, x, y, w
|
|
else
|
|
error("getTile invalid args!!! Usage: ImageElement:getTile(x,y,w,h) or gui:getTile(imagedata,x,y,w,h)")
|
|
end
|
|
return i, love.graphics.newQuad(x, y, w, h, i:getWidth(), i:getHeight())
|
|
end
|
|
|
|
function gui:topStack()
|
|
local siblings = self.parent.children
|
|
for i = 1, #siblings do
|
|
if siblings[i] == self then
|
|
table.remove(siblings, i)
|
|
break
|
|
end
|
|
end
|
|
siblings[#siblings + 1] = self
|
|
end
|
|
|
|
function gui:bottomStack()
|
|
local siblings = self.parent.children
|
|
for i = 1, #siblings do
|
|
if siblings[i] == self then
|
|
table.remove(siblings, i)
|
|
break
|
|
end
|
|
end
|
|
table.insert(siblings, 1, self)
|
|
end
|
|
|
|
local mainupdater = updater:newLoop().OnLoop
|
|
|
|
function gui:canPress(mx, my) -- Get the intersection of the clip area and the self then test with the clip, otherwise test as normal
|
|
local x, y, w, h
|
|
if self.__variables.clip[1] then
|
|
local clip = self.__variables.clip
|
|
x, y, w, h = self:intersecpt(clip[2], clip[3], clip[4], clip[5])
|
|
return mx < x + w and mx > x and my + h < y + h and my + h > y
|
|
else
|
|
x, y, w, h = self:getAbsolutes()
|
|
end
|
|
return not (mx > x + w or mx < x or my > y + h or my < y)
|
|
end
|
|
|
|
function gui:isBeingCovered(mx, my)
|
|
local children = gui:getAllChildren()
|
|
for i = #children, 1, -1 do
|
|
if children[i] == self then
|
|
return false
|
|
elseif children[i]:canPress(mx, my) and not (children[i] == self) and
|
|
not (children[i].ignore) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function gui:getLocalCords(mx, my)
|
|
x, y, w, h = self:getAbsolutes()
|
|
return mx - x, my - y
|
|
end
|
|
|
|
function gui:setParent(parent)
|
|
local temp = self.parent:getChildren()
|
|
for i = 1, #temp do
|
|
if temp[i] == self then
|
|
table.remove(self.parent.children, i)
|
|
break
|
|
end
|
|
end
|
|
if parent then
|
|
table.insert(parent.children, self)
|
|
self.parent = parent
|
|
end
|
|
end
|
|
|
|
local function processDo(ref) ref.Do[1]() end
|
|
|
|
function gui:clone(opt)
|
|
--[[
|
|
{
|
|
copyTo: Who to set the parent to
|
|
connections: Do we copy connections? (true/false)
|
|
}
|
|
]]
|
|
-- DO = {[[setImage]], c.image or IMAGE}
|
|
-- Connections are used greatly throughout do we copy those
|
|
local temp
|
|
local u = self:getUniques()
|
|
if self.type == frame then
|
|
temp = gui:newFrame(self:getDualDim())
|
|
elseif self.type == text + box then
|
|
temp = gui:newTextBox(self.text, self:getDualDim())
|
|
elseif self.type == text + button then
|
|
temp = gui:newTextButton(self.text, self:getDualDim())
|
|
elseif self.type == text then
|
|
temp = gui:newTextLabel(self.text, self:getDualDim())
|
|
elseif self.type == image + button then
|
|
temp = gui:newImageButton(u.DO[2], self:getDualDim())
|
|
elseif self.type == image then
|
|
temp = gui:newImageLabel(u.DO[2], self:getDualDim())
|
|
else -- We are dealing with a complex object
|
|
temp = processDo(u)
|
|
end
|
|
|
|
for i, v in pairs(u) do temp[i] = v end
|
|
|
|
local conn
|
|
if opt then
|
|
temp:setParent(opt.copyTo or gui.virtual)
|
|
if opt.connections then
|
|
conn = true
|
|
for i, v in pairs(self) do
|
|
if v.Type == "connector" then
|
|
-- We want to copy the connection functions from the original object and bind them to the new one
|
|
if not temp[i] then
|
|
-- Incase we are dealing with a custom object, create a connection if the custom objects unique declearation didn't
|
|
temp[i] = multi:newConnection()
|
|
end
|
|
temp[i]:Bind(v:getConnections())
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- This recursively clones and sets the parent to the temp
|
|
for i, v in pairs(self:getChildren()) do
|
|
v:clone({copyTo = temp, connections = conn})
|
|
end
|
|
|
|
return temp
|
|
end
|
|
|
|
function gui:isActive()
|
|
return self.active and not (self:isDescendantOf(gui.virtual))
|
|
end
|
|
|
|
-- Base get uniques
|
|
function gui:getUniques(tab)
|
|
local base = {
|
|
active = self.active,
|
|
visible = self.visible,
|
|
visibility = self.visibility,
|
|
color = self.color,
|
|
borderColor = self.borderColor,
|
|
rotation = self.rotation
|
|
}
|
|
|
|
if tab then for i, v in pairs(tab) do base[i] = tab[i] end end
|
|
return base
|
|
end
|
|
|
|
-- Base Library
|
|
function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual)
|
|
local c = {}
|
|
local buildBackBetter
|
|
local centerX = false
|
|
local centerY = false
|
|
local centering = false
|
|
local dragbutton = 2
|
|
local draggable = false
|
|
local hierarchy = false
|
|
|
|
local function testHierarchy(c, x, y, button, istouch, presses)
|
|
if hierarchy then
|
|
return not (global_drag or c:isBeingCovered(x, y))
|
|
end
|
|
return true
|
|
end
|
|
|
|
setmetatable(c, gui)
|
|
c.__variables = {clip = {false, 0, 0, 0, 0}}
|
|
c.active = true
|
|
c.type = typ
|
|
c.dualDim = self:newDualDim(x, y, w, h, sx, sy, sw, sh)
|
|
c.children = {}
|
|
c.visible = true
|
|
c.visibility = 1
|
|
c.color = {.6, .6, .6}
|
|
c.borderColor = color.black
|
|
c.rotation = 0
|
|
|
|
c.OnPressed = testHierarchy .. multi:newConnection()
|
|
c.OnPressedOuter = multi:newConnection()
|
|
c.OnReleased = testHierarchy .. multi:newConnection()
|
|
c.OnReleasedOuter = multi:newConnection()
|
|
c.OnReleasedOther = multi:newConnection()
|
|
|
|
c.OnDragStart = multi:newConnection()
|
|
c.OnDragging = multi:newConnection()
|
|
c.OnDragEnd = multi:newConnection()
|
|
|
|
c.OnEnter = testHierarchy .. multi:newConnection()
|
|
c.OnExit = multi:newConnection()
|
|
|
|
c.OnMoved = testHierarchy .. multi:newConnection()
|
|
|
|
local dragging = false
|
|
local entered = false
|
|
local moved = false
|
|
local pressed = false
|
|
|
|
gui.Events.OnMouseMoved(function(x, y, dx, dy, istouch)
|
|
if not c:isActive() then return end
|
|
if c:canPress(x, y) then
|
|
c.OnMoved:Fire(c, x, y, dx, dy, istouch)
|
|
if entered == false then
|
|
c.OnEnter:Fire(c, x, y)
|
|
entered = true
|
|
end
|
|
if dragging then
|
|
c.OnDragging:Fire(c, dx, dy, x, y, istouch)
|
|
end
|
|
elseif entered then
|
|
entered = false
|
|
c.OnExit:Fire(c, x, y)
|
|
end
|
|
end)
|
|
|
|
gui.Events.OnMouseReleased(function(x, y, button, istouch, presses)
|
|
if not c:isActive() then return end
|
|
if c:canPress(x, y) then
|
|
c.OnReleased:Fire(c, x, y, dx, dy, istouch, presses)
|
|
elseif pressed then
|
|
c.OnReleasedOuter:Fire(c, x, y, button, istouch, presses)
|
|
else
|
|
c.OnReleasedOther:Fire(c, x, y, button, istouch, presses)
|
|
end
|
|
pressed = false
|
|
if dragging and button == dragbutton then
|
|
dragging = false
|
|
global_drag = false
|
|
c.OnDragEnd:Fire(c, dx, dy, x, y, istouch, presses)
|
|
end
|
|
end)
|
|
|
|
gui.Events.OnMousePressed(function(x, y, button, istouch, presses)
|
|
if not c:isActive() then return end
|
|
if c:canPress(x, y) then
|
|
c.OnPressed:Fire(c, x, y, dx, dy, istouch)
|
|
pressed = true
|
|
|
|
-- Only change and trigger the event if it is a different object
|
|
if c ~= object_focus then
|
|
gui.Events.OnObjectFocusChanged:Fire(object_focus, c)
|
|
object_focus = c
|
|
end
|
|
|
|
if draggable and button == dragbutton and not c:isBeingCovered(x, y) and
|
|
not global_drag then
|
|
dragging = true
|
|
global_drag = true
|
|
c.OnDragStart:Fire(c, dx, dy, x, y, istouch)
|
|
end
|
|
else
|
|
c.OnPressedOuter:Fire(c, x, y, button, istouch, presses)
|
|
end
|
|
end)
|
|
|
|
function c:setRoundness(rx, ry, seg, side)
|
|
self.roundness = side or true
|
|
self.__rx, self.__ry, self.__segments = rx or 5, ry or 5, seg or 30
|
|
end
|
|
|
|
function c:setRoundnessDirection(hori, vert)
|
|
self.__rhori = hori
|
|
self.__rvert = vert
|
|
end
|
|
|
|
function c:respectHierarchy(bool) hierarchy = bool end
|
|
|
|
function c:OnUpdate(func) -- Not crazy about this approach, will probably rework this
|
|
if type(self) == "function" then func = self end
|
|
mainupdater(function() func(c) end)
|
|
end
|
|
|
|
local function centerthread()
|
|
local centerfunc = function()
|
|
return centerX or centerY -- If the condition is true it acts like a yield
|
|
end
|
|
c:newThread(function()
|
|
while true do
|
|
thread.hold(centerfunc)
|
|
local x, y, w, h = c:getAbsolutes()
|
|
if centerX then
|
|
c:setDualDim(-w / 2, nil, nil, nil, .5)
|
|
end
|
|
if centerY then
|
|
c:setDualDim(nil, -h / 2, nil, nil, nil, .5)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
function c:enableDragging(but)
|
|
if not but then
|
|
draggable = false
|
|
return
|
|
end
|
|
dragbutton = but or dragbutton
|
|
draggable = true
|
|
end
|
|
|
|
function c:centerX(bool)
|
|
centerX = bool
|
|
if centering then return end
|
|
centering = true
|
|
centerthread()
|
|
end
|
|
|
|
function c:centerY(bool)
|
|
centerY = bool
|
|
if centering then return end
|
|
centering = true
|
|
centerthread()
|
|
end
|
|
|
|
-- Add to the parents children table
|
|
if virtual then
|
|
c.parent = gui.virtual
|
|
table.insert(gui.virtual.children, c)
|
|
else
|
|
c.parent = self
|
|
table.insert(self.children, c)
|
|
end
|
|
return c
|
|
end
|
|
|
|
function gui:newDualDim(x, y, w, h, sx, sy, sw, sh)
|
|
local dd = {}
|
|
dd.offset = {}
|
|
dd.scale = {}
|
|
dd.offset.pos = {x = x or 0, y = y or 0}
|
|
dd.offset.size = {x = w or 0, y = h or 0}
|
|
dd.scale.pos = {x = sx or 0, y = sy or 0}
|
|
dd.scale.size = {x = sw or 0, y = sh or 0}
|
|
return dd
|
|
end
|
|
|
|
function gui:getDualDim()
|
|
local dd = self.dualDim
|
|
return dd.offset.pos.x, dd.offset.pos.y, dd.offset.size.x, dd.offset.size.y,
|
|
dd.scale.pos.x, dd.scale.pos.y, dd.scale.size.x, dd.scale.size.y
|
|
end
|
|
|
|
-- Frames
|
|
function gui:newFrame(x, y, w, h, sx, sy, sw, sh)
|
|
return self:newBase(frame, x, y, w, h, sx, sy, sw, sh)
|
|
end
|
|
|
|
function gui:newVirtualFrame(x, y, w, h, sx, sy, sw, sh)
|
|
return self:newBase(frame, x, y, w, h, sx, sy, sw, sh, true)
|
|
end
|
|
local testIMG
|
|
-- Texts
|
|
function gui:newTextBase(typ, txt, x, y, w, h, sx, sy, sw, sh)
|
|
local c = self:newBase(text + typ, x, y, w, h, sx, sy, sw, sh)
|
|
c.text = txt
|
|
c.align = gui.ALIGN_LEFT
|
|
c.adjust = 0
|
|
c.textScaleX = 1
|
|
c.textScaleY = 1
|
|
c.textOffsetX = 0
|
|
c.textOffsetY = 0
|
|
c.textShearingFactorX = 0
|
|
c.textShearingFactorY = 0
|
|
c.textVisibility = 1
|
|
c.font = love.graphics.newFont(12)
|
|
c.textColor = color.black
|
|
c.OnFontUpdated = multi:newConnection()
|
|
|
|
function c:calculateFontOffset(font, adjust)
|
|
local adjust = adjust or 20
|
|
local x, y, width, height = self:getAbsolutes()
|
|
local top = height + adjust
|
|
local bottom = 0
|
|
local canvas = love.graphics.newCanvas(width, height + adjust)
|
|
love.graphics.setCanvas(canvas)
|
|
love.graphics.clear(0, 0, 0, .5, false, false)
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.setFont(font)
|
|
love.graphics.printf(self.text, 0, adjust / 2, width, "left",
|
|
self.rotation, self.textScaleX, self.textScaleY, 0,
|
|
0, self.textShearingFactorX,
|
|
self.textShearingFactorY)
|
|
love.graphics.setCanvas()
|
|
local data = canvas:newImageData()
|
|
local f_top, f_bot = false, false
|
|
for yy = 0, height - 1 do
|
|
for xx = 0, width - 1 do
|
|
local r, g, b, a = data:getPixel(xx, yy)
|
|
if r ~= 0 or g ~= 0 or b ~= 0 then
|
|
if yy < top and not f_top then
|
|
top = yy
|
|
f_top = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
for yy = height - 1, 0, -1 do
|
|
for xx = 0, width - 1 do
|
|
local r, g, b, a = data:getPixel(xx, yy)
|
|
if r ~= 0 or g ~= 0 or b ~= 0 then
|
|
if yy > bottom and not f_bot then
|
|
bottom = yy
|
|
f_bot = false
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return top - adjust, bottom - adjust
|
|
end
|
|
|
|
function c:setFont(font, size)
|
|
if type(font) == "string" then
|
|
self.fontFile = font
|
|
self.font = love.graphics.newFont(font, size)
|
|
else
|
|
self.font = font
|
|
end
|
|
self.OnFontUpdated:Fire(self)
|
|
end
|
|
|
|
function c:fitFont(n, max)
|
|
local max = max or math.huge
|
|
local font
|
|
local isdefault = false
|
|
if self.fontFile then
|
|
if self.fontFile:match("ttf") then
|
|
font = function(n)
|
|
return love.graphics.newFont(self.fontFile, n, "normal")
|
|
end
|
|
else
|
|
font = function(n)
|
|
return love.graphics.newFont(self.fontFile, n)
|
|
end
|
|
end
|
|
else
|
|
isdefault = true
|
|
font = function(n) return love.graphics.setNewFont(n) end
|
|
end
|
|
local x, y, width, height = self:getAbsolutes()
|
|
local Font, text = self.Font, self.text
|
|
local s = 3
|
|
Font = font(s)
|
|
while height < max and Font:getHeight() < height and Font:getWidth(text) < width do
|
|
s = s + 1
|
|
Font = font(s)
|
|
end
|
|
Font = font(s - (4 + (n or 0)))
|
|
Font:setFilter("linear", "nearest", 4)
|
|
self.font = Font
|
|
self.textOffsetY = 0
|
|
local top, bottom = self:calculateFontOffset(Font, 0)
|
|
self.textOffsetY = floor(((height - bottom) - top) / 2)
|
|
self.OnFontUpdated:Fire(self)
|
|
return s - (4 + (n or 0))
|
|
end
|
|
|
|
function c:centerFont()
|
|
local x, y, width, height = self:getAbsolutes()
|
|
local top, bottom = self:calculateFontOffset(self.font, 0)
|
|
self.textOffsetY = floor(((height - bottom) - top) / 2)
|
|
self.OnFontUpdated:Fire(self)
|
|
end
|
|
|
|
function c:getUniques()
|
|
return gui.getUniques(c, {
|
|
text = c.text,
|
|
align = c.align,
|
|
textScaleX = c.textScaleX,
|
|
textScaleY = c.textScaleY,
|
|
textOffsetX = c.textOffsetX,
|
|
textOffsetY = c.textOffsetY,
|
|
textShearingFactorX = c.textShearingFactorX,
|
|
textShearingFactorY = c.textShearingFactorY,
|
|
textVisibility = c.textVisibility,
|
|
font = c.font,
|
|
textColor = c.textColor
|
|
})
|
|
end
|
|
return c
|
|
end
|
|
|
|
function gui:newTextButton(txt, x, y, w, h, sx, sy, sw, sh)
|
|
local c = self:newTextBase(button, txt, x, y, w, h, sx, sy, sw, sh)
|
|
c:respectHierarchy(true)
|
|
|
|
c.OnEnter(function(c, x, y, dx, dy, istouch)
|
|
love.mouse.setCursor(cursor_hand)
|
|
end)
|
|
|
|
c.OnExit(function(c, x, y, dx, dy, istouch) love.mouse.setCursor() end)
|
|
|
|
return c
|
|
end
|
|
|
|
function gui:newTextLabel(txt, x, y, w, h, sx, sy, sw, sh)
|
|
return self:newTextBase(frame, txt, x, y, w, h, sx, sy, sw, sh)
|
|
end
|
|
|
|
-- local val used when drawing
|
|
|
|
local function getTextPosition(text, self, mx, my, exact)
|
|
-- Initialize variables
|
|
local pos = 0
|
|
local font = love.graphics.getFont()
|
|
local width = 0
|
|
local height = font:getHeight()
|
|
-- Loop through each character in the string
|
|
for i = 1, #text do
|
|
|
|
local _w = font:getWidth(text:sub(i, i))
|
|
local x, y, w, h = math.floor(width + self.adjust + self.textOffsetX),
|
|
0, _w, height
|
|
|
|
width = width + _w
|
|
|
|
if not (mx > x + w or mx < x or my > y + h or my < y) then
|
|
if not (exact) and
|
|
(_w -
|
|
(width - (mx - math.floor(self.adjust + self.textOffsetX))) <
|
|
_w / 2 and i >= 1) then
|
|
return i - 1
|
|
else
|
|
return i
|
|
end
|
|
elseif i == #text and mx > x + w then
|
|
return #text
|
|
end
|
|
end
|
|
return pos
|
|
end
|
|
|
|
local cur = love.mouse.getCursor()
|
|
function gui:newTextBox(txt, x, y, w, h, sx, sy, sw, sh)
|
|
local c = self:newTextBase(box, txt, x, y, w, h, sx, sy, sw, sh)
|
|
c:respectHierarchy(true)
|
|
c.doSelection = false
|
|
|
|
c.OnReturn = multi:newConnection()
|
|
|
|
c.cur_pos = 0
|
|
c.selection = {0, 0}
|
|
|
|
function c:getUniques()
|
|
return gui.getUniques(c, {
|
|
doSelection = c.doSelection,
|
|
cur_pos = c.cur_pos,
|
|
adjust = c.adjust
|
|
})
|
|
end
|
|
|
|
function c:HasSelection()
|
|
return c.selection[1] ~= 0 and c.selection[2] ~= 0
|
|
end
|
|
|
|
function c:GetSelection()
|
|
local start, stop = c.selection[1], c.selection[2]
|
|
if start > stop then start, stop = stop, start end
|
|
return start, stop
|
|
end
|
|
|
|
function c:GetSelectedText()
|
|
if not c:HasSelection() then return "" end
|
|
local sta, sto = c.selection[1], c.selection[2]
|
|
if sta > sto then sta, sto = sto, sta end
|
|
return c.text:sub(sta, sto)
|
|
end
|
|
|
|
function c:ClearSelection()
|
|
c.doSelection = false
|
|
c.selection = {0, 0}
|
|
end
|
|
|
|
c.OnEnter(function(c, x, y, dx, dy, istouch)
|
|
love.mouse.setCursor(love.mouse.getSystemCursor("ibeam"))
|
|
end)
|
|
|
|
c.OnExit(function(c, x, y, dx, dy, istouch) love.mouse.setCursor(cur) end)
|
|
|
|
c.OnPressed(function(c, x, y, dx, dy, istouch)
|
|
object_focus.bar_show = true
|
|
c.cur_pos = getTextPosition(c.text, c, c:getLocalCords(x, y))
|
|
c.selection[1] = c.cur_pos
|
|
c.doSelection = true
|
|
end)
|
|
|
|
c.OnMoved(function(c, x, y, dx, dy, istouch)
|
|
if c.doSelection then
|
|
local xx, yy = c:getLocalCords(x, y)
|
|
c.selection[2] = getTextPosition(c.text, c, xx, yy, true)
|
|
end
|
|
end); -- Needed to keep next line from being treated like a function call
|
|
|
|
-- Connect to both events
|
|
(c.OnReleased + c.OnReleasedOuter)(function(c, x, y, dx, dy, istouch)
|
|
c.doSelection = false
|
|
end);
|
|
|
|
-- ReleasedOther is different than ReleasedOuter (Other/Outer)
|
|
(c.OnReleasedOther + c.OnPressedOuter)(function()
|
|
c.doSelection = false
|
|
c.selection = {0, 0}
|
|
end)
|
|
|
|
c.OnPressedOuter(function() c.bar_show = false end)
|
|
|
|
return c
|
|
end
|
|
|
|
local function textBoxThread()
|
|
updater:newThread("Textbox Handler", function()
|
|
while true do
|
|
-- Do nothing if we aren't dealing with a textbox
|
|
thread.hold(function() return object_focus:hasType(box) end)
|
|
local ref = object_focus
|
|
ref.bar_show = true
|
|
thread.sleep(.5)
|
|
ref.bar_show = false
|
|
thread.sleep(.5)
|
|
end
|
|
end).OnError(textBoxThread)
|
|
end
|
|
textBoxThread()
|
|
|
|
local function insert(obj, n_text)
|
|
if obj:HasSelection() then
|
|
local start, stop = obj:GetSelection()
|
|
obj.text = obj.text:sub(1, start - 1) .. n_text ..
|
|
obj.text:sub(stop + 1, -1)
|
|
obj:ClearSelection()
|
|
obj.cur_pos = start
|
|
if #n_text > 1 then obj.cur_pos = start + #n_text end
|
|
else
|
|
obj.text = obj.text:sub(1, obj.cur_pos) .. n_text ..
|
|
obj.text:sub(obj.cur_pos + 1, -1)
|
|
obj.cur_pos = obj.cur_pos + 1
|
|
if #n_text > 1 then obj.cur_pos = obj.cur_pos + #n_text end
|
|
end
|
|
end
|
|
|
|
local function delete(obj, cmd)
|
|
if obj:HasSelection() then
|
|
local start, stop = obj:GetSelection()
|
|
obj.text = obj.text:sub(1, start - 1) .. obj.text:sub(stop + 1, -1)
|
|
obj:ClearSelection()
|
|
obj.cur_pos = start - 1
|
|
else
|
|
if cmd == "delete" then
|
|
obj.text = obj.text:sub(1, obj.cur_pos) ..
|
|
obj.text:sub(obj.cur_pos + 2, -1)
|
|
else
|
|
obj.text = obj.text:sub(1, obj.cur_pos - 1) ..
|
|
obj.text:sub(obj.cur_pos + 1, -1)
|
|
object_focus.cur_pos = object_focus.cur_pos - 1
|
|
if object_focus.cur_pos == 0 then
|
|
object_focus.cur_pos = 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
gui.Events.OnObjectFocusChanged(function(prev, new)
|
|
--
|
|
end)
|
|
|
|
gui.HotKeys.OnSelectAll(function()
|
|
if object_focus:hasType(box) then
|
|
object_focus.selection = {1, #object_focus.text}
|
|
end
|
|
end)
|
|
|
|
gui.Events.OnTextInputed(function(text)
|
|
if object_focus:hasType(box) then insert(object_focus, text) end
|
|
end)
|
|
|
|
gui.HotKeys.OnCopy(function()
|
|
if object_focus:hasType(box) then
|
|
love.system.setClipboardText(object_focus:GetSelectedText())
|
|
end
|
|
end)
|
|
|
|
gui.HotKeys.OnPaste(function()
|
|
if object_focus:hasType(box) then
|
|
insert(object_focus, love.system.getClipboardText())
|
|
end
|
|
end)
|
|
|
|
gui.HotKeys.OnCut(function()
|
|
if object_focus:hasType(box) and object_focus:HasSelection() then
|
|
love.system.setClipboardText(object_focus:GetSelectedText())
|
|
delete(object_focus, "backspace")
|
|
end
|
|
end)
|
|
|
|
gui.Events.OnKeyPressed(function(key, scancode, isrepeat)
|
|
-- Don't process if we aren't dealing with a textbox
|
|
if not object_focus:hasType(box) then return end
|
|
if key == "left" then
|
|
object_focus.cur_pos = object_focus.cur_pos - 1
|
|
object_focus.bar_show = true
|
|
elseif key == "right" then
|
|
object_focus.cur_pos = object_focus.cur_pos + 1
|
|
object_focus.bar_show = true
|
|
elseif key == "return" then
|
|
object_focus.OnReturn:Fire(object_focus, object_focus.text)
|
|
elseif key == "backspace" then
|
|
delete(object_focus, "backspace")
|
|
elseif key == "delete" then
|
|
delete(object_focus, "delete")
|
|
end
|
|
end)
|
|
|
|
-- Images
|
|
|
|
local load_image = THREAD:newFunction(function(path)
|
|
require("love.image")
|
|
return love.image.newImageData(path)
|
|
end)
|
|
|
|
local load_images = THREAD:newFunction(function(paths)
|
|
require("love.image")
|
|
local images = #paths
|
|
for i = 1, #paths do
|
|
sThread.pushStatus(i, images, love.image.newImageData(paths[i]))
|
|
end
|
|
end)
|
|
|
|
-- Loads a resource and adds it to the cache
|
|
gui.cacheImage = thread:newFunction(function(self, path_or_paths)
|
|
if type(path_or_paths) == "string" then
|
|
-- runs thread to load image then cache it for faster loading
|
|
load_image(path_or_paths).OnReturn(function(img)
|
|
image_cache[path_or_paths] = img
|
|
end)
|
|
-- table of paths
|
|
elseif type(path_or_paths) == "table" then
|
|
local handler = load_images(path_or_paths)
|
|
handler.OnStatus(function(part, whole, img)
|
|
image_cache[path_or_paths[part]] = img
|
|
thread.pushStatus(part, whole, image_cache[path_or_paths[part]])
|
|
end)
|
|
end
|
|
end)
|
|
|
|
function gui:newImageBase(typ, x, y, w, h, sx, sy, sw, sh)
|
|
local c = self:newBase(image + typ, x, y, w, h, sx, sy, sw, sh)
|
|
c.color = color.white
|
|
c.visibility = 0
|
|
local IMAGE
|
|
|
|
function c:getUniques()
|
|
return gui.getUniques(c, {
|
|
-- Recreating the image object using set image is the way to go
|
|
DO = {[[setImage]], c.image or IMAGE}
|
|
})
|
|
end
|
|
|
|
c.setImage = function(self, i, x, y, w, h)
|
|
if i == nil then return end
|
|
local img = load_image(i)
|
|
load_image(i).OnReturn(function(img)
|
|
img = love.graphics.newImage(img)
|
|
IMAGE = i
|
|
if type(i) == "string" then i = image_cache[i] or i end
|
|
|
|
if i and x then
|
|
self.imageHeigth = h
|
|
self.imageWidth = w
|
|
|
|
if type(i) == "string" then
|
|
image_cache[i] = img
|
|
i = image_cache[i]
|
|
end
|
|
|
|
self.image = i
|
|
self.image:setWrap("repeat", "repeat")
|
|
self.imageColor = color.white
|
|
self.quad = love.graphics.newQuad(x, y, w, h, self.image:getWidth(), self.image:getHeight())
|
|
self.imageVisibility = 1
|
|
|
|
return
|
|
end
|
|
|
|
if type(i) == "userdata" and i:type() == "Image" then
|
|
img = i
|
|
end
|
|
|
|
local x, y, w, h = self:getAbsolutes()
|
|
self.imageColor = color.white
|
|
self.imageVisibility = 1
|
|
self.image = img
|
|
self.image:setWrap("repeat", "repeat")
|
|
self.imageHeigth = img:getHeight()
|
|
self.imageWidth = img:getWidth()
|
|
self.quad = love.graphics.newQuad(0, 0, self.imageWidth, self.imageHeigth, self.imageWidth, self.imageHeigth)
|
|
end)
|
|
end
|
|
return c
|
|
end
|
|
|
|
function gui:newImageLabel(source, x, y, w, h, sx, sy, sw, sh)
|
|
local c = self:newImageBase(frame, x, y, w, h, sx, sy, sw, sh)
|
|
c:setImage(source)
|
|
return c
|
|
end
|
|
|
|
function gui:newImageButton(source, x, y, w, h, sx, sy, sw, sh)
|
|
local c = self:newImageBase(frame, x, y, w, h, sx, sy, sw, sh)
|
|
c:respectHierarchy(true)
|
|
c:setImage(source)
|
|
|
|
c.OnEnter(function(c, x, y, dx, dy, istouch)
|
|
love.mouse.setCursor(cursor_hand)
|
|
end)
|
|
|
|
c.OnExit(function(c, x, y, dx, dy, istouch) love.mouse.setCursor() end)
|
|
|
|
return c
|
|
end
|
|
|
|
-- Video
|
|
function gui:newVideo(source, x, y, w, h, sx, sy, sw, sh)
|
|
local c = self:newImageBase(video, x, y, w, h, sx, sy, sw, sh)
|
|
c.OnVideoFinished = multi:newConnection()
|
|
c.playing = false
|
|
|
|
function c:setVideo(v)
|
|
if type(v) == "string" then
|
|
c.video = love.graphics.newVideo(v)
|
|
elseif v then
|
|
c.video = v
|
|
end
|
|
c.audiosource = c.video:getSource()
|
|
if c.audiosource then c.audioLength = c.audiosource:getDuration() end
|
|
c.videoHeigth = c.video:getHeight()
|
|
c.videoWidth = c.video:getWidth()
|
|
c.quad = love.graphics.newQuad(0, 0, w, h, c.videoWidth, c.videoHeigth)
|
|
end
|
|
|
|
function c:getVideo() return self.video end
|
|
|
|
if type(source) == "string" then c:setVideo(source) end
|
|
|
|
function c:play()
|
|
c.playing = true
|
|
c.video:play()
|
|
end
|
|
|
|
function c:setVolume(vol)
|
|
if self.audiosource then self.audiosource:setVolume(vol) end
|
|
end
|
|
|
|
function c:pause() c.video:pause() end
|
|
|
|
function c:stop()
|
|
c.playing = false
|
|
c.video:pause()
|
|
c.video:rewind()
|
|
end
|
|
|
|
function c:rewind() c.video:rewind() end
|
|
|
|
function c:seek(n) c.video:seek(n) end
|
|
|
|
function c:tell() return c.video:tell() end
|
|
|
|
c:newThread(function(self)
|
|
|
|
local testCompletion = function() -- More intensive test
|
|
if self.video:tell() == 0 then
|
|
self.OnVideoFinished:Fire(self)
|
|
return true
|
|
end
|
|
end
|
|
|
|
local isplaying = function() -- Less intensive test
|
|
return self.video:isPlaying()
|
|
end
|
|
|
|
while true do thread.chain(isplaying, testCompletion) end
|
|
|
|
end)
|
|
|
|
c.videoVisibility = 1
|
|
c.videoColor = color.white
|
|
|
|
return c
|
|
end
|
|
|
|
-- Draw Function
|
|
|
|
-- local label, image, text, button, box, video, animation (spritesheet)
|
|
local drawtypes = {
|
|
[0] = function(child, x, y, w, h) end,
|
|
[1] = function(child, x, y, w, h)
|
|
if child.image then
|
|
love.graphics.setColor(child.imageColor[1], child.imageColor[2],
|
|
child.imageColor[3], child.imageVisibility)
|
|
love.graphics.draw(child.image, child.quad, x, y, rad(child.rotation), w / child.imageWidth, h / child.imageHeigth)
|
|
end
|
|
end,
|
|
[2] = function(child, x, y, w, h)
|
|
love.graphics.setColor(child.textColor[1], child.textColor[2],
|
|
child.textColor[3], child.textVisibility)
|
|
love.graphics.setFont(child.font)
|
|
if child.align == gui.ALIGN_LEFT then
|
|
child.adjust = 0
|
|
elseif child.align == gui.ALIGN_CENTER then
|
|
local fw = child.font:getWidth(child.text)
|
|
child.adjust = (w - fw) / 2
|
|
elseif child.align == gui.ALIGN_RIGHT then
|
|
local fw = child.font:getWidth(child.text)
|
|
child.adjust = w - fw - 4
|
|
end
|
|
love.graphics.printf(child.text, child.adjust + x + child.textOffsetX,
|
|
y + child.textOffsetY, w, "left", child.rotation,
|
|
child.textScaleX, child.textScaleY, 0, 0,
|
|
child.textShearingFactorX,
|
|
child.textShearingFactorY)
|
|
end,
|
|
[4] = function(child, x, y, w, h)
|
|
if child.bar_show then
|
|
local lw = love.graphics.getLineWidth()
|
|
love.graphics.setLineWidth(1)
|
|
local font = child.font
|
|
local fh = font:getHeight()
|
|
local fw = font:getWidth(child.text:sub(1, child.cur_pos))
|
|
love.graphics.line(child.textOffsetX + child.adjust + x + fw, y + 4,
|
|
child.textOffsetX + child.adjust + x + fw,
|
|
y + fh - 2)
|
|
love.graphics.setLineWidth(lw)
|
|
end
|
|
if child:HasSelection() then
|
|
local blue = color.highlighter_blue
|
|
local start, stop = child.selection[1], child.selection[2]
|
|
if start > stop then start, stop = stop, start end
|
|
local x1, y1 = child.font:getWidth(child.text:sub(1, start - 1)), 0
|
|
local x2, y2 = child.font:getWidth(child.text:sub(1, stop)), h
|
|
love.graphics.setColor(blue[1], blue[2], blue[3], .5)
|
|
love.graphics.rectangle("fill", x + x1 + child.adjust, y + y1,
|
|
x2 - x1, y2 - y1)
|
|
end
|
|
end,
|
|
[8] = function(child, x, y, w, h)
|
|
if child.video and child.playing then
|
|
love.graphics.setColor(child.videoColor[1], child.videoColor[2],
|
|
child.videoColor[3], child.videoVisibility)
|
|
if w ~= child.imageWidth and h ~= child.imageHeigth then
|
|
love.graphics.draw(child.video, x, y, rad(child.rotation),
|
|
w / child.videoWidth, h / child.videoHeigth)
|
|
else
|
|
love.graphics.draw(child.video, child.quad, x, y,
|
|
rad(child.rotation), w / child.videoWidth,
|
|
h / child.videoHeigth)
|
|
end
|
|
end
|
|
end,
|
|
[16] = function(child, x, y, w, h)
|
|
--
|
|
end
|
|
}
|
|
|
|
local draw_handler = function(child)
|
|
local bg = child.color
|
|
local bbg = child.borderColor
|
|
local ctype = child.type
|
|
local vis = child.visibility
|
|
local x, y, w, h = child:getAbsolutes()
|
|
local roundness = child.roundness
|
|
local rx, ry, segments = child.__rx or 0, child.__ry or 0,
|
|
child.__segments or 0
|
|
child.x = x
|
|
child.y = y
|
|
child.w = w
|
|
child.h = h
|
|
|
|
if child.clipDescendants then
|
|
local children = child:getAllChildren()
|
|
for c = 1, #children do -- Tell the children to clip themselves
|
|
local clip = children[c].__variables.clip
|
|
clip[1] = true
|
|
clip[2] = x
|
|
clip[3] = y
|
|
clip[4] = w
|
|
clip[5] = h
|
|
end
|
|
end
|
|
|
|
if child.__variables.clip[1] then
|
|
local clip = child.__variables.clip
|
|
love.graphics.setScissor(clip[2], clip[3], clip[4], clip[5])
|
|
elseif type(roundness) == "string" then
|
|
love.graphics.setScissor(x - 1, y - 2, w + 2, h + 3)
|
|
end
|
|
|
|
-- Set color
|
|
love.graphics.setLineStyle("smooth")
|
|
love.graphics.setLineWidth(3)
|
|
love.graphics.setColor(bbg[1], bbg[2], bbg[3], vis)
|
|
love.graphics.rectangle("line", x, y, w, h, rx, ry, segments)
|
|
love.graphics.setColor(bg[1], bg[2], bg[3], vis)
|
|
love.graphics.rectangle("fill", x, y, w, h, rx, ry, segments)
|
|
if roundness == "top" then
|
|
love.graphics.rectangle("fill", x, y + ry / 2, w, h - ry / 2 + 1)
|
|
love.graphics.setLineStyle("rough")
|
|
|
|
love.graphics.setColor(bbg[1], bbg[2], bbg[3], 1)
|
|
love.graphics.setLineWidth(1)
|
|
love.graphics.line(x, y + ry, x, y + h + 1, x + 1 + w, y + h + 1,
|
|
x + 1 + w, y + ry)
|
|
love.graphics.line(x, y + h, x + 1 + w, y + h)
|
|
love.graphics.setScissor()
|
|
love.graphics.setColor(bbg[1], bbg[2], bbg[3], .6)
|
|
love.graphics.line(x - 1, y + ry / 2 + 2, x - 1, y + h + 2)
|
|
love.graphics.line(x + w + 2, y + ry / 2 + 2, x + w + 2, y + h + 2)
|
|
elseif roundness == "bottom" then
|
|
love.graphics.rectangle("fill", x, y, w, h - ry + 2)
|
|
love.graphics.setLineStyle("rough")
|
|
love.graphics.setColor(bbg[1], bbg[2], bbg[3], 1)
|
|
love.graphics.setLineWidth(2)
|
|
love.graphics.line(x - 1, y + ry + 1, x - 1, y - 1, x + w + 1, y - 1,
|
|
x + w + 1, y + ry + 1)
|
|
love.graphics.setScissor()
|
|
love.graphics.line(x - 1, y - 1, x + w + 1, y - 1)
|
|
|
|
love.graphics.setColor(bbg[1], bbg[2], bbg[3], .6)
|
|
love.graphics.setLineWidth(2)
|
|
love.graphics.line(x - 1, y + 2, x - 1, y + h - 4 - ry / 2)
|
|
love.graphics.line(x + w + 1, y + 2, x + w + 1, y + h - 4 - ry / 2)
|
|
end
|
|
|
|
-- Start object specific stuff
|
|
drawtypes[band(ctype, video)](child, x, y, w, h)
|
|
drawtypes[band(ctype, image)](child, x, y, w, h)
|
|
drawtypes[band(ctype, text)](child, x, y, w, h)
|
|
drawtypes[band(ctype, box)](child, x, y, w, h)
|
|
if child.post then child:post() end
|
|
if child.__variables.clip[1] then
|
|
love.graphics.setScissor() -- Remove the scissor
|
|
end
|
|
end
|
|
|
|
drawer:newLoop(function()
|
|
local children = gui:getAllChildren()
|
|
for i = 1, #children do
|
|
local child = children[i]
|
|
if child.effect then
|
|
child.effect(function() draw_handler(child) end)
|
|
else
|
|
draw_handler(child)
|
|
end
|
|
end
|
|
first_loop = true
|
|
end)
|
|
|
|
-- Drawing and Updating
|
|
gui.draw = drawer.run
|
|
gui.update = updater.run
|
|
|
|
-- Virtual gui
|
|
gui.virtual.type = frame
|
|
gui.virtual.children = {}
|
|
gui.virtual.dualDim = gui:newDualDim()
|
|
gui.virtual.x = 0
|
|
gui.virtual.y = 0
|
|
setmetatable(gui.virtual, gui)
|
|
|
|
local w, h = love.graphics.getDimensions()
|
|
gui.virtual.dualDim.offset.size.x = w
|
|
gui.virtual.dualDim.offset.size.y = h
|
|
gui.virtual.w = w
|
|
gui.virtual.h = h
|
|
|
|
-- Root gui
|
|
gui.type = frame
|
|
gui.children = {}
|
|
gui.dualDim = gui:newDualDim()
|
|
gui.x = 0
|
|
gui.y = 0
|
|
|
|
local w, h = love.graphics.getDimensions()
|
|
gui.dualDim.offset.size.x = w
|
|
gui.dualDim.offset.size.y = h
|
|
gui.w = w
|
|
gui.h = h
|
|
|
|
local g_width, g_height
|
|
local function GetSizeAdjustedToAspectRatio(dWidth, dHeight)
|
|
local isLandscape = g_width > g_height
|
|
|
|
local newHeight = 0
|
|
local newWidth = 0
|
|
|
|
if g_width / g_height > dWidth / dHeight then
|
|
newHeight = dWidth * g_height / g_width
|
|
newWidth = dWidth
|
|
else
|
|
newWidth = dHeight * g_width / g_height
|
|
newHeight = dHeight
|
|
end
|
|
|
|
return newWidth, newHeight, (dWidth-newWidth)/2, (dHeight-newHeight)/2
|
|
end
|
|
|
|
function gui:setAspectSize(w, h)
|
|
if w and h then
|
|
g_width, g_height = w, h
|
|
gui.aspect_ratio = true
|
|
else
|
|
gui.aspect_ratio = false
|
|
end
|
|
end
|
|
|
|
gui.Events.OnResized(function(w, h)
|
|
if gui.aspect_ratio then
|
|
local nw, nh, xt, yt = GetSizeAdjustedToAspectRatio(w, h)
|
|
print(nw, nh, xt, yt)
|
|
gui.x = xt
|
|
gui.y = yt
|
|
gui.dualDim.offset.size.x = nw
|
|
gui.dualDim.offset.size.y = nh
|
|
gui.w = nw
|
|
gui.h = nh
|
|
else
|
|
gui.dualDim.offset.size.x = w
|
|
gui.dualDim.offset.size.y = h
|
|
gui.w = w
|
|
gui.h = h
|
|
end
|
|
end)
|
|
|
|
return gui
|