Gspöt - retained GUI lib

Showcase your libraries, tools and other projects that help your fellow love users.
fsc1
Prole
Posts: 3
Joined: Wed Jun 29, 2016 12:53 pm

Re: Gspöt - retained GUI lib

Post by fsc1 » Mon Mar 12, 2018 5:53 am

Hey guys, I have been trying to create a little IRC client with Love2D, LuaIRC (https://github.com/JakobOvrum/LuaIRC) and Gspot, but before I go any further, I need some advice, or some clear direction, maybe I am even going off on the wrong path here, but my goal is to have every line input on IRC on its own press-able event, so I can tap it on my tablet, and be able to do something with it (double tap and it'll automatically quote the person I want to respond to, etc).

Can someone give me some bit of guidance on this idea?

Code: Select all

gui = require('Gspot')
local irc = require "irc"
local sleep = require "socket".sleep
local s = irc.new{nick = "test"}
msgs = ""
nickname = ""

s:hook("OnChat", function(user, channel, message)
print(("[%s] %s: %s"):format(channel, user.nick, message)) --debug
msgs = message
nickname = user.nick
print(msgs)
end)

function love.load()
  scrollgroup = gui:scrollgroup(nil, {0, gui.style.unit, 460, 200},group)
	s:connect("localhost")
  s:join("#test")
end

love.update = function(dt)
  if msgs then scrollgroup:addchild(gui:text(msgs, {w = 400}), 'grid')
    print(msgs) --debug
    msgs=nil
  end
  gui:update(dt)  
  s:think()
end

love.draw = function()  
  --love.graphics.setColor(0, 255, 0, 255)
  --love.graphics.print(("%s: %s"):format(nickname, msgs))
  gui:draw()

end

love.keypressed = function(key, code, isrepeat)
end

love.textinput = function(key)
end

love.mousepressed = function(x, y, button)
	gui:mousepress(x, y, button) -- pretty sure you want to register mouse events
end
love.mousereleased = function(x, y, button)
	gui:mouserelease(x, y, button)
end
love.wheelmoved = function(x, y)
	gui:mousewheel(x, y)
end

love.quit = function()  
  s:disconnect("bye")
end

User avatar
pgimeno
Party member
Posts: 1895
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Gspöt - retained GUI lib

Post by pgimeno » Mon Mar 12, 2018 12:22 pm

The demo already does most of what you need. By removing the unnecessary stuff and with very few extra mods, I put this together:

Code: Select all

-- Original author: https://github.com/trubblegum
-- This is a modified version of https://github.com/trubblegum/Gspot/blob/cf0a49d7d2073686d7ddb32a4fa04e90593d36c4/main.lua
-- The original program did not include a copyright notice.
-- Modifications © Copyright 2015 Pedro Gimeno Fortea.
--
-- This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
-- Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
-- 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
-- 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
-- 3. This notice may not be removed or altered from any source distribution.

if love._version_major == 0 and love._version_minor < 9 then
	error("This library needs love2d 0.9.0 or above")
end

gui = require('Gspot') -- import the library
--mainmenu = gui() -- create a gui instance. don't have to do this, but you may want a gui for each gamestate so they can talk to each other, and you won't have to recontsruct the gui every time you enter a state

love.load = function()
	
	-- another group, with various behaviours
	group2 = gui:group('Group 2', {gui.style.unit, 128, 256, 256})
	group2.drag = true
	-- scrollgroup's children, excepting its scrollbar, will scroll
	scrollgroup = gui:scrollgroup(nil, {0, gui.style.unit, 256, 256}, group2) -- scrollgroup will create its own scrollbar
	scrollgroup.scrollh.tip = 'Scroll (mouse or wheel)' -- scrollgroup.scrollh is the horizontal scrollbar
	scrollgroup.scrollh.style.hs = scrollgroup.style.unit*2
	scrollgroup.scrollv.tip = scrollgroup.scrollh.tip -- scrollgroup.scrollv is the vertical scrollbar
	--scrollgroup.scroller:setshape('circle') -- to set a round handle
	scrollgroup.scrollv.style.hs = "auto"
	
	-- additional scroll controls
	button = gui:button('up', {group2.pos.w, 0}, group2) -- a small button attached to the scrollgroup's group, because all of a scrollgroup's children scroll
	button.click = function(this)
		local scroll = scrollgroup.scrollv
		scroll.values.current = math.max(scroll.values.min, scroll.values.current - scroll.values.step) -- decrement scrollgroup.scrollv.values.current by scrollgroup.scrollv.values.step, and the slider will go up a notch
	end
	button = gui:button('dn', {group2.pos.w, group2.pos.h + gui.style.unit}, group2)
	button.click = function(this)
		local scroll = scrollgroup.scrollv
		scroll.values.current = math.min(scroll.values.max, scroll.values.current + scroll.values.step) -- this one increment's the scrollbar's values.current, moving the slider down a notch
	end
	
	local function addHistory(nick, text)
		local nickwidget = gui:text(nick, {w=32})
		nickwidget.click = function(this) gui:feedback(this.label) end
		local textwidget = gui:text(text, {w=128})
		textwidget.click = function(this) gui:feedback(this.label) end
		local box = gui:hidden()
		box:addchild(nickwidget, 'horizontal')
		box:addchild(textwidget, 'horizontal')
		box.pos.h = math.max(nickwidget.pos.h, textwidget.pos.h)
		scrollgroup:addchild(box, 'vertical')
		scrollgroup.scrollv.values.current = scrollgroup.scrollv.values.max
	end

	-- text input
	input = gui:input('Chat', {64, love.graphics.getHeight() - 32, 256, gui.style.unit})
	input.keyrepeat = true -- this is the default anyway
	input.done = function(this) -- Gspot calls element:done() when you hit enter while element has focus. override this behaviour with element.done = false
		addHistory('Me', this.value)
		this.value = ''
		this.Gspot:unfocus()
	end
	button = gui:button('Speak', {input.pos.w + gui.style.unit, 0, 64, gui.style.unit}, input) -- attach a button
	button.click = function(this)
		this.parent:done()
	end
end

love.update = function(dt)
	gui:update(dt)
end

love.draw = function()
	gui:draw()
end

love.keypressed = function(key, code, isrepeat)
	if gui.focus then
		gui:keypress(key) -- only sending input to the gui if we're not using it for something else
	elseif key == 'return'then -- binding enter key to input focus
		input:focus()
	end
end

love.textinput = function(key)
	if gui.focus then
		gui:textinput(key) -- only sending input to the gui if we're not using it for something else
	end
end

-- deal with 0.10 mouse API changes
love.mousepressed = function(x, y, button)
	gui:mousepress(x, y, button) -- pretty sure you want to register mouse events
end
love.mousereleased = function(x, y, button)
	gui:mouserelease(x, y, button)
end
love.wheelmoved = function(x, y)
	gui:mousewheel(x, y)
end
I'm a bit puzzled myself at the need of a hidden element. I initially used a group element, but the children were partially hidden behind it rather than being inside it.

(Edited to remove a leftover function call that was causing crashes)

fsc1
Prole
Posts: 3
Joined: Wed Jun 29, 2016 12:53 pm

Re: Gspöt - retained GUI lib

Post by fsc1 » Tue Mar 13, 2018 12:11 am

pgimeno, thanks for that addHistory function, I would have never guessed to use the hidden element to get the children to fit. I tried all night trying to get a box group working as a child to the scrollgroup. Not sure how you figured that out! But thanks.

User avatar
pgimeno
Party member
Posts: 1895
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Gspöt - retained GUI lib

Post by pgimeno » Tue Mar 13, 2018 1:57 am

You're welcome!

Well, I just remembered this: https://github.com/pgimeno/Gspot/issues ... -264350329

Short version: drawing order depends on creation order, therefore creating the group first works.

There are plans to change that among many other things: https://github.com/pgimeno/Gspot/issues/6

There's still some issue with label widgets, because in my machine at least, when I write 'y', the bottom pixel row of the 'y' disappears under the next box when I enter a new line, making it look like a 'v' meaning the height is miscalculated.

User avatar
pgimeno
Party member
Posts: 1895
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Gspöt - retained GUI lib

Post by pgimeno » Sat Apr 07, 2018 5:22 pm

Adaptation to LÖVE 11.0 is finished and pending in a pull request: https://github.com/pgimeno/Gspot/pull/9

I'd appreciate feedback on the approach taken before I merge it. Lacking any feedback, I will merge it in 3 days.

Edit: Pushed that and https://github.com/pgimeno/Gspot/pull/8 to fix the text element miscalculation.

User avatar
Karasuro
Prole
Posts: 16
Joined: Wed Aug 21, 2013 5:32 am

Re: Gspöt - retained GUI lib

Post by Karasuro » Sat Feb 23, 2019 11:38 pm

I'm trying to render 2 groups where the first is attached to the camera view, and the second is attached to what's being drawn within the camera. The elements are drawing correctly but the click functions are not registering.

Code: Select all

stages = {}

local worldMap = {}
local camera = {}
local guiStagesMenu = {}
local guiVolcanoIcon = {}

local function hideGUI()
    guiStagesMenu:hide()
    guiVolcanoIcon:hide()
end

local function showGUI()
    guiStagesMenu:show()
    guiVolcanoIcon:show()
end

function stages:init()

    local btnBackImg = lg.newImage('lib/assets/menus/btn_back.png')

    guiStagesMenu = gui:group('', {5, 5, btnBackImg:getWidth(), btnBackImg:getHeight()})
    guiStagesMenu.style = {
        unit = 20,
        font = lg.newFont(20),
        fg = {1, 1, 1, 1},
        bg = {1, 1, 1, 0},
        labelfg = {1, 1, 1, 1},
        default = {1, 1, 1, 0},
        hilite = {1, 1, 1, 0},
        focus = {1, 1, 1, 0},
        hs = 'auto'
    }

    local btnBack = gui:imgbutton('', {0, 0, btnBackImg:getWidth(), btnBackImg:getHeight()}, guiStagesMenu, btnBackImg)
    btnBack.click = function()
        playSound('btnTone')
        hideGUI()
        Gamestate.pop()
    end

    local btnStageIcon = lg.newImage('lib/assets/menus/btn_stage_icon.png')

    guiVolcanoIcon = gui:group('', {340, 510, btnStageIcon:getWidth(), btnStageIcon:getHeight()})
    guiStagesMenu.style = {
        unit = 20,
        font = lg.newFont(20),
        fg = {1, 1, 1, 1},
        bg = {1, 1, 1, 0},
        labelfg = {1, 1, 1, 1},
        default = {1, 1, 1, 0},
        hilite = {1, 1, 1, 0},
        focus = {1, 1, 1, 0},
        hs = 'auto'
    }

    local btnVolcanoStage = gui:imgbutton('', {0, 0, btnStageIcon:getWidth(), btnStageIcon:getHeight()}, guiVolcanoIcon, btnStageIcon)
    btnVolcanoStage.click = function()
        playSound('btnTone')
        --hideGUI()
        --Gamestate.push(s01volcano)
    end

    worldMap.x = 0
    worldMap.y = 0
    worldMap.w = 2520
    worldMap.h = 1440
    worldMap.image = lg.newImage('lib/assets/menus/stage_menu_bg.png')

    camera.x = worldMap.w/2
    camera.y = worldMap.h/2
    camera.w = gWIDTH
    camera.h = gHEIGHT
    camera.scale = 0.3 -- minimum 0.3
    camera.rotation = 0
    camera.view = Camera(camera.x, camera.y, camera.scale, camera.rotation)
    camera.origin = {
        x = 0,
        y = 0
    }
    camera.bounds = {
        x1 = worldMap.x + camera.w/2,
        y1 = worldMap.y + camera.h/2,
        x2 = worldMap.w - camera.w/2,
        y2 = worldMap.h - camera.h/2
    }
end

local function introAnimation()
    Timer.script(function()
        introSequence = true
        camera.view:lookAt(worldMap.w/2, worldMap.h/2)
        camera.view:zoomTo(0.3)
        Timer.tween(3, camera.view, {scale = 1}, 'in-out-quad')
        Timer.tween(3, camera.view, {x = 340, y = 510}, 'in-out-quad')
    end)
end

function stages:enter()
    cameraDragging = false
    camera.x = worldMap.w/2
    camera.y = worldMap.h/2
    introAnimation()
    Timer.after(3, function()
        introSequence = false
        showGUI()
    end)
end

function stages:update(dt)
    if cameraDragging and not introSequence then
        local dx = prevCameraX + (mClickX - mGetX)
        local dy = prevCameraY + (mClickY - mGetY)
        camera.x = math.max(camera.bounds.x1, math.min(dx, camera.bounds.x2))
        camera.y = math.max(camera.bounds.y1, math.min(dy, camera.bounds.y2))
        camera.view:lookAt(camera.x, camera.y)
    end

    guiStagesMenu.pos.x = ((camera.view.x+5) - (camera.w/2))
    guiStagesMenu.pos.y = ((camera.view.y+5) - (camera.h/2))

    Timer.update(dt)
    gui:update(dt)
end

function stages:draw()
    camera.view:attach()

    lg.setColor(1, 1, 1, 1)
    lg.draw(worldMap.image, worldMap.x, worldMap.y)

    if not introSequence then
        gui:draw()
    end

    camera.view:detach()

    if gDEBUG then
        local mx, my = camera.view:mousePosition()
        lg.print(mx..'|'..my, mGetX+10, mGetY)
    end
end

function stages:mousepressed(x, y, button)
    if not introSequence then
        if button == 1
        and not mCollision(guiStagesMenu.pos)
        then
            cameraDragging = true
            mClickX , mClickY = x, y
            prevCameraX = camera.view.x
            prevCameraY = camera.view.y
        end
    end
    gui:mousepress(x, y, button)
end

function stages:mousereleased(x, y, button)
    if not introSequence then
        if button == 1 then
            cameraDragging = false
        end
    end
    gui:mouserelease(x, y, button)
end

User avatar
pgimeno
Party member
Posts: 1895
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Gspöt - retained GUI lib

Post by pgimeno » Sun Feb 24, 2019 1:10 am

Karasuro wrote:
Sat Feb 23, 2019 11:38 pm
I'm trying to render 2 groups where the first is attached to the camera view, and the second is attached to what's being drawn within the camera. The elements are drawing correctly but the click functions are not registering.
You need to transform the click positions from screen to world coordinates before passing them to Gspöt. You don't mention which camera library you're using, so I can't check it to tell you whether it provides a function to do that out of the box.

Also, are you calling the mouse events, in the first place? The snippet you've posted is too limited to tell.

User avatar
Karasuro
Prole
Posts: 16
Joined: Wed Aug 21, 2013 5:32 am

Re: Gspöt - retained GUI lib

Post by Karasuro » Sun Feb 24, 2019 1:50 am

pgimeno wrote:
Sun Feb 24, 2019 1:10 am
You need to transform the click positions from screen to world coordinates before passing them to Gspöt. You don't mention which camera library you're using, so I can't check it to tell you whether it provides a function to do that out of the box.

Also, are you calling the mouse events, in the first place? The snippet you've posted is too limited to tell.
I'm using HUMP for camera. It does have the conversion added using camera:worldCoords(x, y) and camera:cameraCoords(x, y)

User avatar
pgimeno
Party member
Posts: 1895
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Gspöt - retained GUI lib

Post by pgimeno » Sun Feb 24, 2019 2:12 am

Karasuro wrote:
Sun Feb 24, 2019 1:50 am
I'm using HUMP for camera. It does have the conversion added using camera:worldCoords(x, y) and camera:cameraCoords(x, y)
Good. Then you need something along these lines:

Code: Select all

function love.mousepressed(x, y, button)
  fixedGui:mousepress(x, y, button)
  local wx, wy = camera:worldCoords(x, y)
  cameraGui:mousepress(wx, wy, button)
end
and similarly for mousereleased. You'll have to adapt it to your code, of course.

Note that if both GUIs overlap, both will receive the click. Solving that would require a different approach. Or perhaps it's enough to check fixedGui.mousein and not call cameraGui:mousepress if it returns a value (assuming that fixedGui overlaps cameraGui).

User avatar
Karasuro
Prole
Posts: 16
Joined: Wed Aug 21, 2013 5:32 am

Re: Gspöt - retained GUI lib

Post by Karasuro » Sun Feb 24, 2019 3:54 am

pgimeno wrote:
Sun Feb 24, 2019 2:12 am
Note that if both GUIs overlap, both will receive the click. Solving that would require a different approach. Or perhaps it's enough to check fixedGui.mousein and not call cameraGui:mousepress if it returns a value (assuming that fixedGui overlaps cameraGui).
I coded my own mouse->object collision mCollision(obj) and can use conditional branching in gui:mousepressed() where obj is the element group.pos or even deeper child.pos and I'd prioritize fixedGui over cameraGui.

Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 9 guests