Safe Haskell | None |
---|---|
Language | Haskell98 |
Extras.Widget
Description
A very simple Graphical User Interface (GUI) for user interaction with buttons, checkboxes, sliders and a few others.
Synopsis
- guiDrawingOf :: ([Widget], [Number] -> Picture) -> Effect
- guiActivityOf :: ([Number] -> state, ([Widget], state) -> [Widget], ([Number], state) -> state, ([Number], state) -> Picture) -> Effect
- data Widget
- toggle :: (Text, Number, Number) -> Widget
- button :: (Text, Number, Number) -> Widget
- slider :: (Text, Number, Number) -> Widget
- randomBox :: (Text, Number, Number) -> Widget
- timer :: (Text, Number, Number) -> Widget
- counter :: (Text, Number, Number) -> Widget
- withConversion :: (Number -> Number, Widget) -> Widget
- type ReactorFun = ([Number], [Number]) -> Number
- withUpdate :: (ReactorFun, Widget) -> Widget
- setConversion :: (Number -> Number) -> Widget -> Widget
- setUpdate :: ReactorFun -> Widget -> Widget
- setLocation :: Point -> Widget -> Widget
- setLabel :: Text -> Widget -> Widget
- mapWidget :: (Text, Widget -> Widget) -> [Widget] -> [Widget]
- findWidgets :: Text -> [Widget] -> [Widget]
- getValue :: Widget -> Number
- setValue :: Number -> Widget -> Widget
- getRawValue :: Widget -> Number
- setRawValue :: Number -> Widget -> Widget
- widgetExample1 :: Effect
- widgetExample2 :: Effect
- widgetExample3 :: Effect
- widgetExample4 :: Effect
- widgetExample5 :: Effect
- widgetExample6 :: Effect
Documentation
Widget API
To use the extra features in this module, you must begin your code with this line:
import Extras.Widget
guiDrawingOf :: ([Widget], [Number] -> Picture) -> Effect #
The function guiDrawingOf
is an entry point for drawing that allows
access to a simple GUI. It needs two arguments: a list of
Widgets and a function to create your drawing. This user-supplied drawing
function will have access to the list of the current values of the widgets,
which is passed as an argument.
Example of use:
program = guiDrawingOf(widgets,draw) where widgets = [ withConversion(fullrange , slider("width" ,-7,-7)) , withConversion(fullrange , slider("height" ,-7,-9)) , withConversion(flipflop , toggle("show circle" ,-7,-5)) , withConversion(flipflop , button("show in green",-7,-3)) , withConversion(radiusrange , randomBox("radius" ,-7,-1)) ] fullrange(v) = 1 + 19 * v flipflop(v) = truncated(1 + 2 * v) radiusrange(v) = 0.2 + 0.8 * v draw(values) = combined([ circle(r)]#s, colored(solidRectangle(w,h), colors#c) ]) where colors = [RGB(1,0,0),RGB(0,1,0)] w = values#1 h = values#2 s = values#3 c = values#4 r = values#5
Note that the order in which the widgets are defined is important, because it determines how to access the correct value. Each widget fits in a box 4 units wide and 1 unit high.
guiActivityOf :: ([Number] -> state, ([Widget], state) -> [Widget], ([Number], state) -> state, ([Number], state) -> Picture) -> Effect #
The function guiActivityOf
is similar to activityOf
, but it
also lets you create and modify widgets at any time during program
execution.
The API for this entry point needs the user to provide 4 functions:
- A function that takes a list of random numbers and creates
a persistent
state
, which is an arbitrary, user-defined data structure. That function is typically calledinit
- A function that takes the current list of widgets and the current state, and it produces a new list of widgets, which may or may not be the same. This function allows the user to modify properties of the widgets. It should also be used to create the initial widgets. When the program starts, the list of widgets is empty, so the user can detect that fact and modify the list accordingly.
- A function that takes the current values of all the
widgets and the current persistent
state
, and it modifies thestate
based on those parameters. - A function that draws the current output, which is based on the values of the widgets and the current state.
A typical call to this function will be
guiActivityOf(init,update,widgets,draw)
where init
, update
, widgets
and draw
are user-supplied
functions that follow the API described above, and which
are called in that order within the system event loop.
Widgets
The internal structure of a Widget
is not exposed in
the user interface. You
have access only to the current value of each widget.
toggle :: (Text, Number, Number) -> Widget #
A toggle (checkbox) with the given label at the given location. When the box is not set, the value produced is 0. When the box is set, the value produced is 1
button :: (Text, Number, Number) -> Widget #
A button placed at the given location. While the button is pressed, the value produced is 1, but when the button is released, the value reverts back to 0.
slider :: (Text, Number, Number) -> Widget #
A slider with the given label at the given location. The possible values will range from 0 to 1, and the initial value will be 0.5.
randomBox :: (Text, Number, Number) -> Widget #
A box that produces a random number between 0 and 1. Each time you click on it, the value will change. The value 1 is never produced, so the actual range of values is 0 to 0.99999...
timer :: (Text, Number, Number) -> Widget #
A toggle that counts time up when you set it. When you click on the left side of the widget, the current value is reset to 0. You can stop the timer and start it again, and the value will increase from where it was when you stopped it.
Example:
program = guiDrawingOf(widgets,draw) where widgets = [ withConversion(\v -> 1 + 9 * v , slider("length",-7,-7)) , withConversion(\v -> v * 30 , timer("angle" ,-7,-9)) ] draw([l,a]) = rotated(translated(redBox,(l/2,0)),a) where redBox = colored(solidRectangle(l,0.25),RGB(1,0,0))
The timer operates in seconds, including decimals. However, the precision of the timer is not guaranteed beyond one or two decimals.
counter :: (Text, Number, Number) -> Widget #
A button that keeps incrementing the value each time you press it. The initial value is 0.
Converting and updating values
withConversion :: (Number -> Number, Widget) -> Widget #
Make the widget use the provided function to convert values from the default range of a widget to a different range.
Example:
newSlider = withConversion(\v -> 20 * v - 10, oldSlider)
Assuming that the old slider did not have any conversion function applied to it, the example above will make the new slider produce values between -10 and 10, while the old slider will still produce values between 0 and 1
When there is no reactor function associated to the widget, the conversion function can be arbitrary. However, when a reactor function is also used, the conversion function is expected to be either strictly increasing or strictly decreasing. Otherwise, the widget may or may not work properly when the values are updated by the reactor function.
type ReactorFun = ([Number], [Number]) -> Number #
A reactor function is used to update the values of a widget automatically. The function is called every time the value of any widget changes, and the values of all the widgets before and after that change are accessible.
Example. Assume you have defined two widgets: a timer and a slider, in that order, where the slider is used to control the position of an object. Then, adding the following reactor function to the slider would make it update automatically:
setBallPosition(old,new) | old#1 < new#1 = new#2 + 0.1 -- when time increases, move the object | otherwise = new#2 -- otherwise,do not change the position
A full example of use is shown in the documentation for withUpdate
withUpdate :: (ReactorFun, Widget) -> Widget #
Add a reactor function to a widget.
Example:
program = guiDrawingOf(widgets,draw) where widgets = [ withUpdate( setBallPy , withConversion( \v -> -10+20*v , slider("ballPy",-8,9))) , withUpdate( setBallVy , withConversion( \v -> -20*v + 10 , slider("ballVy",-8,7))) , timer("time",-8,5) , withConversion(\v -> 0.1 + 4.9*v, slider("radius",-8,3)) ] draw(v) = translated(redBall,(0,v#ballPy)) where redBall = colored(solidCircle(v#radius),RGB(1,0,0)) [ballPy,ballVy,time,radius] = range(length(widgets)) setBallPy(old,new) | old#time < new#time = new#ballPy + new#ballVy * (new#time - old#time) | otherwise = new#ballPy setBallVy(old,new) | new#ballPy + new#radius >= 10 = -abs(new#ballVy) | new#ballPy - new#radius <= -10 = abs(new#ballVy) | otherwise = new#ballVy
Widget modifications
setConversion :: (Number -> Number) -> Widget -> Widget #
Same functionality as withConversion
, but using a different convention
for the arguments.
setUpdate :: ReactorFun -> Widget -> Widget #
Same functionality as withUpdate
, but using a different convention
for the arguments.
setLocation :: Point -> Widget -> Widget #
Change the location of an existing widget
mapWidget :: (Text, Widget -> Widget) -> [Widget] -> [Widget] #
Apply the given transformation to the widget with the given label
Advanced functions
findWidgets :: Text -> [Widget] -> [Widget] #
Get all the widgets that have the given label. Typically, there should be only one, but since labels are just arbitrary text, there is no way to prevent two widgets from having the same label.
setValue :: Number -> Widget -> Widget #
Use a value already converted to set the internal value of the widget. It may not work properly if the conversion function is not strictly monotonic.
getRawValue :: Widget -> Number #
Get the internal value of an existing widget. This function does not use attached conversion functions, so use it with caution.
setRawValue :: Number -> Widget -> Widget #
Change the internal value of an existing widget. Use this function with caution, as it may produce unexpected results when there are conversion and reactor functions attached to the widget.
Examples
This is the example shown in the documentation for guiDrawingOf
This is the example shown in the documentation for timer
This example shows a tree created by a recursive function
This is the example shown in the documentation for withUpdate
This example shows two sets of sliders for selecting colors according to either RGB or HSL values. Each set is kept in sync with each other.