codeworld-base-0.2.0.0: Replacement base module for CodeWorld

Safe HaskellNone
LanguageHaskell98

Extras.Widget

Contents

Description

A very simple Graphical User Interface (GUI) for user interaction with buttons, checkboxes, sliders and a few others.

Synopsis

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:

  1. 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 called init
  2. 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.
  3. A function that takes the current values of all the widgets and the current persistent state, and it modifies the state based on those parameters.
  4. 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

data Widget #

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

setLabel :: Text -> Widget -> Widget #

Change the label 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.

getValue :: Widget -> Number #

Get the converted value from the widget.

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

widgetExample1 :: Effect #

This is the example shown in the documentation for guiDrawingOf

widgetExample2 :: Effect #

This is the example shown in the documentation for timer

widgetExample3 :: Effect #

This example shows a tree created by a recursive function

widgetExample4 :: Effect #

This is the example shown in the documentation for withUpdate

widgetExample5 :: Effect #

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.