This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Kingdaro's profile picture

Immediate Mode GUI

Started by Kingdaro, 22 December 2013 - 01:34 AM
Kingdaro #1
Posted 22 December 2013 - 02:34 AM
This is essentially a nice, nameless GUI library that utilizes the concept of an immediate mode GUI. The way it works is that you call a function to create a GUI element, while checking the state of said GUI element at that very instant. As an example for buttons:


if imgui.button{ text = 'Some Button', x = 5, y = 3 } then
  print "I was clicked!"
end

The function here creates and "registers" a draw command in the library, while also telling you whether the mouse clicked in the area of that theoretical button. The concept of an IMGUI is to store the state of buttons in the program itself, rather than keep a reference of API-generated classes or objects. In this system, hiding an element is as easy as not calling the function.

Because the GUI functions in this libraries use tables, you can change the properties of buttons if you store them beforehand:

local button = { text = 'I was not clicked yet', x = 1, y = 1 }

if imgui.button(button) then
   button.text = 'I am clicked!'
end

The library right now only supports buttons and sliders.

With the introduction out of the way, onto usage.


imgui.label - Draws a static label with the given text, x and y values in the table. Example:
imgui.label{ text = "Label Text", x = [xpos], y = [ypos] }

imgui.button - A regular button. Returns true when clicked, false otherwise. text, x, and y all must be given. width and height default to the size of the text itself, with padding. bgColor and textColor default to the value in the program's defaults table.
Example:

--# all default properties of the button (except text, x, and y which must be defined)
local defaultbutton = {
   text = "Button Text";
   x = 1, y = 1;

   bgColor = colors.blue;
   textColor = colors.white;
   width = (the width of text);
   height = (the height of text);
}

--# a normal button
local button = { text = 'This is a button!', x = 1, y = 1 }
if imgui.button(button) then
   button.text = 'I was clicked!'
end

--# a red button
local red = { text = 'This button is red!', x = 1, y = 1, bgColor = colors.red }
if imgui.button(red) then
   --# turns into a blue button
   red.bgColor = blue
end

imgui.slider - A slider. x and y must be defined. Can be vertical or horizontal. min and max determine the upper and lower values of the slider, and value determines the value itself of the slider. value defaults to the midpoint between max and min. length is the visual length of the slider.
Example:

--# All default slider values (except x and y, which must be defined):
local slider = {
   x = 1, y = 1;

   bgColor = colors.gray;
   fgColor = colors.blue;
   length = 20;
   orientation = 'horizontal'; --# can also be vertical
   min = 0;
   max = 1;
   value = 0.5;
}

--# regular slider with min and max values 0 and 10
--# will have a default value of 5 unless given
slider = { x = 1, y = 1, min = 0, max = 10 }

--# imgui will update the slider automatically.
imgui.slider(slider)


imgui.setDefaults(defaults) - Where "defaults" is a table that defines the default values that should be used in drawing GUI elements. The default defaults table:
Spoiler
local defaults = {
	label = {
		bgColor = colors.black;
		textColor = colors.white;
	};
	button = {
		bgColor = colors.blue;
		textColor = colors.white;
		padding = {2, 1};
	};
	slider = {
		orientation = 'horizontal';
		length = 20;
		bgColor = colors.gray;
		fgColor = colors.blue;
		min = 0;
		max = 1;
	};
}
Any of these values can be redefined using this function.

imgui.clear(color) - Convenience, clears the screen using the given color. If color is not given, will use the current background color.

imgui.register(func) - Used to "register" a function to draw on imgui.draw(). Used by all GUI functions in the library, and can be used to implement custom GUI elements.

imgui.draw - Draws all of the GUI elements.

imgui.pullEvent(filter) - Updates the libraries state, and returns events pulled. Example:

os.loadAPI('imgui')

local button = { text = "Hello World!", x = 2, y = 2 }

while true do
   if imgui.button(button) then
      button.bgColor = 2 ^ math.random(14) --# button changes to a random color when clicked
   end

   imgui.clear(colors.black)
   imgui.draw()

   ev, p1 = imgui.pullEvent()
   if ev == 'key' and p1 == keys.backspace then
      return
   end
end


And a much more "colorful" example written to test the library:
Spoiler

os.loadAPI('imgui')

-- a regular old button
local button = { text = 'This is a button!', x = 2, y = 4, width = 23 }

-- a toggleable button
-- each property can be mapped to an "on" an "off" state
local toggleable = {
	text = 'Toggle button', x = 2, y = 8, width = 23, bgColor = colors.red
}

-- default slider, minimum value 0, maximum value 10
-- value starts at middle by default
local slider1 = { x = 2, y = 14, min = 0, max = 10 }

-- rainbow slider! :D/>/>/>/>/>
local rainbow = {
	colors.red, colors.orange, colors.yellow,
	colors.lime, colors.green, colors.lightBlue,
	colors.blue, colors.purple}
local slider2 = { x = 2, y = 16, min = 1, max = 8, value = 1,
	fgColor = colors.red }

-- vertical slider
local vslider = { x = 26, y = 4, length = 10, orientation = 'vertical' }

