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

Widget Api Full-Featured Gui

Started by thomasbogue, 10 August 2013 - 04:34 PM
thomasbogue #1
Posted 10 August 2013 - 06:34 PM
Announcing the widget API for easily creating beautiful GUI's with a variety of widgets.

[attachment=1303:widget_screenshot.png]
Example of my mob spawner controller. Note the sliders, buttons and labels are nicely (and automatically) arranged. I apologize that the screen shot is too small. I uploaded a larger image, and I'm not sure how to make it larger on the forum. If anyone has advice, I'd be happy to hear it.

Documentation:
[attachment=1305:widget_doc.pdf]

1 Features
• ease of use; few and simple commands to create and customize widgets
• automatic positioning of widgets means less work and better looking widgets; They also look good on different screen sizes
• myriad of widget types:
– buttons
– sliders
– toggles
– chooser (think drop-down combo box)
– labels
– progress bars
– and more
• object oriented framework makes adding new widgets easy; typically 10-15
minutes for me
• documentation and quick-start guide

2 Installation
Download widget from http://pastebin.com/PSmSpaLk . Place file in either rom/apis or your directly to your ComputerCraft computer.

Alternatively if you're running Minecraft v1.6 you can download and install the resource pack
[attachment=1306:widget.zip]
At least I think so. I'm running 1.5 myself so I haven't been able to test resource packs.

Load with the command
os.loadAPI("widget")

3 Quick Start
SpoilerThe first step to creating a GUI is to load the widget library with the code

os.loadAPI("widget")
The second is to create the widgets which will compose your interface. The simplest widget is the button. To create a button, use something like this code:

exampleButton=widget.newButton("press me")
This will create a button labeled “press me”. There are a number of different widgets (see section 5) such as sliders, progress bars, and choosers (which function like drop-down comboboxes). Lets add a new slider as well.

exampleSlider=widget.newSlider(1,10)
And a button to quit is always a good idea (you can use control-t, but its awkward).

quitButton=widget.newButton("quit")
Then we need to tell the system how to arrange our various widgets. We do this by placing them in rows or columns with code like

exampleColumn=widget.newColumn()
exampleColumn:add(exampleButton)
exampleColumn:add(exampleSlider)
exampleColumn:add(quitButton)
This adds the widget to our column. Note that the operator ‘:’ is used between the name of the variable and the function name. The function add is a member function of the variable exampleColumn. Basically its a function inside of the variable. To access it, use the semicolon (‘:’) after the variable name. This library uses a lot of member functions, so watch out for them.

The column will now give its space to the widgets you’ve added. In this case it will split the space evenly (or as evenly as possible) between the two buttons and the slider. Its important to note that rows and columns are widgets too. That means you can have rows of columns or columns of rows. My mob spawner controller has a column of rows with columns in it.

Next, we should tell the computer what to do when the button is pressed. For example, if we wanted to print the value of the slider, then we would use the code

function exampleButton.onClick(button)
exampleButton:blink()
print("The slider has a value of " .. tostring(exampleSlider.value))
end
function quitButton.onClick(button)
exampleColumn.run=false
end
Now we just need to tell the computer we’re ready to run the GUI.

exampleColumn:run()
This will display the GUI and process mouse clicks in-game. Note that you can adjust the slider by right-clicking on it.

4 Configuring the API
SpoilerFor the most part no configuration is necessary.
4.1 Changing the color scheme
If you want to change the color scheme, set the following variables:
  • widget.defaultButtonBackground
  • widget.defaultButtonForeground
  • widget.terminalBackground
  • widget.defaultSliderForeground
  • widget.defaultSliderBackground
  • widget.defaultFlagMonitorForeground
  • widget.defaultProgressBarForeground
4.2 Changing the default monitor
Normally the attached advanced monitor is automatically detected. However, there are two situations in which you would want to set the monitor manually. The first is if there are more than one advanced monitors adjacent to your computer and it is autodetecting the wrong one. In that case, use something like

widget.setMonitor(peripheral.wrap("bottom"))
The other possibility is that you want your GUI to appear not on an advanced monitor, but on the advanced computer itself. Again this is usually handled automatically, but if there is an advanced monitor attached it will try to display there unless you set the monitor to the terminal manually with the command

widget.setMonitor(term)

5 widget API for GUI designers
Spoiler5.1 widget
Constructor:

example=widget.newWidget()
This sets up an empty widget called example with does nothing.
Functions and Fields:
example:run()
runs the GUI with this widget as the screen; stops when widget.quit is true
example:onClick()
defines what the widget does when clicked; by default does nothing
example:draw()
draws the widget; This is useful if the widget needs to be redrawn. by default does nothing
example:processEvent(event,side,x,y)
processes an event, ignoring events other than monitor touch and mouse click
example.vWeight
specifies the height of the widget compared to a standard widget; For example, if vWeight was 0.5, this widget would ask for half as much room as a vWeight=1 widget.
example.hWeight
specifies the width of the widget compared to a standard widget One might ask why this even exists, since it is a blank widget which does nothing. The answer is that all other interface elements (buttons, progress bars, et c.) are also widgets, and get to run these functions as well. In programmer terminology all widget classes (such as button) derive from this class. So while you’ll probably never use the newWidget function, every widget you create will have access to the run and onClick functions. And you can overwrite the onClick function of any widget to make it do whatever you want when you click it.

