Concept of a button?

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Ramcat
Prole
Posts: 18
Joined: Sun Jun 01, 2014 12:12 am

Concept of a button?

Post by Ramcat »

So I just announced the "soft" launch of "LoveTank". And I also mentioned the desire to add a screen that allows people to select which tanks will participate in the battle. What I'd like to do is have each tank name be a "button", so that when you click it, it changes color or otherwise shows it's selected.

I'm an old coder, I started on mainframes about 10 years before the PC revolution and moved to PC programming as my profession after my stint in my county's armed forces. My point there is I have coded in a lot of languages and paradigms. I code AS3 and like the similarities between that and Lua. I read https://love2d.org/wiki/Easy_GUI_System where he seems to be using classes and prototypical features of the language to "bolt on" methods that react to clicking on them or hovering over them. Unfortunately I could not find his code (seems to be missing from github).

As I was writing this, I remember reading http://stackoverflow.com/questions/2207 ... -clickable where it appears that the "love.update(dt)" function is creating an anonymous function that corresponds to an already existing Love2D function. To me that seems like bad coding but maybe each of the love.mousepressed functions will all fire. I guess this is similar to the Observer pattern that notifies as many listeners as exist.

If I had the code below, would both functions "fire"?

Code: Select all

function love.update(dt)
    function love.mousepressed( x, y)   
        if x > 440 and x < 540 and y > 380 and y < 410 then 
            love.event.quit()
        end
    end
end

function love.mousepressed(x, y, button)
	_GameOn = true
end
Trying it out in my code that did not work, unless I made a mistake. I could only get the "anonymous" instance to fire. As soon as I removed that the other function would fire. I am clueless as to how to make an object that would own a click event and catch the user click independent from the Love2D love.mousepressed callback.
User avatar
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Above the Pocket Dimension
Contact:

Re: Concept of a button?

Post by HugoBDesigner »

The function "love.mousepressed" is called whenever you click with the mouse, so you don't need to (actually, you CAN'T) put it in love.update. You can either put your pressing button code in love.mousepressed (as with _GameOn = true) or do this:

Code: Select all

function love.update(dt)
    if love.mouse.isDown("l") then
        local x, y = love.mouse.getPosition()
        --rest of the click code here
    end
end
To make it even easier, you can add a button function, like this:

Code: Select all

function love.load()
	buttons = {} --here's where you'll store all your buttons
	buttons["quit"] = button:new(440, 380, 100, 30, love.event.quit)
	--I organized this way: "x", "y", "width", "height", "function", "arguments in a table or nil"
	--I think it's easier to use width and height instead of first x/y and last x/y
end

function love.mousepressed(x, y, button)
	if button == "l" then
		for i, v in pairs(buttons) do
			if x >= v.x and y >= v.y and x < v.x+v.width and y < v.y+v.height then
				v.click()
			end
		end
	end
end

function button:new(x, y, w, h, f, a)
	local self = {}
	self.x = x
	self.y = y
	self.width = w
	self.height = h
	self.function = f
	self.arguments = a
	self.click = function()
		if self.function then
			if self.arguments then
				self.function(unpack(self.arguments))
			else
				self.function()
			end
		end
	end
end
Or you can use my more advanced code for a library I'm working on. There are several other examples that may be better for you, but if you want, I can give you my button code. I hope I helped!
@HugoBDesigner - Twitter
HugoBDesigner - Blog
Ramcat
Prole
Posts: 18
Joined: Sun Jun 01, 2014 12:12 am

Re: Concept of a button?

Post by Ramcat »

Very enlightening.

So your "buttons" table is sort of like the stage in Action Script. You add the active buttons that are currently displayed on the screen to the button table and remove any buttons from that table as they are removed from the screen. That way your looping code can only click active/displayed buttons.

I am thinking in terms of MVC (Model-View-Controller) and realize I have to shift my thinking. Still though I can see a "controller" type object that managed what buttons were in your "buttons" table. The concept could be extended to any VB type controls, buttons, textboxes, dropdowns, etc. This would force the programmer to create their own focus system to deal with multiple textboxes, etc.

