diff --git a/assets/14018-3193093789.gif b/anime/assets/14018-3193093789.gif similarity index 100% rename from assets/14018-3193093789.gif rename to anime/assets/14018-3193093789.gif diff --git a/test.ogv b/anime/assets/bunny.ogv similarity index 100% rename from test.ogv rename to anime/assets/bunny.ogv diff --git a/anime/assets/zanarkand.mp3 b/anime/assets/zanarkand.mp3 new file mode 100644 index 0000000..c4bb2d1 Binary files /dev/null and b/anime/assets/zanarkand.mp3 differ diff --git a/anime/index.yml b/anime/index.yml index db1eb6f..1e36b94 100644 --- a/anime/index.yml +++ b/anime/index.yml @@ -9,7 +9,7 @@ settings: categories: # the name of each category should correspond to a yaml file with the same name - name: shounen # name must be alphanumeric, cannot start with a number or contain special characters "-" and "." are ok if they are not at the beginning displayName: "Shounen" # if blank will use name - image: "assets/14018-3193093789.gif" # if set will display image instead of name, categories are referenced by name which is required + image: "anime/assets/14018-3193093789.gif" # if set will display image instead of name, categories are referenced by name which is required - name: openings displayName: "Openings" # if blank will use name - name: sadness diff --git a/anime/shounen.yml b/anime/shounen.yml index 3af0c64..5869446 100644 --- a/anime/shounen.yml +++ b/anime/shounen.yml @@ -25,4 +25,11 @@ questions: # expects a list equal to the tier time-limit: 10 # If omitted there is no time limit answer: "Katsuhiro Otomo" template: "whatis" # template to use + display-answer: true # displays the correct answer before returning to the main screen + - title: "What is this?" + answer: "Bunny shit" + video: "anime/assets/bunny.ogv" + start: 4 + playFor: 3 + template: "whatvideo" # template to use display-answer: true # displays the correct answer before returning to the main screen \ No newline at end of file diff --git a/assets/speaker.png b/assets/speaker.png new file mode 100644 index 0000000..d9cd38d Binary files /dev/null and b/assets/speaker.png differ diff --git a/board.lua b/board.lua index 714b004..7688626 100644 --- a/board.lua +++ b/board.lua @@ -269,7 +269,9 @@ function LoadTemplate(name, path) error = multi.error, theme = theme, gui = gui, - timer = timer + timer = timer, + -- love stuff + newSource = love.audio.newSource } env._G = env diff --git a/gui/addons/extensions.lua b/gui/addons/extensions.lua index ca1883c..6540a9b 100644 --- a/gui/addons/extensions.lua +++ b/gui/addons/extensions.lua @@ -3,24 +3,34 @@ local theme = require("gui.core.theme") local color = require("gui.core.color") local multi, thread = require("multi"):init() local mediaProc = gui:newProcessor() +local miscProc = gui:newProcessor() local function noOf(sx,sy,sw,sh) return nil,nil,nil,nil,sx,sy,sw,sh end -function gui:newVideoPlayer(source, x, y, w, h, sx, sy, sw, sh) - local window = gui:newWindow(x, y, w, h, source, true, theme:new({ +function gui:newVideoPlayer(source, x, y, w, h, sx, sy, sw, sh, draggable, th) + local window = self:newWindow(x, y, w, h, sx, sy, sw, sh, "", draggable, th or theme:new({ primary = "#000000", primaryDark = "#10465c", primaryText = "#ffffff" })) local video = window:newVideo(source, 0, 0, 0, 0, 0, .05, 1, .75) - local play_pause = window:newImageButton("gui/assets/play.png",0,0,0,0,.45,.82,0,.175) - local seek = window:newFrame(0,0,0,0,0,.8,1,.015) - seek.color = color.new("#3c434c") - local seeker = seek:newFrame(0,0,0,0,0,.1,0,.8) - seeker.drawBorder = false - seeker.color = color.new("#0c278a") + local length = video:getDuration() + local play_pause = window:newImageButton("gui/assets/play.png",0,0,0,0,.45,.86,0,.14) + local seek = window:newProgressBar(0,0,0,0,0,.8,1,.05,length*100,0) + seek.OnProgressUpdated(function(self,_,value, drag) + if drag then + local status = video:isPlaying() + video:seek(value/100) + if status then + video:play() + else + video:stop() + end + end + end) + seek:isClickable(true) play_pause.square = "h" play_pause.isPaused = true play_pause:OnReleased(function(self) @@ -34,16 +44,31 @@ function gui:newVideoPlayer(source, x, y, w, h, sx, sy, sw, sh) self.isPaused = not self.isPaused end) - local length = video:getDuration() mediaProc:newThread(function() while true do - thread.yield() - seeker:setDualDim(nil,nil,nil,nil,nil,nil,video:tell()/length) + thread.hold(function() return video:isPlaying() end) + local p = video:tell() + seek:update(p*100) end end) - -- print() + function window:seek(...) + video.video:seek(...) + end + function window:play() + video:play() + end + + function window:stop() + video:stop() + end + + window.OnClose(function() + video:pause() + end) + + return window end function gui:newCheckbox(label, x, y, size, sx, sy, checked) @@ -117,21 +142,68 @@ end function gui:newProgressBar(x, y, w, h, sx, sy, sw, sh, count, value) local value = value or 0 + local count = count or 100 local progressbar = self:newFrame(x,y,w,h,sx,sy,sw,sh) - local fillframe = progressbar:newFrame(noOf(.025, .1, .95, .8)) + local fillframe = progressbar:newFrame(2,2,-4,-4,0,0,1,1) local fill = fillframe:newFrame(noOf(0, 0, 1, 1)) - + local percentDisplay = fillframe:newTextLabel("",noOf(0,0,1,1)) + percentDisplay.align = gui.ALIGN_CENTER + percentDisplay.textColor = color.new("#CC5500") fillframe.visibility = 0 progressbar.color = color.new("#000000") fill.color = color.new("#ffffff") progressbar.fillframe = fillframe progressbar.fill = fill + progressbar.display = percentDisplay + progressbar.OnProgressUpdated = multi:newConnection() + percentDisplay.visibility = 0 + local displayPercent = false - function progressbar:update(value) - if value > count then value = count end - if value < 0 then value = 0 end + function progressbar:showPercent(bool) + displayPercent = bool + if bool then + miscProc:newThread(function() + thread.skip(2) + _,_,_h = percentDisplay:getAbsolutes() + percentDisplay:setFont(math.floor(h/1.3)) + self:update() + end) + end + end + + function progressbar:isClickable(bool) + fillframe:respectHierarchy(not bool) + -- Makes the bar updatable by clicking on it + fillframe:enableDragging(bool and gui.MOUSE_PRIMARY) + end + + local calcFunc = function(self, dx, dy, x, y, istouch) + local sx, sy, sw, sh = self:getAbsolutes() + if x >= sx and x <= sx + sw then + progressbar:update((((x - sx)/sw) * count), true) + end + end + + fillframe:OnDragStart(calcFunc) + + fillframe.OnDragging(calcFunc) + + fillframe.OnPressed(function(self, x, y, dx, dy, istouch) + calcFunc(self, dx, dy, x, y, istouch) + end) + + function progressbar:update(v, drag) + v = v or value + if v > count then v = count end + if v < 0 then v = 0 end local percent = value/count fill:setDualDim(noOf(nil,nil,percent)) + if displayPercent then + percentDisplay.text = math.floor((percent*100)+.5).. "%" + percentDisplay:centerFont() + end + value = v + self.OnProgressUpdated:Fire(self, percent, value, drag) end function progressbar:add(n) @@ -151,17 +223,27 @@ function gui:newProgressBar(x, y, w, h, sx, sy, sw, sh, count, value) end function progressbar:max() - value = count - self:update(value) + self:update(count) end function progressbar:min() - value = 0 - self:update(value) + self:update(0) + end + + function progressbar:half() + self:update(math.floor(count/2)) + end + + function progressbar:getPercent() + return math.floor(((value/count)*100)+.5) + end + + function progressbar:getValue() + return value end progressbar:update(value) -- to change colors and modify main components - return progressbar, fill, fillframe + return progressbar, fill, percentDisplay, fillframe end \ No newline at end of file diff --git a/gui/addons/system.lua b/gui/addons/system.lua index 862920c..fbe77ee 100644 --- a/gui/addons/system.lua +++ b/gui/addons/system.lua @@ -312,7 +312,7 @@ end -- ── window constructor (unchanged from original) ────────────────────────────── local windowCount = 0 -function gui:newWindow(x, y, w, h, text, draggable, theme) +function gui:newWindow(x, y, w, h, sx, sy, sw, sh, text, draggable, theme) local process = gui:newProcessor(text or "window_"..windowCount) windowCount = windowCount + 1 local parent = self @@ -323,9 +323,9 @@ function gui:newWindow(x, y, w, h, text, draggable, theme) local sizenwse = love.mouse.getSystemCursor("sizenwse") local theme = theme or default_theme - local header = self:newFrame(x, y, w, 35) + local header = self:newFrame(x, y, w, 35, sx, sy, sw) header:setRoundness(10, 10, nil, "top") - local window = header:newFrame(0, 35, 0, h - 35, 0, 0, 1) + local window = header:newFrame(0, 35, 0, h, sx, sy, 1, sh) window.clipDescendants = true local left = window:newFrame(0, -4, 4, 0, 0, 0, 0, 1):tag("left") local right = window:newFrame(-4, -4, 4, 0, 1, 0, 0, 1):tag("right") @@ -384,8 +384,11 @@ function gui:newWindow(x, y, w, h, text, draggable, theme) local X = header:newTextButton("", -25, -25, 20, 20, 1, 1) X:setRoundness(10, 10) + X:respectHierarchy(false) X.align = gui.ALIGN_CENTER X.color = color.red + window.XButton = X + local darkenX = color.darken(color.red, .2) X.OnEnter(function(self) self.color = darkenX end) X.OnExit(function(self) self.color = color.red end) @@ -403,7 +406,10 @@ function gui:newWindow(x, y, w, h, text, draggable, theme) end) end - window.OnClose = function() return window end % X.OnPressed + window.OnClose = multi:newConnection() + X.OnPressed(function(self, ...) + window.OnClose:Fire(window, ...) + end) window.OnClose(function() header:setParent(gui.virtual) love.mouse.setCursor(pointer) @@ -758,7 +764,7 @@ function gui:showTaskManager() local WIN_W = TOTAL_W + 20 local WIN_H = 620 - taskManager = gui:newWindow(0, 0, WIN_W, WIN_H, "Task Manager", true, TM_THEME) + taskManager = gui:newWindow(0, 0, WIN_W, WIN_H, nil, nil, nil, nil, "Task Manager", true, TM_THEME) taskManager.clipDescendants = true -- ── tab bar ────────────────────────────────────────────────────────────── diff --git a/gui/core/transitions.lua b/gui/core/transitions.lua index 768ef0c..9e176aa 100644 --- a/gui/core/transitions.lua +++ b/gui/core/transitions.lua @@ -1,6 +1,6 @@ local gui = require("gui") local multi, thread = require("multi"):init() -local processor = gui:newProcessor("Transistion Processor") +local processor = gui:newProcessor("Transition Processor") local transition = {} local width, height, flags = love.window.getMode() @@ -13,23 +13,26 @@ end transition.__index = transition transition.__call = function(t, start, stop, time, ...) local args = {...} - return function(st, sp, ti) -- allow these values to be overridden - if not (st or start) or not (sp or stop) then return multi.error("start and stop must be supplied") end - if start == stop then + return function(st, sp, ti) + local s = st or start + local e = sp or stop + local dur = ti or time or 1 + if not s or not e then return multi.error("start and stop must be supplied") end + if s == e then local temp = { - OnStep = function() end, + OnStep = function() end, OnStop = multi:newConnection() } - proc:newTask(function() + processor:newTask(function() temp.OnStop:Fire() end) return temp end - local handle = t.func(t, start, stop, (ti or time) or 1, unpack(args)) + local handle = t.func(t, s, e, dur, unpack(args)) return { OnStep = handle.OnStatus, OnStop = handle.OnReturn + handle.OnError, - Kill = t.Kill + Kill = function() t:Kill() end } end end @@ -43,11 +46,11 @@ function transition:newTransition(func) c.OnStop = multi:newConnection() c.kill = false - function c:SetFPS(fps) - self.fps = fps + function c:SetFPS(f) + self.fps = f end - function c:GetFPS(fps) + function c:GetFPS() return self.fps end @@ -60,17 +63,29 @@ function transition:newTransition(func) 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 +transition.glide = transition:newTransition(function(t, start, stop, time) + local split = stop - start + local startTime = love.timer.getTime() + local endTime = startTime + time + local stepTime = 1 / t.fps + t.running = true - for i = 0, steps do - if not(t.kill) then - thread.sleep(piece) - thread.pushStatus(start + i*(split/steps),piece*i) + + while not t.kill do + thread.sleep(stepTime) + + local now = love.timer.getTime() + local elapsed = now - startTime + local clamped = math.min(elapsed, time) + local value = start + (clamped / time) * split + + thread.pushStatus(value, clamped) + + if now >= endTime then + break end end + t.running = false t.kill = false end) diff --git a/gui/init.lua b/gui/init.lua index 7339ecc..81ac2f8 100644 --- a/gui/init.lua +++ b/gui/init.lua @@ -136,6 +136,7 @@ updater:newTask(function() Hook("joystickadded", gui.Events.OnJoystickAdded.Fire) end) + -- Hotkeys local function noOf(sx,sy,sw,sh) @@ -485,13 +486,13 @@ function gui:canPress(mx, my) -- Get the intersection of the clip area and the s return not (mx > x + w or mx < x or my > y + h or my < y) end -function gui:isBeingCovered(mx, my) +function gui:isBeingCovered(mx, my, respect) + -- if not respect then return false end local children = gui:getAllChildren() for i = #children, 1, -1 do - if children[i] == self then + if children[i] == self or not respect then return false - elseif children[i]:canPress(mx, my) and not (children[i] == self) and - not (children[i].ignore) then + elseif children[i]:canPress(mx, my) and not (children[i] == self) and not (children[i].ignore) then return true end end @@ -590,7 +591,8 @@ function gui:getUniques(tab) color = self.color, borderColor = self.borderColor, drawBorder = self.drawborder, - rotation = self.rotation + rotation = self.rotation, + shader = self.shader } if tab then for i, v in pairs(tab) do base[i] = tab[i] end end @@ -619,6 +621,20 @@ local function testVisual(c, x, y, button, istouch, presses) return not(c:hasTag("visual") or c:parentHasTag("visual")) end +local extensions = {} + +function gui.registerExtension(template) + table.insert(extensions, template) +end + +function gui:extend(c) + for i,v in pairs(extensions) do + for key, value in pairs(v) do + c[key] = value + end + end +end + -- Base Library function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) local c = {} @@ -633,7 +649,7 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) local function testHierarchy(c, x, y, button, istouch, presses) if hierarchy then - return not (global_drag or c:isBeingCovered(x, y)) + return not (global_drag or c:isBeingCovered(x, y, true)) end return true end @@ -723,6 +739,12 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) end) local _mouseRelRef = gui.Events.OnMouseReleased(function(x, y, button, istouch, presses) + pressed = false -- we need to handle dragging stopped even if an element is not active + if dragging and button == dragbutton then + dragging = false + global_drag = false + c.OnDragEnd:Fire(c, dx, dy, x, y, istouch, presses) + end if not c:isActive() then return end if c:canPress(x, y) then c.OnReleased:Fire(c, x, y, button, istouch, presses) @@ -731,12 +753,6 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) 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) local _mousePressRef = gui.Events.OnMousePressed(function(x, y, button, istouch, presses) @@ -751,7 +767,7 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) object_focus = c end - if draggable and button == dragbutton and not c:isBeingCovered(x, y) and + if draggable and button == dragbutton and not c:isBeingCovered(x, y, hierarchy) and not global_drag then dragging = true global_drag = true @@ -803,7 +819,9 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) return self end - function c:respectHierarchy(bool) hierarchy = bool end + function c:respectHierarchy(bool) + hierarchy = bool + end local function centerthread() if centerX or centerY then @@ -914,6 +932,41 @@ function gui:newBase(typ, x, y, w, h, sx, sy, sw, sh, virtual) if typ == frame then gui.Events.OnCreated:Fire(c) -- Trigger frame types instantly end + -- shader stuff + + function c:setShader(shader) + if type(shader) == "string" then + self.shader = love.graphics.newShader(shader) + else + self.shader = shader -- already a compiled love Shader object + end + return self + end + + function c:clearShader() + self.shader = nil + end + + function c:setShaderUniform(name, ...) + if not self.shader then return end + if self.shader:hasUniform(name) then + self.shader:send(name, ...) + end + end + + function c:enableShaderTime() + self.__shaderTime = 0 + mainupdater.OnLoop(function(_, _, dt) + if not self.shader then return end + self.__shaderTime = self.__shaderTime + dt + if self.shader:hasUniform("time") then + self.shader:send("time", self.__shaderTime) + end + end) + end + + gui:extend(c, typ) + return c end @@ -1122,6 +1175,7 @@ function gui:newTextBase(typ, txt, x, y, w, h, sx, sy, sw, sh) textColor = c.textColor }) end + return c end @@ -1879,6 +1933,8 @@ function gui:newVideo(source, x, y, w, h, sx, sy, sw, sh) function c:tell() return c.video:tell() end + function c:isPlaying() return c.video:isPlaying() end + updater:newThread("Video Handler",function() local testCompletion = function() -- More intensive test @@ -2048,7 +2104,13 @@ local draw_handler = function(child, no_draw, dt) end end - if child.shader and band(ctype, image) == 2 then + if child.shader then + if child.shader:hasUniform("size") then + child.shader:send("size", {w, h}) + end + if child.shader:hasUniform("position") then + child.shader:send("position", {x, y}) + end love.graphics.setShader(child.shader) end @@ -2066,16 +2128,15 @@ local draw_handler = function(child, no_draw, dt) love.graphics.setLineStyle("smooth") love.graphics.setLineWidth(1) + if drawB then love.graphics.setColor(bbg[1], bbg[2], bbg[3], vis) draw_factor(child,"line", x, y, w, h, rx, ry, nil, nil, segments) - end - - if drawB then if roundness == "top" then + love.graphics.setColor(bg[1], bg[2], bg[3], vis) draw_factor(child,"fill", x, y + ry / 2, w, h - ry / 2 + 1) --love.graphics.rectangle("fill", x, y + ry / 2, w, h - ry / 2 + 1) - love.graphics.setLineStyle("rough") + love.graphics.setLineStyle("smooth") 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, @@ -2087,9 +2148,10 @@ local draw_handler = function(child, no_draw, dt) 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.setColor(bg[1], bg[2], bg[3], vis) draw_factor(child,"fill", x, y, w, h - ry + 2) --love.graphics.rectangle("fill", x, y, w, h - ry + 2) - love.graphics.setLineStyle("rough") + love.graphics.setLineStyle("smooth") 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, @@ -2123,14 +2185,114 @@ end gui.draw_handler = draw_handler +local function has_blur_ancestor(child) + local parent = child.parent + while parent and parent ~= gui and parent ~= gui.virtual do + if parent.__blur then return parent end + parent = parent.parent + end + return nil +end + +local function blur_draw(child, dt) + local x, y, w, h = child:getAbsolutes() + child.x = x + child.y = y + child.w = w + child.h = h + + local b = child.__blur + local pw, ph = math.max(1, math.ceil(w)), math.max(1, math.ceil(h)) + + if not b.canvas1 + or b.canvas1:getWidth() ~= pw + or b.canvas1:getHeight() ~= ph then + b.canvas1 = love.graphics.newCanvas(pw, ph) + b.canvas2 = love.graphics.newCanvas(pw, ph) + end + + local prevCanvas = love.graphics.getCanvas() + local prevShader = love.graphics.getShader() + local pr, pg, pb, pa = love.graphics.getColor() + + -- ------------------------------------------------------- + -- Pass 1: draw the object AND all its descendants onto canvas1 + -- ------------------------------------------------------- + love.graphics.setCanvas(b.canvas1) + love.graphics.setShader() + love.graphics.clear(0, 0, 0, 0) + love.graphics.setScissor() + + -- Shift everything into canvas space by offsetting by -x, -y + love.graphics.push() + love.graphics.translate(-x, -y) + + -- Draw the parent object itself + draw_handler(child, nil, dt) + + -- Draw all descendants in order + local descendants = child:getAllChildren() + for i = 1, #descendants do + local desc = descendants[i] + -- Recalculate absolutes so positions are correct + local dx, dy, dw, dh = desc:getAbsolutes() + desc.x = dx + desc.y = dy + desc.w = dw + desc.h = dh + draw_handler(desc, nil, dt) + end + + love.graphics.pop() + + -- ------------------------------------------------------- + -- Pass 2: horizontal blur canvas1 → canvas2 + -- ------------------------------------------------------- + b.shader_h:send("size", {pw, ph}) + b.shader_h:send("radius", b.radius) + + love.graphics.setCanvas(b.canvas2) + love.graphics.clear(0, 0, 0, 0) + love.graphics.setShader(b.shader_h) + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw(b.canvas1, 0, 0) + + -- ------------------------------------------------------- + -- Pass 3: vertical blur canvas2 → screen + -- ------------------------------------------------------- + b.shader_v:send("size", {pw, ph}) + b.shader_v:send("radius", b.radius) + + love.graphics.setCanvas(prevCanvas) + love.graphics.setShader(b.shader_v) + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw(b.canvas2, x, y) + + -- Restore state + love.graphics.setShader(prevShader) + love.graphics.setColor(pr, pg, pb, pa) + love.graphics.setScissor() +end + local draw_loop = drawer:newLoop(function(self, dt) local children = gui:getAllChildren() for i = 1, #children do local child = children[i] - if child.effect then + -- Skip if this child belongs to a blur parent + -- (blur_draw handles drawing it during the canvas capture pass) + if has_blur_ancestor(child) then + -- update x/y/w/h so layout still works, but don't draw + local x, y, w, h = child:getAbsolutes() + child.x = x + child.y = y + child.w = w + child.h = h + elseif child.__blur then + blur_draw(child, dt) + elseif child.effect then child.effect(function() draw_handler(child, nil, dt) end) else - draw_handler(child,nil,dt) + draw_handler(child, nil, dt) end end first_loop = true @@ -2256,4 +2418,10 @@ gui.Events.OnResized(function(w, h) end end) +-- load shaders +files = love.filesystem.getDirectoryItems("gui/shaders") +for i,v in pairs(files) do + require("gui.shaders."..v:sub(1,-5)).init(gui) +end + return gui diff --git a/gui/shaders/blur.lua b/gui/shaders/blur.lua new file mode 100644 index 0000000..98471e7 --- /dev/null +++ b/gui/shaders/blur.lua @@ -0,0 +1,70 @@ +local blur_h = love.graphics.newShader([[ + extern vec2 size; + extern float radius; + + vec4 effect(vec4 color, Image tex, vec2 tc, vec2 sc) { + vec2 step = vec2(1.0 / size.x, 0.0); + vec4 result = vec4(0.0); + float total = 0.0; + float sigma = radius * 0.5; + float r = floor(radius); + float i = -r; + + while (i <= r) { + float weight = exp(-0.5 * (i * i) / (sigma * sigma)); + result += Texel(tex, tc + step * i) * weight; + total += weight; + i = i + 1.0; + } + + return (result / total) * color; + } +]]) + +local blur_v = love.graphics.newShader([[ + extern vec2 size; + extern float radius; + + vec4 effect(vec4 color, Image tex, vec2 tc, vec2 sc) { + vec2 step = vec2(0.0, 1.0 / size.y); + vec4 result = vec4(0.0); + float total = 0.0; + float sigma = radius * 0.5; + float r = floor(radius); + float i = -r; + + while (i <= r) { + float weight = exp(-0.5 * (i * i) / (sigma * sigma)); + result += Texel(tex, tc + step * i) * weight; + total += weight; + i = i + 1.0; + } + + return (result / total) * color; + } +]]) + +local c = {} + +function c:setBlur(radius) + radius = radius or 8 + c.__blur = { + radius = radius, + canvas1 = nil, -- populated on first draw + canvas2 = nil, + shader_h = blur_h, + shader_v = blur_v, + } +end + +function c:clearBlur() + c.__blur = nil +end + +function c:setBlurRadius(radius) + if c.__blur then + c.__blur.radius = radius + end +end + +return {init = function(gui) gui.registerExtension(c) end} \ No newline at end of file diff --git a/main.lua b/main.lua index 8f602ac..efc2508 100644 --- a/main.lua +++ b/main.lua @@ -4,6 +4,52 @@ local playerList = {} local playerStaticList = {} local scoreboard = {} +multi, thread = require("multi"):init({priority=true}) +multi.setClock(require("socket").gettime) -- When on linux os.clock doesn't reture actual seconds the program has elapsed for +GLOBAL, THREAD = require("multi.integration.loveManager"):init() + +gui = require("gui") +color = require("gui.core.color") +theme = require("gui.core.theme") +utils = require("utils") +board = require("board") +yaml = require("yaml") +loader = require("loader") +require("gui.addons") + +scoreUpdater = gui:getProcessor():newProcessor("score-updater") +scoreUpdater.Start() + +gui.registerExtension({ + sayHello = function(self) + print("Extended Hello World") + end, + sayBye = function(self) + print("Extended Bye World") + end, +}) + +function love.load() + gui:cacheImage({"assets/checked.png","assets/unchecked.png","assets/speaker.png"}) + gui:setAspectSize(1920, 1080) + gui.aspect_ratio = true + + local bg = gui:newFrame() + bg:fullFrame() + bg.color = color.new("#242f9b") + + -- bg:setBlur(10) + bg:OnUpdate(function(self, dt) + if self.__blur then + -- pulse the blur between 2 and 12 + local t = love.timer.getTime() + self:setBlurRadius(7 + 5 * math.sin(t * 2)) + end + end) + + StartGame(bg) +end + function GetActivePlayer() if not activePlayer then return end return activePlayer.link @@ -35,25 +81,6 @@ function love.filedropped(file) print("Load file? " .. file:getFilename()) end - -function init() - multi, thread = require("multi"):init({priority=true}) - multi.setClock(require("socket").gettime) -- When on linux os.clock doesn't reture actual seconds the program has elapsed for - GLOBAL, THREAD = require("multi.integration.loveManager"):init() - - gui = require("gui") - color = require("gui.core.color") - theme = require("gui.core.theme") - utils = require("utils") - board = require("board") - yaml = require("yaml") - loader = require("loader") - require("gui.addons") - - scoreUpdater = gui:getProcessor():newProcessor("score-updater") - scoreUpdater.Start() -end - function ScoreBoard(frame, x, y, w, h, sx, sy, sw, sh) -- Colors local C_BG_PANEL = color.new("#1a1a2e") @@ -313,56 +340,15 @@ function ScoreBoard(frame, x, y, w, h, sx, sy, sw, sh) return scoreboard end +function LandingPage(bg) - --- local webp = require("webp") -init() - -local function noOf(sx,sy,sw,sh) - return nil,nil,nil,nil,sx,sy,sw,sh end -function love.load() - gui:cacheImage({"assets/checked.png","assets/unchecked.png"}) - gui:setAspectSize(1920, 1080) - gui.aspect_ratio = true - -- local ext = require("gui.addons.extensions") - local bg = gui:newFrame() - bg:fullFrame() - bg.color = color.new("#242f9b") - - -- pb = bg:newProgressBar(0, 0, 200, 40, 0, 0, 0, 0, 200, 0) - - -- thread:newThread(function() - -- for i=1,200 do - -- thread.sleep(.01) - -- pb:add(1) - -- end - -- end) - - -- local group = bg:newRadioGroup({ - -- padding = 5, - -- "Option A", - -- "Option B", - -- "Option C" - -- },0,0,0,0, 80) - - -- group.OnSelectionChanged(function(group, selection) - -- print(selection:getLabel()) - -- end) - - - - +function StartGame(bg) local qframe = bg:newFrame(0, 0, 0, 0, .2, .05, .75, .9) qframe.color = color.new("#060ee9") local scoreboard = ScoreBoard(bg, 0, 0, 0, 0, .015, .05, .170, .9) - board.buildBoard(qframe, "anime") - - -- gui:newVideoPlayer("test.ogv",0,0,428,240) - -- local img = webp.load("test.webp") - -- gui:newImageLabel(img,0,0,735,1041) end function love.update(dt) diff --git a/multi b/multi index 4a52cd5..c864133 160000 --- a/multi +++ b/multi @@ -1 +1 @@ -Subproject commit 4a52cd5e14abfc409bc82f6427d0cf79f8d2a894 +Subproject commit c86413351c8e2fdd3b595d3df1ba0b9ac7d1f9b1 diff --git a/templates/whatsound.lua b/templates/whatsound.lua new file mode 100644 index 0000000..56d42c9 --- /dev/null +++ b/templates/whatsound.lua @@ -0,0 +1,69 @@ +--[[ Constants + * (all lua builtins that don't allow io/executing code) + color (interface) + gui (interface) + multi (interface bound to a processor) no thread module + + callback true/false correct/wrong +]] +local label +local imageHolder + +local function index(window, q, callback) + if not q.sound or q.sound == "" then + error("Missing 'sound' field for question!") + callback() + return + end + frame = window:newFrame(0,0,0,-200,0,.2,1,.8) + frame.visibility = 0 + label = window:newTextLabel(" " ..q.title.. " ",0,0,0,0,0,0,1,.2) + label.align = ALIGN_CENTER + label.textColor = color.white + label.color = color.new("#060ce9") + imageHolder = frame:newImageButton("assets/speaker.png",0,0,0,0,0,0,.3,.4) + imageHolder:setAspectSize(imageHolder.imageWidth,imageHolder.imageHeight) + imageHolder:centerX(true) + imageHolder:centerY(true) + local sound = newSource(q.sound,"stream") + imageHolder:OnReleased(function() + if tonumber(q.start) then + sound:seek(q.start) + end + + sound:play() + + timer(tonumber(q.playFor) or 10, function() + sound:stop() + sound:seek(0) + end) + end) + local correct = window:newTextButton("Correct",0,-200,0,100,0,1,.5) + correct.color = color.new("#52b11b") + local wrong = window:newTextButton("Wrong",0,-200,0,100,.5,1,.5) + wrong.color = color.new("#bd2626") + local skip = window:newTextButton("Skip",0,-100,0,100,.25,1,.5) + skip.color = color.new("#5d5d5d") + window.apply({ + fitFont={}, + align=window.ALIGN_CENTER, + OnReleased=function(self) + sound:stop() + if self.text == "Skip" then + callback() + return + end + callback(self.text == "Correct") + end, + },correct,wrong,skip) +end + +local function update(dt) -- time in seconds that has passed since + label:fitFont() + label:centerFont() +end + +return { + index = index, + update = update +} diff --git a/templates/whatvideo.lua b/templates/whatvideo.lua new file mode 100644 index 0000000..1c353a7 --- /dev/null +++ b/templates/whatvideo.lua @@ -0,0 +1,62 @@ +--[[ Constants + * (all lua builtins that don't allow io/executing code) + color (interface) + gui (interface) + multi (interface bound to a processor) no thread module + + callback true/false correct/wrong +]] +local label +local video + +local function index(window, q, callback) + if not q.video or q.video == "" then + error("Missing 'video' field for question!") + callback() + return + end + frame = window:newFrame(0,0,0,-200,0,.2,1,.8) + frame.visibility = 0 + label = window:newTextLabel(" " ..q.title.. " ",0,0,0,0,0,0,1,.2) + label.align = ALIGN_CENTER + label.textColor = color.white + label.color = color.new("#060ce9") + local holder = frame:newFrame(-300, -200, 600, 400, .5, .5) + holder:centerX(true) + holder:centerY(true) + holder.visibility = 0 + video = holder:newVideoPlayer(q.video, 0, 0, 600, 400, false) + video:setAspectSize(video.imageWidth,video.imageHeight) + video:OnReleased(function() + video:play() + end) + video.XButton.visible = false + local correct = window:newTextButton("Correct",0,-200,0,100,0,1,.5) + correct.color = color.new("#52b11b") + local wrong = window:newTextButton("Wrong",0,-200,0,100,.5,1,.5) + wrong.color = color.new("#bd2626") + local skip = window:newTextButton("Skip",0,-100,0,100,.25,1,.5) + skip.color = color.new("#5d5d5d") + window.apply({ + fitFont={}, + align=window.ALIGN_CENTER, + OnReleased=function(self) + video:stop() + if self.text == "Skip" then + callback() + return + end + callback(self.text == "Correct") + end, + },correct,wrong,skip) +end + +local function update(dt) -- time in seconds that has passed since + label:fitFont() + label:centerFont() +end + +return { + index = index, + update = update +}