diff --git a/addons/init.lua b/addons/init.lua new file mode 100644 index 0000000..e69de29 diff --git a/addons/system.lua b/addons/system.lua index d7227aa..d69b531 100644 --- a/addons/system.lua +++ b/addons/system.lua @@ -1,4 +1,5 @@ local gui = require("gui") +local theme = require("gui.core.theme") local default_theme = theme:new("64342e", "b2989e", "909b9a") function gui:newWindow(x, y, w, h, text, draggable) diff --git a/core/canvas.lua b/core/canvas.lua new file mode 100644 index 0000000..6af5c95 --- /dev/null +++ b/core/canvas.lua @@ -0,0 +1,26 @@ +local gui = require("gui") + +function newCanvas(domain) + local c + if domain == "visual" then + c = gui:newVisualFrame() + else + c = gui:newVirtualFrame() + end + c:fullFrame() + function c:swap(c1, c2) + local temp = c1.children + c1.children = c2.children + c2.children = temp + for i,v in pairs(c1.children) do + v.parent = c1 + end + for i,v in pairs(c2.children) do + v.parent = c2 + end + end + + return c +end + +return newCanvas \ No newline at end of file diff --git a/core/color.lua b/core/color.lua index 5dceb57..555a1a2 100644 --- a/core/color.lua +++ b/core/color.lua @@ -47,7 +47,7 @@ function color.hsl(h, s, l) elseif h < 4 then r,b,g = 0,x,c elseif h < 5 then r,b,g = x,0,c else r,b,g = c,0,x - end return (r+m)*255, (g+m)*255, (b+m)*255 + end return (r+m)*255, (g+m)*255, (b+m)*255, 255 end min = math.min @@ -60,7 +60,7 @@ function color.hsv(h, s, v) local r = min (max (3*abs (((h )/180)%2-1)-1, 0), 1) local g = min (max (3*abs (((h -120)/180)%2-1)-1, 0), 1) local b = min (max (3*abs (((h +120)/180)%2-1)-1, 0), 1) - return k1 + k2 * r, k1 + k2 * g, k1 + k2 * b + return k1 + k2 * r, k1 + k2 * g, k1 + k2 * b, 1 end function color.isLight(r, g, b) @@ -102,9 +102,10 @@ function color.new(r, g, b, fmt) end local temp if fmt then - temp = {r,b,g} + temp = {r, b, g, 1} else temp = {love.math.colorFromBytes(r, g, b)} + temp[4] = 1 end setmetatable(temp, mt) return temp diff --git a/core/simulate.lua b/core/simulate.lua new file mode 100644 index 0000000..0b887d3 --- /dev/null +++ b/core/simulate.lua @@ -0,0 +1,90 @@ +local gui = require("gui") +local multi, thread = require("multi"):init() +local transition = require("gui.elements.transitions") + +-- Triggers press then release +local function getPosition(obj, x, y) + if not x or y then + local cx, cy, w, h = obj:getAbsolutes() + return cx + w/2, cy + h/2 + else + return x, y + end +end + +proc = gui:getProcessor() + +local simulate = {} + +function simulate:Press(button, x, y, istouch) + if self then + x, y = getPosition(self, x, y) + elseif x == nil or y == nil then + x, y = love.mouse.getPosition() + end + love.mouse.setPosition(x, y) + gui.Events.OnMousePressed:Fire(x, y, button or gui.MOUSE_PRIMARY, istouch or false) +end + +function simulate:Release(button, x, y, istouch) + if self then + x, y = getPosition(self, x, y) + elseif x == nil or y == nil then + x, y = love.mouse.getPosition() + end + love.mouse.setPosition(x, y) + gui.Events.OnMouseReleased:Fire(x, y, button or gui.MOUSE_PRIMARY, istouch or false) +end + +simulate.Click = proc:newFunction(function(self, button, x, y, istouch) + if self then + x, y = getPosition(self, x, y) + elseif x == nil or y == nil then + x, y = love.mouse.getPosition() + end + love.mouse.setPosition(x, y) + gui.Events.OnMousePressed:Fire(x, y, button or gui.MOUSE_PRIMARY, istouch or false) + thread.skip(1) + gui.Events.OnMouseReleased:Fire(x, y, button or gui.MOUSE_PRIMARY, istouch or false) +end, true) + +simulate.Move = proc:newFunction(function(self, dx, dy, x, y, istouch) + local dx, dy = dx or 0, dy or 0 + + if self then + x, y = getPosition(self, x, y) + elseif x == nil or y == nil then + x, y = love.mouse.getPosition() + end + + if dx == 0 and dy == 0 then + _x, _y = love.mouse.getPosition() + if x == _x and y == _y then + return + end + local dx, dy = 0, 0 + dx = x - _x + dy = y - _y + return simulate.Move(nil, dx, dy) + end + gui.Events.OnMouseMoved:Fire(x, y, 0, 0, istouch or false) + thread.skip(1) + local gx = transition.glide(0, dx, .25) + local gy = transition.glide(0, dy, .25) + local xx = gx() + xx.OnStep(function(p) + _x, _y = love.mouse.getPosition() + love.mouse.setPosition(x + p, _y) + end) + local yy = gy() + yy.OnStep(function(p) + _x, _y = love.mouse.getPosition() + love.mouse.setPosition(_x, y + p) + end) + if not(dx==0 and dy == 0) then + thread.hold(xx.OnStop * yy.OnStop) + end + gui.Events.OnMouseMoved:Fire(x + dx, y + dy, 0, 0, istouch or false) +end, true) + +return simulate \ No newline at end of file diff --git a/core/theme.lua b/core/theme.lua index c13a0d6..bcb3aba 100644 --- a/core/theme.lua +++ b/core/theme.lua @@ -47,7 +47,6 @@ function theme:random(seed, lightness, rand) end end - t.seed = seed return t end diff --git a/elements/init.lua b/elements/init.lua new file mode 100644 index 0000000..626d5a7 --- /dev/null +++ b/elements/init.lua @@ -0,0 +1,104 @@ +local gui = require("gui") +local color = require("gui.core.color") +local theme = require("gui.core.theme") +local transition = require("gui.elements.transitions") +local processor = gui:newProcessor("menu") + +function gui:newMenu(title, sx, position, trans, callback, t,t2) + if not title then multi.error("Argument 1 string('title') is required") end + if not sx then multi.error("Argument 2 number('sx') is required") end + if callback then if not type(callback) == "function" then multi.error("Argument 5 function('callback(menu(self),align[left,center,right],transition_position)') must be a function") end end + local t = t or .35 + local t2 = t2 or .25 + local position = position or gui.ALIGN_LEFT + local trans = trans or transition.glide + + local menu, to, tc, open + if callback then + menu = self:newFrame(0, 0, 0, 0, .5 -sx/2, 1, sx, 1) + to = trans(1, 0, t) + tc = trans(0, 1, t) + else + if position == gui.ALIGN_LEFT then + menu = self:newFrame(0, 0, 0, 0, -sx, 0, sx, 1) + to = trans(-sx, 0, t2) + tc = trans(0, -sx, t2) + elseif position == gui.ALIGN_CENTER then + menu = self:newFrame(0, 0, 0, 0, .5 -sx/2, 1, sx, 1) + to = trans(1, 0, t) + tc = trans(0, 1, t) + elseif position == gui.ALIGN_RIGHT then + menu = self:newFrame(0, 0, 0, 0, 1, 0, sx, 1) + to = trans(1, 1 - sx, t2) + tc = trans(1 - sx, 1, t2) + end + end + + function menu:isOpen() + return open + end + + function menu:Open(show) + if show then + if not menu.lock then + menu.visible = true + menu.lock = true + local t = to() + t.OnStop(function() + open = true + menu.lock = false + end) + t.OnStep(function(p) + if callback then + callback(menu,position,p,"open") + for i,v in pairs(menu:getAllChildren()) do + callback(v,position,p,"open") + end + else + if position == gui.ALIGN_CENTER then + menu:setDualDim(nil, nil, nil, nil, nil, p) + else + menu:setDualDim(nil, nil, nil, nil, p) + end + end + end) + end + else + if not menu.lock then + menu.lock = true + local t = tc() + t.OnStop(function() + open = false + menu.lock = false + menu.visible = false + end) + t.OnStep(function(p) + if callback then + callback(menu,position,p,"open") + for i,v in pairs(menu:getAllChildren()) do + callback(v,position,p,"open") + end + else + if position == gui.ALIGN_CENTER then + menu:setDualDim(nil, nil, nil, nil, nil, p) + else + menu:setDualDim(nil, nil, nil, nil, p) + end + end + end) + end + end + end + + menu.OnCreate = processor:newConnection() + local items = {} + function menu:addItem(text) + local item = menu:newTextButton(text,0,100*#items,0,100,0,0,1) + items[#items+1] = item + item:fitFont() + item.align = gui.ALIGN_CENTER + self.OnCreate:Fire(self,item,items) + end + + return menu +end diff --git a/elements/transitions.lua b/elements/transitions.lua new file mode 100644 index 0000000..8605335 --- /dev/null +++ b/elements/transitions.lua @@ -0,0 +1,65 @@ +local gui = require("gui") +local multi, thread = require("multi"):init() +local processor = gui:newProcessor("Transistion Processor") +local transition = {} + +local width, height, flags = love.window.getMode() +local fps = 60 + +if flags.refreshrate > 0 then + fps = flags.refreshrate +end + +transition.__index = transition +transition.__call = function(t, start, stop, time, ...) + local args = {...} + return function() + if not start or not stop then return multi.error("start and stop must be supplied") end + if start == stop then + local temp = { + OnStep = function() end, + OnStop = multi:newConnection() + } + proc:newTask(function() + temp.OnStop:Fire() + end) + return temp + end + local handle = t.func(t, start, stop, time or 1, unpack(args)) + return { + OnStep = handle.OnStatus, + OnStop = handle.OnReturn + handle.OnError + } + end +end + +function transition:newTransition(func) + local c = {} + setmetatable(c, self) + + c.fps = fps + c.func = processor:newFunction(func) + c.OnStop = multi:newConnection() + + function c:SetFPS(fps) + self.fps = fps + end + + function c:GetFPS(fps) + return self.fps + end + + return c +end + +transition.glide = transition:newTransition(function(t, start, stop, time, ...) + local steps = t.fps*time + local piece = time/steps + local split = stop-start + for i = 0, steps do + thread.sleep(piece) + thread.pushStatus(start + i*(split/steps)) + end +end) + +return transition \ No newline at end of file diff --git a/init.lua b/init.lua index a8fe4a2..b4e9dcc 100644 --- a/init.lua +++ b/init.lua @@ -4,7 +4,9 @@ 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") @@ -16,6 +18,8 @@ local global_drag local object_focus = gui local first_loop = false +gui.Version = "0.1.0" + -- Types gui.TYPE_FRAME = frame gui.TYPE_IMAGE = image @@ -25,7 +29,7 @@ gui.TYPE_VIDEO = video gui.TYPE_BUTTON = button gui.TYPE_ANIM = anim --- +-- Variables gui.__index = gui gui.MOUSE_PRIMARY = 1 @@ -58,6 +62,16 @@ gui.Events.OnTouchMoved = multi:newConnection() gui.Events.OnTouchPressed = multi:newConnection() gui.Events.OnTouchReleased = multi:newConnection() +-- Joysticks and gamepads +gui.Events.OnGamepadPressed = multi:newConnection() +gui.Events.OnGamepadReleased = multi:newConnection() +gui.Events.OnGamepadAxis = multi:newConnection() +gui.Events.OnJoystickAdded = multi:newConnection() +gui.Events.OnJoystickHat = multi:newConnection() +gui.Events.OnJoystickPressed = multi:newConnection() +gui.Events.OnJoystickReleased = multi:newConnection() +gui.Events.OnJoystickRemoved = multi:newConnection() + -- Non Love Events gui.Events.OnThemeChanged = multi:newConnection() @@ -82,27 +96,44 @@ local function Hook(funcname, func) end end --- This will run the hooks after everything has loaded +-- Incase you define one of these methods, we need to process this after that updater:newTask(function() + -- System 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) + + -- Mouse + Hook("mousefocus", gui.Events.OnMouseFocus.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) + + -- Keyboard + Hook("textedited", gui.Events.OnTextEdited.Fire) + Hook("textinput", gui.Events.OnTextInputed.Fire) + + -- Touchscreen Hook("touchmoved", gui.Events.OnTouchMoved.Fire) Hook("touchpressed", gui.Events.OnTouchPressed.Fire) Hook("touchreleased", gui.Events.OnTouchReleased.Fire) + + -- Joystick/Gamepad + Hook("gamepadpressed", gui.Events.OnGamepadPressed.Fire) + Hook("gamepadaxis", gui.Events.OnGamepadAxis.Fire) + Hook("gamepadreleased", gui.Events.OnGamepadReleased.Fire) + Hook("joystickpressed", gui.Events.OnJoystickPressed.Fire) + Hook("joystickreleased", gui.Events.OnJoystickReleased.Fire) + Hook("joystickhat", gui.Events.OnJoystickHat.Fire) + Hook("joystickremoved", gui.Events.OnJoystickRemoved.Fire) + Hook("joystickadded", gui.Events.OnJoystickAdded.Fire) end) -- Hotkeys @@ -179,6 +210,10 @@ gui.HotKeys.OnRedo = gui:setHotKey({"lctrl", "y"}) + -- Utils +gui.newFunction = updater.newFunction + +function gui:getProcessor() return updater end + function gui:getObjectFocus() return object_focus end function gui:hasType(t) return band(self.type, t) == t end @@ -186,6 +221,7 @@ 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 + self.OnPositionChanged:Fire(self, x, y) end function gui:moveInBounds(dx, dy) @@ -237,14 +273,37 @@ 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) + +function gui:offsetToScale() + local children = self:getAllChildren() + for i = 1, #children do + local child = children[i] + local x, y = child:getAbsolutes() + local _, __, w, h = child.parent:getAbsolutes() + print(x / w, y / h) + local _, __, w, h = child:getAbsolutes() + local _, __, pw, ph = child.parent:getAbsolutes() + print(w / pw, h / ph) + end +end + +function gui:getAbsolutes(transform) -- returns x, y, w, h + if transform then + return transform((self.parent.w * self.dualDim.scale.pos.x) + + self.dualDim.offset.pos.x + self.parent.x), + transform((self.parent.h * self.dualDim.scale.pos.y) + + self.dualDim.offset.pos.y + self.parent.y), transform((self.parent.w * + self.dualDim.scale.size.x) + self.dualDim.offset.size.x), + transform((self.parent.h * self.dualDim.scale.size.y) + + self.dualDim.offset.size.y) + else + 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 end function gui:getAllChildren(vis) @@ -263,31 +322,38 @@ function gui:getAllChildren(vis) end function gui:newThread(func) - return updater:newThread("ThreadHandler<" .. self.type .. ">", func, self, - thread) + 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 - } - } + --[[ + dd.offset.pos = {x = x or 0, y = y or 0} + self.dualDim.offset.size = {x = w or 0, y = h or 0} + self.dualDim.scale.pos = {x = sx or 0, y = sy or 0} + self.dualDim.scale.size = {x = sw or 0, y = sh or 0} + ]] + self.dualDim = self:newDualDim( + x or self.dualDim.offset.pos.x, + y or self.dualDim.offset.pos.y, + w or self.dualDim.offset.size.x, + h or self.dualDim.offset.size.y, + sx or self.dualDim.scale.pos.x, + sy or self.dualDim.scale.pos.y, + sw or self.dualDim.scale.size.x, + sh or self.dualDim.scale.size.y) + self.OnSizeChanged:Fire(self, x, y, w, h, sx, sy, sw, sh) +end + +function gui:rawSetDualDim(x, y, w, h, sx, sy, sw, sh) + self.dualDim = self:newDualDim( + x or self.dualDim.offset.pos.x, + y or self.dualDim.offset.pos.y, + w or self.dualDim.offset.size.x, + h or self.dualDim.offset.size.y, + sx or self.dualDim.scale.pos.x, + sy or self.dualDim.scale.pos.y, + sw or self.dualDim.scale.size.x, + sh or self.dualDim.scale.size.y) end local image_cache = {} @@ -332,6 +398,11 @@ end local mainupdater = updater:newLoop().OnLoop +function gui:OnUpdate(func) -- Not crazy about this approach, will probably rework this + if type(self) == "function" then func = self end + mainupdater(function() func(self) end) +end + 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 @@ -385,8 +456,7 @@ function gui:clone(opt) 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 @@ -398,9 +468,9 @@ function gui:clone(opt) 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()) + temp = gui:newImageButton(u.Do[2], self:getDualDim()) elseif self.type == image then - temp = gui:newImageLabel(u.DO[2], self:getDualDim()) + temp = gui:newImageLabel(u.Do[2], self:getDualDim()) else -- We are dealing with a complex object temp = processDo(u) end @@ -413,7 +483,7 @@ function gui:clone(opt) if opt.connections then conn = true for i, v in pairs(self) do - if v.Type == "connector" then + if type(v) == "table" and 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 @@ -437,6 +507,11 @@ function gui:isActive() return self.active and not (self:isDescendantOf(gui.virtual)) end +function gui:isOnScreen() + + return +end + -- Base get uniques function gui:getUniques(tab) local base = { @@ -445,6 +520,7 @@ function gui:getUniques(tab) visibility = self.visibility, color = self.color, borderColor = self.borderColor, + drawBorder = self.drawborder, rotation = self.rotation } @@ -452,9 +528,32 @@ function gui:getUniques(tab) return base end +function gui:setTag(tag) + self.tags[tag] = true +end + +function gui:hasTag(tag) + return self.tags[tag] +end + +function gui:parentHasTag(tag) + local parent = self.parent + while parent do + if parent.tags and parent.tags[tag] then return true end + parent = parent.parent + if parent == gui.virtual or parent == gui then return false end + end + return false +end + +local function testVisual(c, x, y, button, istouch, presses) + return not(c:hasTag("visual") or c:parentHasTag("visual")) +end + -- Base Library function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) local c = {} + c.tags = {} local buildBackBetter local centerX = false local centerY = false @@ -470,8 +569,19 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) return true end - setmetatable(c, gui) + local function defaultCheck(...) + if not c:isActive() then return false end + local x, y = love.mouse.getPosition() + if c:canPress(x, y) then + return c, ... + end + return false + end + + setmetatable(c, self) + c.__index = self.__index c.__variables = {clip = {false, 0, 0, 0, 0}} + c.focus = false c.active = true c.type = typ c.dualDim = self:newDualDim(x, y, w, h, sx, sy, sw, sh) @@ -480,22 +590,38 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) c.visibility = 1 c.color = {.6, .6, .6} c.borderColor = color.black + c.drawBorder = true 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.OnLoad = multi:newConnection() - c.OnDragStart = multi:newConnection() - c.OnDragging = multi:newConnection() - c.OnDragEnd = multi:newConnection() + c.OnPressed = testVisual .. (testHierarchy .. multi:newConnection()) + c.OnPressedOuter = testVisual .. multi:newConnection() + c.OnReleased = testVisual .. (testHierarchy .. multi:newConnection()) + c.OnReleasedOuter = testVisual .. multi:newConnection() + c.OnReleasedOther = testVisual .. multi:newConnection() - c.OnEnter = testHierarchy .. multi:newConnection() - c.OnExit = multi:newConnection() + c.OnDragStart = testVisual .. multi:newConnection() + c.OnDragging = testVisual .. multi:newConnection() + c.OnDragEnd = testVisual .. multi:newConnection() - c.OnMoved = testHierarchy .. multi:newConnection() + c.OnEnter = (testHierarchy .. multi:newConnection()) + c.OnExit = testVisual .. multi:newConnection() + + c.OnMoved = testVisual .. (testHierarchy .. multi:newConnection()) + c.OnWheelMoved = testVisual .. (defaultCheck / gui.Events.OnWheelMoved) + + c.OnSizeChanged = testVisual .. multi:newConnection() + c.OnPositionChanged = testVisual .. multi:newConnection() + + c.OnLeftStickUp = testVisual .. multi:newConnection() + c.OnLeftStickDown = testVisual .. multi:newConnection() + c.OnLeftStickLeft = testVisual .. multi:newConnection() + c.OnLeftStickRight = testVisual .. multi:newConnection() + c.OnRightStickUp = testVisual .. multi:newConnection() + c.OnRightStickDown = testVisual .. multi:newConnection() + c.OnRightStickLeft = testVisual .. multi:newConnection() + c.OnRightStickRight = testVisual .. multi:newConnection() local dragging = false local entered = false @@ -522,7 +648,7 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) 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) + c.OnReleased:Fire(c, x, y, button, istouch, presses) elseif pressed then c.OnReleasedOuter:Fire(c, x, y, button, istouch, presses) else @@ -559,6 +685,11 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) end end) + function c:isOffScreen() + local x, y, w, h = self:getAbsolutes() + return y + h < 0 or y > gui.h or x + w < 0 or x > gui.w + 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 @@ -571,27 +702,16 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) 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 + if centerX or centerY then + local x, y, w, h = c:getAbsolutes() + if centerX then + c:rawSetDualDim(-w / 2, nil, nil, nil, .5) end - end) + if centerY then + c:rawSetDualDim(nil, -h / 2, nil, nil, nil, .5) + end + end end function c:enableDragging(but) @@ -607,14 +727,22 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) centerX = bool if centering then return end centering = true - centerthread() + self.OnSizeChanged(centerthread) + self.OnPositionChanged(centerthread) + updater:newLoop(centerthread) end function c:centerY(bool) centerY = bool if centering then return end centering = true - centerthread() + self.OnSizeChanged(centerthread) + self.OnPositionChanged(centerthread) + updater:newLoop(centerthread) + end + + function c:fullFrame() + self:setDualDim(0,0,0,0,0,0,1,1) end -- Add to the parents children table @@ -625,6 +753,7 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) c.parent = self table.insert(self.children, c) end + local a = 0 return c end @@ -653,6 +782,13 @@ 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 + +function gui:newVisualFrame(x, y, w, h, sx, sy, sw, sh) + local visual = self:newBase(frame, x, y, w, h, sx, sy, sw, sh) + visual:setTag("visual") + return visual +end + local testIMG -- Texts function gui:newTextBase(typ, txt, x, y, w, h, sx, sy, sw, sh) @@ -669,7 +805,7 @@ function gui:newTextBase(typ, txt, x, y, w, h, sx, sy, sw, sh) c.textVisibility = 1 c.font = love.graphics.newFont(12) c.textColor = color.black - c.OnFontUpdated = multi:newConnection() + c.OnFontUpdated = testVisual .. multi:newConnection() function c:calculateFontOffset(font, adjust) local adjust = adjust or 20 @@ -716,7 +852,9 @@ function gui:newTextBase(typ, txt, x, y, w, h, sx, sy, sw, sh) end function c:setFont(font, size) - if type(font) == "string" then + if type(font) == "number" then + self.font = love.graphics.newFont(font) + elseif type(font) == "string" then self.fontFile = font self.font = love.graphics.newFont(font, size) else @@ -725,10 +863,8 @@ function gui:newTextBase(typ, txt, x, y, w, h, sx, sy, sw, sh) self.OnFontUpdated:Fire(self) end - function c:fitFont(n, max) - local max = max or math.huge + function c:fitFont(min_size, max_size) local font - local isdefault = false if self.fontFile then if self.fontFile:match("ttf") then font = function(n) @@ -740,27 +876,67 @@ function gui:newTextBase(typ, txt, x, y, w, h, sx, sy, sw, sh) 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) + local text = self.text + local x, y, max_width, max_height = self:getAbsolutes() + local min_size = min_size or 1 + local max_size = max_size or 100 -- You can adjust the maximum font size as needed + local tolerance = 0.1 + local f + while max_size - min_size > tolerance do + local size = (min_size + max_size) / 2 + + f = font(size) + local text_width = f:getWidth(text) + local text_height = f:getHeight() + + if text_width > max_width or text_height > max_height then + max_size = size + else + min_size = size + end 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)) + self:setFont(f) + return min_size 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) @@ -842,7 +1018,7 @@ function gui:newTextBox(txt, x, y, w, h, sx, sy, sw, sh) c:respectHierarchy(true) c.doSelection = false - c.OnReturn = multi:newConnection() + c.OnReturn = testVisual .. multi:newConnection() c.cur_pos = 0 c.selection = {0, 0} @@ -1027,12 +1203,12 @@ 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])) + _G.THREAD.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) +gui.cacheImage = updater: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) @@ -1048,10 +1224,48 @@ gui.cacheImage = thread:newFunction(function(self, path_or_paths) end end) +function gui:applyGradient(direction, ...) + local colors = {...} + if direction == "horizontal" then + direction = true + elseif direction == "vertical" then + direction = false + else + error("Invalid direction '" .. tostring(direction) .. "' for gradient. Horizontal or vertical expected.") + end + local result = love.image.newImageData(direction and 1 or #colors, direction and #colors or 1) + for i, color in ipairs(colors) do + local x, y + if direction then + x, y = 0, i - 1 + else + x, y = i - 1, 0 + end + result:setPixel(x, y, color[1], color[2], color[3], color[4] or 255) + end + + local img = love.graphics.newImage(result) + img:setFilter('linear', 'linear') + local x, y, w, h = self:getAbsolutes() + self.imageColor = color.white + self.imageVisibility = 1 + self.image = img + self.image:setWrap("repeat", "repeat") + self.imageHeight = img:getHeight() + self.imageWidth = img:getWidth() + self.quad = love.graphics.newQuad(0, 0, self.imageWidth, self.imageHeight, self.imageWidth, self.imageHeight) + + if not (band(self.type, image) == image) then + self.type = self.type + image + 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 + c.scaleX = 1 + c.scaleY = 1 local IMAGE function c:getUniques() @@ -1061,45 +1275,55 @@ function gui:newImageBase(typ, x, y, w, h, sx, sy, sw, sh) }) end + function c:flip(vert) + if vert then + c.scaleY = c.scaleY * -1 + else + c.scaleX = c.scaleX * -1 + end + end + + function c:getSource() + return 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 + img = love.image.newImageData(i) + 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 i and x then + c.imageHeight = h + c.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 + if type(i) == "string" then + image_cache[i] = img + i = image_cache[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) + c.image = i + c.image:setWrap("repeat", "repeat") + c.imageColor = color.white + c.quad = love.graphics.newQuad(x, y, w, h, c.image:getWidth(), c.image:getHeight()) + c.imageVisibility = 1 + + return + end + + if type(i) == "userdata" and i:type() == "Image" then + img = i + end + + local x, y, w, h = c:getAbsolutes() + c.imageColor = color.white + c.imageVisibility = 1 + c.image = img + c.image:setWrap("repeat", "repeat") + c.imageHeight = img:getHeight() + c.imageWidth = img:getWidth() + c.quad = love.graphics.newQuad(0, 0, c.imageWidth, c.imageHeight, c.imageWidth, c.imageHeight) end return c end @@ -1126,7 +1350,7 @@ 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) + local c = self:newImageBase(video, x, y, w, h, sx, sy, sw, sh) c.OnVideoFinished = multi:newConnection() c.playing = false @@ -1200,9 +1424,24 @@ 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) + if child.scaleX < 0 or child.scaleY < 0 then + local sx, sy = child.scaleX, child.scaleY + local adjustX, adjustY = child.scaleX * w, child.scaleY * h + love.graphics.setColor(child.imageColor[1], child.imageColor[2], + child.imageColor[3], child.imageVisibility) + if sx < 0 and sy < 0 then + love.graphics.draw(child.image, child.quad, x - adjustX, y - adjustY, rad(child.rotation), (w / child.imageWidth) * child.scaleX, (h / child.imageHeight) * child.scaleY) + elseif sx < 0 then + love.graphics.draw(child.image, child.quad, x - adjustX, y, rad(child.rotation), (w / child.imageWidth) * child.scaleX, h / child.imageHeight) + else + love.graphics.draw(child.image, child.quad, x, y - adjustY, rad(child.rotation), w / child.imageWidth, (h / child.imageHeight) * child.scaleY) + end + else + 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.imageHeight) + end end end, [2] = function(child, x, y, w, h) @@ -1251,7 +1490,7 @@ local drawtypes = { 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 + if w ~= child.imageWidth and h ~= child.imageHeight then love.graphics.draw(child.video, x, y, rad(child.rotation), w / child.videoWidth, h / child.videoHeigth) else @@ -1266,7 +1505,7 @@ local drawtypes = { end } -local draw_handler = function(child) +local draw_handler = function(child, no_draw) local bg = child.color local bbg = child.borderColor local ctype = child.type @@ -1280,6 +1519,8 @@ local draw_handler = function(child) child.w = w child.h = h + if no_draw then return end + if child.clipDescendants then local children = child:getAllChildren() for c = 1, #children do -- Tell the children to clip themselves @@ -1292,47 +1533,56 @@ local draw_handler = function(child) end end + if child.shader and band(ctype, image) == 2 then + love.graphics.setShader(child.shader) + 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 - + local drawB = child.drawBorder -- 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) + if drawB then + love.graphics.setColor(bbg[1], bbg[2], bbg[3], vis) + love.graphics.rectangle("line", x, y, w, h, rx, ry, segments) + end 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") + + if drawB then + 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], 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) + 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 end -- Start object specific stuff @@ -1340,12 +1590,20 @@ local draw_handler = function(child) 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 + + if child.shader then + love.graphics.setShader() + end end +gui.draw_handler = draw_handler + drawer:newLoop(function() local children = gui:getAllChildren() for i = 1, #children do @@ -1359,9 +1617,39 @@ drawer:newLoop(function() first_loop = true end) +drawer:newThread(function() + while true do + thread.sleep(.01) + local children = gui.virtual:getAllChildren() + for i = 1, #children do + local child = children[i] + if child.effect then + child.effect(function() draw_handler(child, true) end) + else + draw_handler(child, true) + end + end + first_loop = true + end +end) + +local processors = { + updater.run +} + -- Drawing and Updating gui.draw = drawer.run -gui.update = updater.run +gui.update = function() + for i = 1, #processors do + processors[i]() + end +end + +function gui:newProcessor(name) + local proc = multi:newProcessor(name or "UnNamedProcess_"..multi.randomString(8), true) + table.insert(processors, proc.run) + return proc +end -- Virtual gui gui.virtual.type = frame @@ -1376,8 +1664,10 @@ gui.virtual.dualDim.offset.size.x = w gui.virtual.dualDim.offset.size.y = h gui.virtual.w = w gui.virtual.h = h +gui.virtual.parent = gui.virtual -- Root gui +gui.parent = gui gui.type = frame gui.children = {} gui.dualDim = gui:newDualDim() @@ -1390,48 +1680,58 @@ 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 +function gui:GetSizeAdjustedToAspectRatio(dWidth, dHeight) local newHeight = 0 local newWidth = 0 - if g_width / g_height > dWidth / dHeight then - newHeight = dWidth * g_height / g_width + if self.g_width / self.g_height > dWidth / dHeight then + newHeight = dWidth * self.g_height / self.g_width newWidth = dWidth else - newWidth = dHeight * g_width / g_height + newWidth = dHeight * self.g_width / self.g_height newHeight = dHeight end return newWidth, newHeight, (dWidth-newWidth)/2, (dHeight-newHeight)/2 end +--gui.GetSizeAdjustedToAspectRatio = GetSizeAdjustedToAspectRatio + function gui:setAspectSize(w, h) if w and h then - g_width, g_height = w, h - gui.aspect_ratio = true + self.g_width, self.g_height = w, h else - gui.aspect_ratio = false + self.g_width, self.g_height = 0, 0 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) + local nw, nh, xt, yt = gui:GetSizeAdjustedToAspectRatio(w, h) gui.x = xt gui.y = yt gui.dualDim.offset.size.x = nw gui.dualDim.offset.size.y = nh gui.w = nw gui.h = nh + + gui.virtual.x = xt + gui.virtual.y = yt + gui.virtual.dualDim.offset.size.x = nw + gui.virtual.dualDim.offset.size.y = nh + gui.virtual.w = nw + gui.virtual.h = nh else gui.dualDim.offset.size.x = w gui.dualDim.offset.size.y = h gui.w = w gui.h = h + + gui.virtual.dualDim.offset.size.x = w + gui.virtual.dualDim.offset.size.y = h + gui.virtual.w = w + gui.virtual.h = h end end)