5.2 row
Constructor:

example=widget.newRow()
Functions and Fields:
example:add(widget)
adds a widget to the row
example.hSpacingWeight
specifies how large the horizontal spacing should be relative to a standard widget; only affects rows

5.3 column
Constructor:
example=widget.newColumn()
Functions and Fields:
example:add(widget)
adds a widget to the column
example.vSpacingWeight
specifies how large the vertical spacing should be relative to a standard widget; only affects columns

5.4 button
Constructor:

example=widget.newButton(”label”)
where “label” should be the label of the button.
Functions and Fields:
example:drawFull()
draws the button as a solid, labeled block
example:drawOutline()
draws the button a hollow, labeled block
example:blink()
draws the button as an outline, then solid again a moment later; This can give feedback to the user that the button was really clicked.
example.label
the label of the button

5.5 toggle button
Constructor:
example=newToggleButton(”label”)
A toggle button is derived from button, and so can use all of a button’s functions and field. In addition it will either draw as a solid or outline depending on its state.
Functions and Fields:
example:toggle()
switched the button between the on and off states
example.state
true iff the button is on

5.6 label
Constructor:
example=widget.newLabel(”text”)
This creates a label called example which displays the word “text”. Functions and Fields:
example.text
what text the label is displaying

5.7 slider
Constructor:
example=widget.newSlider(1,10)
creates a slider called example which goes between 1 and 10 inclusive
Functions and Fields:
example.value
the value of the slider
example.foregroundColor 
the color of the filled slider portion
example.backgroundColor
the color of the unfilled slider portion

5.8 flag monitor
Constructor:
example=widget.newFlagMonitor(”top”)
creates a flag monitor called example which monitors and sets the redstone bundled signal out the top of the computer:
Functions and Fields:
example:get(i)
returns the i’th color of the output
example:set(i,value)
sets the i’th color of the output to value; if value is absent or nil, it is treated as true
example:clear(i)
sets the i’th color of the output to off
example:toggle(i)
sets the i’th color of the output to whatever it was not before; If it was on previously, it will be off. If it was off previously, it will be on.
example.flags
a 16-bit integer representing the state of the bundled output

5.9 chooser
Constructor:
example=widget.newChooser(”axe”,”pick”,”axe”,”fish”)
will create a chooser called example which allows the user to pick between “pick”, “axe” and “fish”. It will initially be set to “axe”. Note that chooser derives from button, so it has all the functionality of a button as well.
Functions and Fields:
example:choose
brings up a page to allow the user to choose one of the possible choices
example.label
the current choice

5.10 progress bar
Constructor:
example=newProgressBar(15)
will create a progress bar called example which will be full when it reaches a value of 15.
Functions and Fields:
example.value
the current value of the progress bar
example.foreground
the color of the filled portion
example.background
the color of the unfilled portion

6 Possible future features
• a variable widget which can change between different widget types
• a tab widget which switches between multiple interface pages
• multimonitor support
• documentation for adding new widgets

7 example application: keypad
Spoiler

redstone.setOutput("right",false) -- lock the door
os.loadAPI("widget")
-- a button to operate a lock via a keypad advanced monitor
-- which means no control-t or control-r override
-- create the widgets
button1=widget.newButton("1")
button2=widget.newButton("2")
button3=widget.newButton("3")
button4=widget.newButton("4")
button5=widget.newButton("5")
button6=widget.newButton("6")
button7=widget.newButton("7")
button8=widget.newButton("8")
button9=widget.newButton("9")
button0=widget.newButton("0")
backspaceButton=widget.newButton("<-")
clearButton=widget.newButton("reset")
-- arrange the widgets
row1=widget.newRow()
row1:add(button7)
row1:add(button8)
row1:add(button9)
row2=widget.newRow()
row2:add(button4)
row2:add(button5)
row2:add(button6)
row3=widget.newRow()
row3:add(button1)
row3:add(button2)
row3:add(button3)
row4=widget.newRow()
row4:add(backspaceButton)
row4:add(button0)
row4:add(clearButton)
main=widget.newColumn()
main:add(row1)
main:add(row2)
main:add(row3)
main:add(row4)
-- program variables
passcode="1337"
entry=""
-- configure the widgets
function clearButton.onClick(button)
entry=""
end
function backspaceButton.onClick(button)
if (entry:len()>0) then
entry=entry:sub(1,entry:len()-1)
end
end
function button0.onClick(button)
entry=entry .. button.label
if (entry==passcode) then
print("access granted " .. os.time())
redstone.setOutput("right",true) -- open the door
os.sleep(4) -- let someone through
redstone.setOutput("right",false) -- automatically lock the door a
entry=""
end
end
-- all the buttons should do the same thing
-- append their label to entry
-- and check if the code is entered
-- so we can just copy the onClick function
-- instead of rewriting it
button1.onClick=button0.onClick
button2.onClick=button0.onClick
button3.onClick=button0.onClick
button4.onClick=button0.onClick
button5.onClick=button0.onClick
button6.onClick=button0.onClick
button7.onClick=button0.onClick
button8.onClick=button0.onClick
button9.onClick=button0.onClick
-- display the GUI
main:run()