Back to the "stage" concept, assuming you had objects that could draw themselves and sort out their z-order (ones farther back draw first) you could add each active object to the stage and remove each object from the stage that should no longer display. Perhaps you could have a "clickable" table as well so objects that could receive clicks could be added to that table as well as the stage and then the user could direct clicks to those objects in an automated fashion (with some z-order sorting if clicks are consumed).

With no inherent "form" architecture it leaves the programmer the need to invent a lot of things.
User avatar
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Above the Pocket Dimension
Contact:

Re: Concept of a button?

Post by HugoBDesigner »

To control whether or not buttons can be clicked, you can also add a variable like "active". In the last code I gave you, you can do this:

Code: Select all

function love.load()
   buttons = {} --here's where you'll store all your buttons
   buttons["quit"] = button:new(440, 380, 100, 30, love.event.quit)
   --I organized this way: "x", "y", "width", "height", "function", "arguments in a table or nil"
   --I think it's easier to use width and height instead of first x/y and last x/y
end

function love.mousepressed(x, y, button)
   if button == "l" then
      for i, v in pairs(buttons) do
         if x >= v.x and y >= v.y and x < v.x+v.width and y < v.y+v.height and v.active then --See the "v.active"?
            v.click()
         end
      end
   end
end

function button:new(x, y, w, h, f, a)
   local self = {}
   self.active = true --HERE'S THE CONTROL FOR ACTIVATION
   self.x = x
   self.y = y
   self.width = w
   self.height = h
   self.function = f
   self.arguments = a
   self.click = function()
      if self.function then
         if self.arguments then
            self.function(unpack(self.arguments))
         else
            self.function()
         end
      end
   end
end
So, whenever you want the button to go inactive, even though it is on the screen, just put this somewhere in your code:

Code: Select all

buttons["quit"].active = false

For what you said, yes, we have to do GUI elements by ourselves. But you can always count in with someone else's libraries, like LÖVE Frames, for example.

I'm also working on a library that includes some GUI elements, but it's still a work in progress. Most GUI elements are easy to do, but take a lot of time to make them "fancy".
@HugoBDesigner - Twitter
HugoBDesigner - Blog
Ramcat
Prole
Posts: 18
Joined: Sun Jun 01, 2014 12:12 am

Re: Concept of a button?

Post by Ramcat »

Thanks again for the input/help. I created my own button class - nothing fancy but clickable and draws itself. Hover would be a nice feature as well as a tooltip, but I don't need those things yet. One thing I can't find going through the docs and example code is, how do I tell how long and how high a given string of text will be when rendered? Java has this feature, although I did have trouble making that work on Mac OS.

Here is a cheap button:

Code: Select all

require 'class'

local M=class(function(self, text, x, y, width, height)
	self.text = text
	self.X = x
	self.Y = y
	self.width = width
	self.height = height
	self.clicked = false
end)

function M:draw()
   love.graphics.setColor(0, 200, 255, 255)
	love.graphics.rectangle("fill", self.X, self.Y, self.width, self.height)
	love.graphics.setColor(0, 0, 0, 255)
	love.graphics.print(self.text, self.X + (self.width / 4), self.Y + (self.height / 4))
end

function M:click(x, y)
	if x > self.X and x < self.X + self.width and y > self.Y and y < self.Y + self.height then
		self.clicked = true
	end
end

function M:wasClicked()
	local clicked = self.clicked
	self.clicked = false
	return clicked
end

return M
User avatar
OttoRobba
Party member
Posts: 104
Joined: Mon Jan 06, 2014 5:02 am
Location: Sao Paulo, Brazil

Re: Concept of a button?

Post by OttoRobba »

To get the length of a string in pixels, use the :getWidth function.

As for hover, it is quite easy and involves making your code simpler:

Code: Select all

function M:isHovering(x,y)
if x > self.X and x < self.X + self.width and y > self.Y and y < self.Y + self.height then
      return true
   end