local timer = os.startTimer(1) -- just to update the clock
while true do
	imgui.label{ text = "A simple label", x = 2, y = 2 }

	if imgui.button(button) then
		button.text = 'I was clicked!'
	end

	if imgui.button(toggleable) then
		toggleable.active = not toggleable.active
		toggleable.bgColor = toggleable.active and colors.green or colors.red
	end

	imgui.label{ text = 'Sliders!', x = 2, y = 13 }

	imgui.slider(slider1)
	imgui.label{ text = ("%d"):format(slider1.value), x = 23, y = slider1.y }

	if imgui.slider(slider2) then
		slider2.fgColor = rainbow[math.ceil(slider2.value)]
	end

	imgui.slider(vslider)

	local w,h = term.getSize()
	imgui.label{ text = textutils.formatTime(os.time()), x = w - 5, y = h - 1}

	imgui.clear(colors.black)
	imgui.draw()

	ev, p1, p2, p3 = imgui.pullEvent()
	if ev == 'key' and p1 == keys.backspace then
		break
	elseif ev == 'timer' and p1 == timer then
		timer = os.startTimer(1)
	end
end

You can grab the code from pastebin: pastebin get 5AcWhbc8 imgui

And as always, comments, suggestions, and bug reports are appreciated.
Edited on 22 December 2013 - 10:15 AM
oeed #2
Posted 22 December 2013 - 03:30 AM
So, I would presume that any code after the button if statement is only called after it's clicked, or is there some wizardry preventing this?

Also, the Pastebin link appears to be the test rather than the API.

Edit: Also, should

if imgui.button{ text = 'Some Button', x = 5, y = 3 } then

be


if imgui.button({ text = 'Some Button', x = 5, y = 3 }) then

Note the '(…)'
Edited on 22 December 2013 - 02:33 AM
theoriginalbit #3
Posted 22 December 2013 - 03:49 AM
Edit: Also, should

if imgui.button{ text = 'Some Button', x = 5, y = 3 } then
be

if imgui.button({ text = 'Some Button', x = 5, y = 3 }) then
Note the '(…)'
Nope, a function can be invoked without braces if, and only if, the argument count is one and the data being passed is either a table or a string
Symmetryc #4
Posted 22 December 2013 - 10:18 AM
Where is the download link? I can't find it in the post (Probably in a really obvious place :/)
lieudusty #5
Posted 22 December 2013 - 11:05 AM
Where is the download link? I can't find it in the post (Probably in a really obvious place :/)
http://pastebin.com/5AcWhbc8
The obvious place
Kingdaro #6
Posted 22 December 2013 - 11:18 AM
I accidentally posted the wrong code last night. Note to self: never code at 4 in the morning :lol:/>

So, I would presume that any code after the button if statement is only called after it's clicked, or is there some wizardry preventing this?
I'm not sure what you mean, but to elaborate further, the if statement essentially serves as a callback for the button's click event.

if imgui.button{ text = 'bagel', x = 2, y = 2 } then
   print "I will only print if bagel was clicked."
end
print "I will always print."
(Of course, if the above code were in some sort of loop, and that you could actually see the print(), but yeah.)
oeed #7
Posted 22 December 2013 - 04:33 PM
Edit: Also, should

if imgui.button{ text = 'Some Button', x = 5, y = 3 } then
be

if imgui.button({ text = 'Some Button', x = 5, y = 3 }) then
Note the '(…)'
Nope, a function can be invoked without braces if, and only if, the argument count is one and the data being passed is either a table or a string
Oh, I didn't know that :)/>

I accidentally posted the wrong code last night. Note to self: never code at 4 in the morning :lol:/>

So, I would presume that any code after the button if statement is only called after it's clicked, or is there some wizardry preventing this?
I'm not sure what you mean, but to elaborate further, the if statement essentially serves as a callback for the button's click event.

if imgui.button{ text = 'bagel', x = 2, y = 2 } then
   print "I will only print if bagel was clicked."
end
print "I will always print."
(Of course, if the above code were in some sort of loop, and that you could actually see the print(), but yeah.)

I see, I'll dig through your code and try to figure out how you did it :P/>
Molinko #8
Posted 22 December 2013 - 08:13 PM
Edit: Also, should

if imgui.button{ text = 'Some Button', x = 5, y = 3 } then
be

if imgui.button({ text = 'Some Button', x = 5, y = 3 }) then
Note the '(…)'
Nope, a function can be invoked without braces if, and only if, the argument count is one and the data being passed is either a table or a string

just to split hairs… you can also drop the brackets when using a method call with ':'. i.e. button:new{ –this is the second arg actually }
theoriginalbit #9
Posted 22 December 2013 - 08:20 PM
just to split hairs… you can also drop the brackets when using a method call with ':'. i.e. button:new{ –this is the second arg actually }
of course you can, notice I said "a function can be invoked" which no matter which notation you use (dot or colon) its still a function that is being invoked. using colon notation is just syntactical sugar, so every rule that applies to dot notation also applies to colon notation.
Symmetryc #10
Posted 23 December 2013 - 12:22 AM
just to split hairs… you can also drop the brackets when using a method call with ':'. i.e. button:new{ –this is the second arg actually }
of course you can, notice I said "a function can be invoked" which no matter which notation you use (dot or colon) its still a function that is being invoked. using colon notation is just syntactical sugar, so every rule that applies to dot notation also applies to colon notation.
Lol BIT, he meant the fact that you are passing more than one argument when using the colon, which your 'rules' says is not possible :P/>.