8 Licensing
SpoilerThis software is copyrighted by Thomas Bogue and is licensed under the Lesser GPL. That means that you can use it, link to it, copy it, download it, modify it, and in fact do almost anything so long as you don’t change the license or say you wrote it. But for for more details check out http://www.gnu.org/licenses/lgpl.html .
Bubba #2
Posted 10 August 2013 - 06:41 PM
Very, very nice! Great documentation, standardization, and functionality. I'm generally not one to use other's APIs, but it looks like this one would be well worth using.
thomasbogue #3
Posted 10 August 2013 - 07:52 PM
Great! If you find a use for it, give me a screenshot. I'd love to see what use people get out of this thing.
minizbot2012 #4
Posted 12 August 2013 - 04:08 PM
adding this to the rom/apis seems to cause computers in my world to not load, tested with the res pack. there is no errors in the terminal window.
Edit: after testing a bit more i find that i am able to manually load the widget api, but this is not what i want, i want it to autoload
Encreedem #5
Posted 16 August 2013 - 11:42 AM
This looks great although I will probably not use it because I'm too lazy :lol:/> . I hope you don't mind if I steal some your ideas to make my API even better ^^
Phoenix #6
Posted 16 August 2013 - 01:10 PM
Questions Questions and a big thumb up for the Documentation.
One best GUI APIs I've seen.
Though I have some questions:
Q:
your example of the mob spawner controller.
How did you add text above the sliders?
Q:
So the Documentation…
It's pretty good.
But "5 widget API for GUI designers"
Always using "example" confuses the F*ck out of me.
But that might be just me.

5.1 widget
Constructor:
"example_widget=widget.newWidget()"



5.2 row
Constructor:
"example_row=widget.newRow()"

Isn't that better?
Or am I just being a Knuckle Head?

That's all.
EDIT:
Error I got while making a GUI

widget:357: vm error:
java.lang.negativeArraySizeException

GUI code:

os.loadAPI("widget")

AddX=widget.newSlider(5,15)
AddY=widget.newSlider(5,15)
Goh=widget.newButton("Goh!")
Test=widget.newButton("Example")
LabelX=widget.newLabel("Add X")
LabelY=widget.newLabel("Add Y")
--exampleSlider.value
--widgets, rows, colums, Lack of english
AddRow=widget.newRow()
AddRow:add(AddX)
AddRow:add(LabelX)--<<<This is causing the crash
AddRow:add(AddY)
AddRow:add(LabelY)--<<<This is causing the crash
AddRow:add(Goh)
ExampleRow=widget.newRow()
ExampleRow:add(Test)
main=widget.newColumn()
main:add(AddRow)
main:add(ExampleRow)
--edit functions
function Goh:onClick()
Goh:blink()
end
-- run GUI
main:run()

Also in the doc. >> "true iff the button is on"
One 'f' too much?
Tonny #7
Posted 18 August 2013 - 06:59 AM
Hi Thomas,

I made some changes in your api, and implemented two new widgets ("switch" and "vertical slider"), both with includet label. If you are interested we can colaborated on this project for future:). Also if someone wants I can share edited api.


InputUsername #8
Posted 19 August 2013 - 02:14 PM
Looks really useful. Great and extensive documentation. I also like the consistent and clean coding style of the API itself. Only I believe I have found a mistake in the code. Line 92 (in the function widget.run inside newWidget) says

if (widget==null) then

but 'null' is not Lua. I think it should be replaced with 'nil'.

Anyway, great job!
Fenthis #9
Posted 16 November 2013 - 02:20 PM
In the docs there is the following:


example=widget.newChooser(”axe”,”pick”,”axe”,”fish”)

It should be:



example=widget.newChooser(”axe”, { ”pick”,”axe”,”fish” })
WorldObserver #10
Posted 20 February 2014 - 01:51 AM
Edit: Nvm i figured it out :P/>
Edited on 20 February 2014 - 10:44 PM
CCGrimHaxor #11
Posted 17 May 2014 - 03:39 PM
Due to my hard to understand simple things do you give me permission to use your api in my os?
Please replay fast the creation of the os is going to be finished soon.
What I will add:
I will give you full credit for the api
I will put a link to this post in my post
I will not edit the lisence
(You can add more if you wish)

Sorry if I wrote a word wrong I am using my phone and I am not that good in english