end
Which is pretty much your current click action minus the self.clicked = true.
Why is this optimal?

Because then you can do this:

Code: Select all

function M:mousepressed(x,y, button)
if M:isHovering(x,y) and button == "l" then
self.clicked = true
end

function M:showTooltip(x,y)
if M:isHovering(x,y) then
--code for displaying a tooltip
end
end

And you could write all manner of interaction (left or right button, keyboard functions changing the button, tooltips, etc...)
User avatar
Djent
Prole
Posts: 27
Joined: Sun Jan 20, 2013 3:22 pm
Location: Rhode Island

Re: Concept of a button?

Post by Djent »

To make it even easier, you can add a button function, like this:

Code: Select all

function love.load()
	buttons = {} --here's where you'll store all your buttons
	buttons["quit"] = button:new(440, 380, 100, 30, love.event.quit)
	--I organized this way: "x", "y", "width", "height", "function", "arguments in a table or nil"
	--I think it's easier to use width and height instead of first x/y and last x/y
end

function love.mousepressed(x, y, button)
	if button == "l" then
		for i, v in pairs(buttons) do
			if x >= v.x and y >= v.y and x < v.x+v.width and y < v.y+v.height then
				v.click()
			end
		end
	end
end

function button:new(x, y, w, h, f, a)
	local self = {}
	self.x = x
	self.y = y
	self.width = w
	self.height = h
	self.function = f
	self.arguments = a
	self.click = function()
		if self.function then
			if self.arguments then
				self.function(unpack(self.arguments))
			else
				self.function()
			end
		end
	end
end
When I try something like this I got "Error attempt to index global button (a nil value)". I had to add

Code: Select all

return self
to the end of button:new().
User avatar
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Above the Pocket Dimension
Contact:

Re: Concept of a button?

Post by HugoBDesigner »

Yeah, you have to, I just forgot. Sorry, really :?
@HugoBDesigner - Twitter
HugoBDesigner - Blog
Ramcat
Prole
Posts: 18
Joined: Sun Jun 01, 2014 12:12 am

Re: Concept of a button?

Post by Ramcat »

@OttoRobba
Thank you. I missed the "See Also" font link.

Leading to: A minor improvement in the button class.

Code: Select all

require 'class'

local M=class(function(self, text, x, y, width, height)
	self.text = text
	self.X = x
	self.Y = y
	self.width = width
	self.height = height
	self.clicked = false
	local font = love.graphics.newFont()
	self.textWidth = font:getWidth(text)
	self.textHeight = font:getHeight(text)
end)

function M:draw()
	love.graphics.setColor(0, 200, 255, 255)
	love.graphics.rectangle("fill", self.X, self.Y, self.width, self.height)
	love.graphics.setColor(0, 0, 0, 255)
	love.graphics.print(self.text, self.X + (self.width / 2) - (self.textWidth / 2), self.Y + (self.height / 2) - (self.textHeight / 2))
end

function M:click(x, y)
	if x > self.X and x < self.X + self.width and y > self.Y and y < self.Y + self.height then
		self.clicked = true
	end
end

function M:wasClicked()
	local clicked = self.clicked
	self.clicked = false
	return clicked
end

return M
Ramcat
Prole
Posts: 18
Joined: Sun Jun 01, 2014 12:12 am

Re: Concept of a button?

Post by Ramcat »

I know I have been gone for two days (which is nothing in development time) but I wanted to shoot a quick update. Thank you all for your input and suggestions. The button classes and framework references were great.

Because I am using Love2D as a learning platform (to learn Lua, and provide a platform to teach Lua to others) and am a professional Software Engineer I didn't want to learn and use someone else's framework. For LoveTank, a small unsophisticated project I didn't need one. But work is asking me to solve a problem for an AS3 project which closely parallels my LoveTank project. So I spend a day changing all of the architecture in LoveTank based on your suggestions. Basically we've invented a micro-architecture scene manager, which I am now porting to AS3.

Can't wait to finish the new architecture in LoveTank and show you what I learned from your more experienced ideas (I love my new button class).
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 23 guests