gui-easy:   Declarative GUIs
1 Quickstart
1.1 Hello, World!
1.2 Counter
1.3 Counters
1.4 Dynamic Counters
1.5 More
2 Geometry Management
3 Custom Views
3.1 Custom Containers
4 Escape Hatches
5 Reference
5.1 Renderers
renderer?
embed
render
render-popup-menu
renderer-root
5.2 Views
5.2.1 Windows & Dialogs
window
dialog
5.2.2 Menus & Menu Items
popup-menu
menu-bar
menu
menu-item
menu-item-separator
5.2.3 Containers
hpanel
vpanel
group
tabs
if-view
cond-view
case-view
list-view
5.2.4 Canvases & Snips
canvas
pict-canvas
snip-canvas
snip
5.2.5 Controls
button
checkbox
choice
image
input
progress
radios
slider
spacer
table
text
5.2.6 Interfaces
5.2.6.1 view<%>
view<%>
dependencies
create
update
destroy
5.2.6.2 window-view<%>
window-view<%>
create
is-dialog?
5.2.6.3 popup-menu-view<%>
popup-menu-view<%>
create
5.3 Observables
obs?
obs
obs-rename
obs-observe!
obs-unobserve!
obs-update!
obs-peek
obs-map
obs-combine
obs-debounce
obs-throttle
5.4 View Helpers
case/  dep
5.5 Observable Operators
define/  obs
@
:  =
λ:  =
<~
~>
λ<~
5.6 Contracts
alignment/  c
margin/  c
maybe-label/  c
position/  c
size/  c
spacing/  c
stretch/  c
obs/  c
maybe-obs/  c
8.6

gui-easy: Declarative GUIs

Bogdan Popa <bogdan@defn.io>

This library provides a declarative API on top of racket/gui. This library is still a work in progress, so expect some breaking changes.

1 Quickstart

1.1 Hello, World!

(window
 (text "Hello, World!"))

The code above describes a view hierarchy rooted in a window that contains the text “Hello, World!”. By itself, it doesn’t do much, but you can take it and pass it to render to convert it into a native GUI:

(render
 (window
  (text "Hello, World!")))

1.2 Counter

State in gui-easy is held by observables.

(define @count (@ 0))
(render
 (window
  (hpanel
   (button "-" (λ () (@count . <~ . sub1)))
   (text (@count . ~> . number->string))
   (button "+" (λ () (@count . <~ . add1))))))

Here we define an observable called @count that holds the current value of a counter. The two buttons change the value of the counter when clicked and the text view displays its current string value via a derived observable. The three widgets are laid out horizontally by the hpanel.

1.3 Counters

Since views are at their core just descriptions of a GUI, it’s easy to abstract over them and make them reusable.

(define (counter @count action)
  (hpanel
   (button "-" (λ () (action sub1)))
   (text (@count . ~> . number->string))
   (button "+" (λ () (action add1)))))
 
(define @counter-1 (@ 0))
(define @counter-2 (@ 0))
 
(render
 (window
  (vpanel
   (counter @counter-1 (λ (proc) (@counter-1 . <~ . proc)))
   (counter @counter-2 (λ (proc) (@counter-2 . <~ . proc))))))

1.4 Dynamic Counters

Taking the previous example further, we can render a dynamic list of counters.

(define @counters (@ '((0 . 0))))
 
(define (append-counter counts)
  (define next-id (add1 (apply max (map car counts))))
  (append counts `((,next-id . 0))))
 
(define (update-count counts k proc)
  (for/list ([entry (in-list counts)])
    (if (eq? (car entry) k)
        (cons k (proc (cdr entry)))
        entry)))
 
(define (counter @count action)
  (hpanel
   #:stretch '(#t #f)
   (button "-" (λ () (action sub1)))
   (text (@count . ~> . number->string))
   (button "+" (λ () (action add1)))))
 
(render
 (window
  #:size '(#f 200)
  (vpanel
   (hpanel
    #:alignment '(center top)
    #:stretch '(#t #f)
    (button "Add counter" (λ () (@counters . <~ . append-counter))))
   (list-view
    @counters
    #:key car
    (λ (k @entry)
      (counter
       (@entry . ~> . cdr)
       (λ (proc)
         (@counters . <~ . (λ (counts) (update-count counts k proc))))))))))

Here the @counters observable holds a list of pairs where the first element of a pair is the id of each counter and the second is its count. When the “Add counter” button is clicked, a new counter is added to the list. The list-view renders each individual counter by passing in a derived observable to its make-view argument.

1.5 More

For more examples, see the "examples" directory in the Git repository.

2 Geometry Management

See Geometry Management in the racket/gui docs for details on how views get laid out.

Containers, Windows & Dialogs take optional keyword arguments that allow you to control the #:spacing and #:alignment of their children and their own #:min-size, #:stretch and #:margin. All of these arguments can be passed as either regular values or as observables, in which case the properties they control will vary with changes to the observables.

3 Custom Views

You can create your own views by implementing the view<%> interface.

As an example, let’s wrap Jeffrey Massung’s canvas-list<%>. I find it helps to work backwards from the API you’d like to end up with. In this case, that would be:

(canvas-list
 @entries
 (λ (item state dc w h)
   (draw-item ...))
 (λ (item)
   (printf "double-clicked ~s~n" item)))

A canvas-list takes an observable of a list of entries, a function that knows how to draw each entry to a gui:dc<%> and a callback for when the user double-clicks an entry. The canvas-list function should then look something like this:

(define (canvas-list @entries draw [action void])
  (new canvas-list-view%
       [@entries @entries]
       [draw draw]
       [action action]))

All it needs to do is abstract over the instantiation of the underlying view<%>. Next, we can define a skeleton implementation of canvas-list-view%:

(define canvas-list-view%
  (class* object% (view<%>)
    (init-field @entries draw action)
    (super-new)
 
    (define/public (dependencies)
      (error 'create "not implemented"))
 
    (define/public (create parent)
      (error 'create "not implemented"))
 
    (define/public (update v what val)
      (error 'update "not implemented"))
 
    (define/public (destroy v)
      (error 'destroy "not implemented"))))

Views must communicate what observables they depend on to their parents. Since the only dependency a canvas list has is its set of entries, that’s straightforward:

(define canvas-list-view%
  (class* object% (view<%>)
    ...
 
    (define/public (dependencies)
      (list @entries))
 
    ...))

When a view is rendered, its parent is in charge of calling its create method. That method must instantiate a GUI object, associate it with the passed-in parent, perform any initialization steps and then return it. In our case:

(define canvas-list-view%
  (class* object% (view<%>)
    ...
 
    (define/public (create parent)
      (new canvas-list%
           [parent parent]
           [items (obs-peek @entries)]
           [paint-item-callback (λ (self entry state dc w h)
                                  (draw entry state dc w h))]
           [action-callback (λ (self item event)
                              (action item))]))
 
    ...))

When the observables the view depends on change, its parent will call its update method with the GUI object that the view returned from its create method, the observable that changed and the observable’s value when it changed. The view is then in charge of modifying its GUI object appropriately.

(define canvas-list-view%
  (class* object% (view<%>)
    ...
 
    (define/public (update v what val)
      (case/dep what
        [@entries (send v set-items val)]))
 
    ...))

Finally, when a view is no longer visible, its destroy method is called to dispose of the GUI object and perform any teardown actions. In our case, there’s nothing to tear down so we can let garbage collection take care of destroying the canvas-list% object:

(define canvas-list-view%
  (class* object% (view<%>)
    ...
 
  (define/public (destroy v)
    (void))))

When the view becomes visible again, its create method will be called again and the whole cycle will repeat itself.

That’s all there is to it whe it comes to custom controls. See the “hn.rkt” example for a program that uses a custom view.

3.1 Custom Containers

Containers are slightly more complicated to implement than controls. They must collect all their children’s unique dependencies and list them in their dependencies method. Additionally, their update method is in charge of dispatching updates to their children.

See “gui-easy-lib/gui/easy/private/view/panel.rkt” for an example.

4 Escape Hatches

Some views take a #:mixin argument that can be used to alter the behavior of the underlying widget. These are intended to be used as “escape hatches” when the library doesn’t provide a piece of functionality you need, but that functionality is available on the native widget.

See "examples/close-window.rkt" for a example of using a mixin to programmatically toggle a window’s visibility.

5 Reference

 (require racket/gui/easy) package: gui-easy-lib

5.1 Renderers

Renderers convert view definitions to GUI elements.

procedure

(renderer? v)  boolean?

  v : any/c
Returns #t if v is a renderer.

procedure

(embed parent view)  renderer?

  parent : (is-a?/c gui:area<%>)
  view : (is-a?/c view<%>)
Renders the view hierarchy represented by view as a child of parent.

Use this function when you need to embed one or more view<%>s within an existing racket/gui application. Otherwise, use render.

procedure

(render view [parent])  renderer?

  view : (is-a?/c window-view<%>)
  parent : (or/c #f renderer?) = #f
Renders the view hierarchy represented by view.

When a parent renderer is provided, renders the view as a child of the root view of parent. This is useful when you need to render a modal dialog on top of an existing window.

procedure

(render-popup-menu parent view x y)  void?

  parent : renderer?
  view : (is-a?/c popup-menu-view<%>)
  x : gui:position-integer?
  y : gui:position-integer?
Renders the popup menu represented by view as a child of parent.

procedure

(renderer-root r)  any/c

  r : renderer?
Returns the root widget of r. This function is handy when you need to embed a gui:top-level-window<%>. The embed function won’t show the embedded window, so you’ll need to get it and send it a show message.

5.2 Views

5.2.1 Windows & Dialogs

procedure

(window [#:title title    
  #:size size    
  #:alignment alignment    
  #:position position    
  #:min-size min-size    
  #:stretch stretch    
  #:style style    
  #:mixin mix]    
  child ...+)  (is-a?/c window-view<%>)
  title : (maybe-obs/c string?) = "Untitled"
  size : (maybe-obs/c size/c) = '(#f #f)
  alignment : (maybe-obs/c alignment/c) = '(center top)
  position : (maybe-obs/c position/c) = 'center
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  style : 
(listof (or/c 'no-resize-border 'no-caption
              'no-system-menu 'hide-menu-bar
              'toolbar-button 'float 'metal
              'fullscreen-button 'fullscreen-aux))
   = null
  mix : (make-mixin-contract gui:frame%) = values
  child : (is-a?/c view<%>)
Returns a representation of a top-level window.

procedure

(dialog [#:title title    
  #:size size    
  #:alignment alignment    
  #:position position    
  #:min-size min-size    
  #:stretch stretch    
  #:style style    
  #:mixin mix]    
  child ...+)  (is-a?/c window-view<%>)
  title : (maybe-obs/c string?) = "Untitled"
  size : (maybe-obs/c size/c) = '(#f #f)
  alignment : (maybe-obs/c alignment/c) = '(center top)
  position : (maybe-obs/c position/c) = 'center
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  style : (listof (or/c 'no-caption 'no-sheet 'resize-border 'close-button))
   = '(close-button)
  mix : (make-mixin-contract gui:dialog%) = values
  child : (is-a?/c view<%>)
Returns a representation of a dialog.

5.2.2 Menus & Menu Items

procedure

(popup-menu menu)  (is-a?/c popup-menu-view<%>)

  menu : (is-a?/c view<%>)
Returns a representation of a popup menu. Popup menus are rendered using render-popup-menu.

procedure

(menu-bar menu)  (is-a?/c view<%>)

  menu : (is-a?/c view<%>)
Returns a representation of a menu-bar menu.

procedure

(menu label item ...)  (is-a?/c view<%>)

  label : (maybe-obs/c maybe-label/c)
  item : (is-a?/c view<%>)
Returns a representation of a menu with items as children.

procedure

(menu-item label [action])  (is-a?/c view<%>)

  label : (maybe-obs/c maybe-label/c)
  action : (-> any) = void
Returns a representation of a menu item that calls action when clicked.

procedure

(menu-item-separator)  (is-a?/c view<%>)

Returns a representation of a menu item separator.

5.2.3 Containers

procedure

(hpanel [#:alignment alignment    
  #:style style    
  #:enabled? enabled?    
  #:spacing spacing    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch]    
  child ...+)  (is-a?/c view<%>)
  alignment : (maybe-obs/c alignment/c) = '(center top)
  style : 
(listof (or/c 'border 'deleted
              'hscroll 'auto-hscroll 'hide-hscroll
              'vscroll 'auto-vscroll 'hide-vscroll))
   = null
  enabled? : (maybe-obs/c boolean?) = #t
  spacing : (maybe-obs/c spacing/c) = 0
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  child : (is-a?/c view<%>)
Returns a representation of a panel that lays out its children horizontally.

procedure

(vpanel [#:alignment alignment    
  #:style style    
  #:enabled? enabled?    
  #:spacing spacing    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch]    
  child ...+)  (is-a?/c view<%>)
  alignment : (maybe-obs/c alignment/c) = '(center top)
  style : 
(listof (or/c 'border 'deleted
              'hscroll 'auto-hscroll 'hide-hscroll
              'vscroll 'auto-vscroll 'hide-vscroll))
   = null
  enabled? : (maybe-obs/c boolean?) = #t
  spacing : (maybe-obs/c spacing/c) = 0
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  child : (is-a?/c view<%>)
Returns a representation of a panel that lays out its children vertically.

procedure

(group label    
  [#:alignment alignment    
  #:style style    
  #:enabled? enabled?    
  #:spacing spacing    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch]    
  child ...+)  (is-a?/c view<%>)
  label : (maybe-obs/c gui:label-string?)
  alignment : (maybe-obs/c alignment/c) = '(center top)
  style : 
(listof (or/c 'border 'deleted
              'hscroll 'auto-hscroll 'hide-hscroll
              'vscroll 'auto-vscroll 'hide-vscroll))
   = null
  enabled? : (maybe-obs/c boolean?) = #t
  spacing : (maybe-obs/c spacing/c) = 0
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  child : (is-a?/c view<%>)
Returns a representation of a labeled vertical panel.

procedure

(tabs choices    
  action    
  child ...    
  [#:choice->label choice->label    
  #:choice=? choice=?    
  #:selection selection    
  #:alignment alignment    
  #:enabled? enabled?    
  #:style style    
  #:spacing spacing    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch])  (is-a?/c view<%>)
  choices : (maybe-obs/c (listof any/c))
  action : 
(-> (or/c 'new 'close 'reorder 'select)
    (listof any/c)
    (or/c #f any/c)
    any)
  child : (is-a?/c view<%>)
  choice->label : (-> any/c gui:label-string?) = values
  choice=? : (-> any/c any/c boolean?) = equal?
  selection : (maybe-obs/c (or/c #f any/c)) = #f
  alignment : (maybe-obs/c alignment/c) = '(left center)
  enabled? : (maybe-obs/c boolean?) = #t
  style : 
(listof (or/c 'no-border
              'can-reorder 'can-close 'new-button
              'flat-portable 'deleted))
   = null
  spacing : (maybe-obs/c spacing/c) = 0
  margin : (maybe-obs/c margin/c) = 0
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
Returns a representation of a tab panel.

The #:choice->label argument controls how each choice is displayed and the #:choice=? argument controls how the current #:selection is compared against the list of choices to determine the currently selected tab.

On user interaction, action is called with a symbol representing the event, the set of choices at the moment the action occurred and the current selection. The selection may be adjusted depending on the event (eg. when the current tab is closed, the selection changes to an adjacent tab). When tabs are reordered, the choices provided to the action represent the new tab order.

See "examples/tabs.rkt" for an example.

Changed in version 0.3 of package gui-easy-lib: Added the #:choice=? argument.
Changed in version 0.3: The selection is now a value in the set of choices instead of an index.

syntax

(if-view cond-e then-e else-e)

 
  cond-e : (maybe-obs/c any/c)
  then-e : (is-a?/c view<%>)
  else-e : (is-a?/c view<%>)
Returns a representation of a panel that renders then-view when the current-value of cond-value is truthy and else-view otherwise.

Changed in version 0.4 of package gui-easy-lib: The if-view form was converted from a procedure into a syntactic form.

syntax

(cond-view
 [cond-e view-e] ...+
 [else view-e])
 
  cond-e : (maybe-obs/c any/c)
  view-e : (is-a?/c view<%>)
Returns a representation of a panel that renders the first view-e for which the associated cond-e’s current value is truthy.

syntax

(case-view e
 [(case-lit ...+) view-e] ...+
 [else view-e])
 
  e : (obs/c any/c)
  view-e : (is-a?/c view<%>)
Returns a representation of a panel that renders the first view-e where one of the case-lits is equal? to e’s current value.

procedure

(list-view entries    
  make-view    
  [#:key key    
  #:alignment alignment    
  #:enabled? enabled?    
  #:style style    
  #:spacing spacing    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch    
  #:mixin mix])  (is-a?/c view<%>)
  entries : (maybe-obs/c list?)
  make-view : (-> any/c any/c (is-a?/c view<%>))
  key : (-> any/c any/c) = values
  alignment : (maybe-obs/c alignment/c) = '(center top)
  enabled? : (maybe-obs/c boolean?) = #t
  style : 
(listof (or/c 'horizontal 'vertical 'border 'deleted
              'hscroll 'auto-hscroll 'hide-hscroll
              'vscroll 'auto-vscroll 'hide-vscroll))
   = null
  spacing : (maybe-obs/c spacing/c) = 0
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  mix : (make-mixin-contract gui:panel%) = values
Returns a representation of a panel that renders the entries by passing each one as a derived observable to make-view. Each entry must have a unique #:key.

5.2.4 Canvases & Snips

procedure

(canvas data    
  draw    
  [#:label label    
  #:enabled? enabled?    
  #:style style    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch    
  #:mixin mix])  (is-a?/c view<%>)
  data : (maybe-obs/c any/c)
  draw : (-> (is-a?/c gui:dc<%>) any/c any)
  label : (maybe-obs/c maybe-label/c) = #f
  enabled? : (maybe-obs/c boolean?) = #t
  style : 
(listof (or/c 'border 'control-border 'combo
              'vscroll 'hscroll 'resize-corner
              'gl 'no-autoclear 'transparent
              'no-focus 'deleted))
 = null
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  mix : (make-mixin-contract gui:canvas%) = values
Returns a representation of a canvas that is redrawn using draw whenever data changes.

procedure

(pict-canvas data    
  make-pict    
  [#:label label    
  #:enabled? enabled?    
  #:style style    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch    
  #:mixin mix])  (is-a?/c view<%>)
  data : (maybe-obs/c any/c)
  make-pict : (-> any/c pict?)
  label : (maybe-obs/c maybe-label/c) = #f
  enabled? : (maybe-obs/c boolean?) = #t
  style : 
(listof (or/c 'border 'control-border 'combo
              'vscroll 'hscroll 'resize-corner
              'gl 'no-autoclear 'transparent
              'no-focus 'deleted))
 = null
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  mix : (make-mixin-contract gui:canvas%) = values
Returns a representation of a canvas that is redrawn using the result of make-pict whenever data changes.

procedure

(snip-canvas data    
  make-snip    
  [#:label label    
  #:enabled? enabled?    
  #:style style    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch    
  #:mixin mix])  (is-a?/c view<%>)
  data : (maybe-obs/c any/c)
  make-snip : (-> any/c gui:dimension-integer? gui:dimension-integer? (is-a?/c gui:snip%))
  label : (maybe-obs/c maybe-label/c) = #f
  enabled? : (maybe-obs/c boolean?) = #t
  style : 
(listof (or/c 'border 'control-border 'combo
              'vscroll 'hscroll 'resize-corner
              'gl 'no-autoclear 'transparent
              'no-focus 'deleted))
 = null
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  mix : (make-mixin-contract gui:canvas%) = values
Returns a representation of a canvas that is redrawn using the result of make-snip whenever data changes. The snip is converted to a bitmap before being drawn to the canvas so it is non-interactive. Use this view when you want to efficiently update plots. For interactive snips, see snip.

procedure

(snip data    
  make-snip    
  [update-snip    
  #:label label    
  #:enabled? enabled?    
  #:style style    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch    
  #:mixin mix])  (is-a?/c view<%>)
  data : (maybe-obs/c any/c)
  make-snip : 
(-> any/c
    gui:dimension-integer?
    gui:dimension-integer?
    (is-a?/c gui:snip%))
  update-snip : (-> (is-a?/c gui:snip%) any/c any) = void
  label : (maybe-obs/c maybe-label/c) = #f
  enabled? : (maybe-obs/c boolean?) = #t
  style : 
(listof (or/c 'no-border 'control-border 'combo
              'resize-corner 'no-focus 'deleted
              'transparent))
 = null
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  mix : (make-mixin-contract mrlib:snip-canvas%) = values
Returns the representation of an editor that holds a snip generated via make-snip. The snip may be updated whenever data changes via update-snip.

5.2.5 Controls

procedure

(button label    
  action    
  [#:enabled? enabled?    
  #:style style    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch])  (is-a?/c view<%>)
  label : (maybe-obs/c gui:label-string?)
  action : (-> any)
  enabled? : (maybe-obs/c boolean?) = #t
  style : (listof (or/c 'border 'multi-line 'deleted)) = null
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
Returns a representation of a button that calls action when clicked.

procedure

(checkbox action    
  [#:label label    
  #:checked? checked?    
  #:enabled? enabled?])  (is-a?/c view<%>)
  action : (-> boolean? any)
  label : (maybe-obs/c gui:label-string?) = #f
  checked? : (maybe-obs/c boolean?) = #f
  enabled? : (maybe-obs/c boolean?) = #f
Returns a representation of a checkbox that calls action when toggled.

procedure

(choice choices    
  action    
  [#:choice->label choice->label    
  #:choice=? choice=?    
  #:selection selection    
  #:label label    
  #:style style    
  #:enabled? enabled?    
  #:min-size min-size    
  #:stretch stretch])  (is-a?/c view<%>)
  choices : (maybe-obs/c (listof any/c))
  action : (-> (or/c #f any/c) any)
  choice->label : (-> any/c gui:label-string?) = values
  choice=? : (-> any/c any/c boolean?) = equal?
  selection : (maybe-obs/c any/c) = #f
  label : (maybe-obs/c maybe-label/c) = #f
  style : (listof (or/c 'horizontal-label 'vertical-label 'deleted))
   = null
  enabled? : (maybe-obs/c boolean?) = #t
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
Returns a representation of a choice widget that calls action whenever the current selection changes.

The #:choice->label argument controls how each choice is displayed and the #:choice=? argument controls how the current #:selection is compared against the list of choices to determine the selection index.

procedure

(image path [#:size size #:mode mode])  (is-a?/c view<%>)

  path : (maybe-obs/c path-string?)
  size : (maybe-obs/c size/c) = '(#f #f)
  mode : (maybe-obs/c (or/c 'fit 'fill)) = 'fit
Returns a representation of an image.

The #:mode argument controls how the image stretches to fill its container. If the mode is 'fit, then the image will preserve its aspect ratio, otherwise it will stretch to fill the container.

procedure

(input value    
  [action    
  #:label label    
  #:enabled? enabled?    
  #:background-color background-color    
  #:style style    
  #:font font    
  #:keymap keymap    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch    
  #:mixin mix    
  #:value=? value=?    
  #:value->text value->text])  (is-a?/c view<%>)
  value : (maybe-obs/c any/c)
  action : (-> (or/c 'input 'return) string? any) = void
  label : (maybe-obs/c maybe-label/c) = #f
  enabled? : (maybe-obs/c boolean?) = #t
  background-color : (maybe-obs/c (or/c #f (is-a?/c gui:color%)))
   = #f
  style : 
(listof (or/c 'single 'multiple 'hscroll 'password
              'vertical-label 'horizontal-label
              'deleted))
   = '(single)
  font : (is-a?/c gui:font%) = gui:normal-control-font
  keymap : (is-a?/c gui:keymap%) = (new gui:keymap%)
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  mix : (make-mixin-contract gui:text-field%) = values
  value=? : (-> any/c any/c boolean?) = equal?
  value->text : (-> any/c string?) = values
Returns a representation of a text field that calls action on change. The first argument to the action is the type of event that caused the input to change and the second is the contents of the text field.

The #:value=? argument controls when changes to the input data are reflected in the contents of the field. The contents of the input field only change when the new value of the underlying observable is not value=? to the previous one. The only exception to this is when the textual value (via #:value->text) of the observable is the empty string, in which case the input is cleared regardless of the value of the underlying observable.

The #:value->text argument controls how the input values are rendered to strings. If not provided, value must be either a string? or an observable of strings.

procedure

(progress value    
  [#:label label    
  #:enabled? enabled?    
  #:style style    
  #:range range    
  #:min-size min-size    
  #:stretch stretch])  (is-a?/c view<%>)
  value : (maybe-obs/c gui:position-integer?)
  label : (maybe-obs/c maybe-label/c) = #f
  enabled? : (maybe-obs/c boolean?) = #t
  style : 
(listof (or/c 'horizontal 'vertical 'plain
              'vertical-label 'horizontal-label
              'deleted))
   = '(horizontal)
  range : (maybe-obs/c gui:positive-dimension-integer?) = 100
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c)
   = 
(list (memq 'horizontal style)
      (memq 'vertical   style))
Returns a representation of a progress bar.

procedure

(radios choices    
  action    
  [#:choice->label choice->label    
  #:choice=? choice=?    
  #:selection selection    
  #:label label    
  #:style style    
  #:enabled? enabled?    
  #:min-size min-size    
  #:stretch stretch])  (is-a?/c view<%>)
  choices : (listof any/c)
  action : (-> (or/c #f any/c) any)
  choice->label : (-> any/c gui:label-string?) = values
  choice=? : (-> any/c any/c boolean?) = equal?
  selection : (maybe-obs/c any/c) = #f
  label : (maybe-obs/c maybe-label/c) = #f
  style : (listof (or/c 'horizontal-label 'vertical-label 'deleted))
   = null
  enabled? : (maybe-obs/c boolean?) = #t
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
Returns a representation of a radio box widget that calls action whenever the current selection changes.

The #:choice->label argument controls how each choice is displayed and the #:choice=? argument controls how the current #:selection is compared against the list of choices to determine the selection index.

Unlike choice, the set of choices cannot be changed.

procedure

(slider value    
  action    
  [#:label label    
  #:enabled? enabled?    
  #:style style    
  #:min-value min-value    
  #:max-value max-value    
  #:min-size min-size    
  #:stretch stretch])  (is-a?/c view<%>)
  value : (maybe-obs/c gui:position-integer?)
  action : (-> gui:position-integer? any)
  label : (maybe-obs/c maybe-label/c) = #f
  enabled? : (maybe-obs/c boolean?) = #t
  style : 
(listof (or/c 'horizontal 'vertical 'plain
              'vertical-label 'horizontal-label
              'deleted))
   = '(horizontal)
  min-value : gui:position-integer? = 0
  max-value : gui:position-integer? = 100
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c)
   = 
(list (memq 'horizontal style)
      (memq 'vertical   style))
Returns a representation of a slider that calls the action on change.

procedure

(spacer)  (is-a?/c view<%>)

Returns a representation of a spacer. Spacers extend to fill the space of their parents.

procedure

(table columns    
  entries    
  [action    
  #:entry->row entry->row    
  #:selection selection    
  #:label label    
  #:enabled? enabled?    
  #:style style    
  #:font font    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch    
  #:column-widths column-widths])  (is-a?/c view<%>)
  columns : (listof gui:label-string?)
  entries : (maybe-obs/c vector?)
  action : 
(-> (or/c 'select 'dclick 'column)
    vector?
    (or/c #f
          exact-nonnegative-integer?
          (listof exact-nonnegative-integer?))
    any)
 = void
  entry->row : (-> any/c vector?) = values
  selection : 
(maybe-obs/c
 (or/c #f
       exact-nonnegative-integer?
       (listof exact-nonnegative-integer?)))
 = #f
  label : (maybe-obs/c maybe-label/c) = #f
  enabled? : (maybe-obs/c boolean?) = #t
  style : 
(listof (or/c 'single 'multiple 'extended
              'vertical-label 'horizontal-label
              'variable-columns 'column-headers
              'clickable-headers 'reorderable-headers
              'deleted))
   = '(single columnn-headers clickable-headers reorderable-headers)
  font : (is-a?/c gui:font%) = gui:view-control-font
  margin : (maybe-obs/c margin/c) = '(0 0)
  min-size : (maybe-obs/c size/c) = '(#f #f)
  stretch : (maybe-obs/c stretch/c) = '(#t #t)
  column-widths : 
(maybe-obs/c
 (listof
  (or/c (list/c exact-nonnegative-integer?
                gui:dimension-integer?)
        (list/c exact-nonnegative-integer?
                gui:dimension-integer?
                gui:dimension-integer?
                gui:dimension-integer?))))
   = null
Returns a representation of a table that calls action when the selection changes or when one of its columns is clicked (if the 'clickable-headers style is set). The action callback is called with the type of event that occurred, the set of entries at the time of the event and the current selection, if any. The current selection can either be a single index in the set of entries or a list of indices in the case of a 'multiple selection table.

The #:entry->row argument converts each row in the input data for display in the table.

The #:column-widths argument controls the widths of the columns. Column lengths can be specified either as a list of the column index (starting from 0) and the default width or a list of the column index, the column width, the minimum width and the maximum width.

procedure

(text s [#:color color #:font font])  (is-a?/c view<%>)

  s : (maybe-obs/c gui:label-string?)
  color : (maybe-obs/c (or/c #f string? (is-a?/c gui:color%)))
   = #f
  font : (is-a?/c gui:font%) = gui:normal-control-font
Returns a representation of a textual label.

5.2.6 Interfaces
5.2.6.1 view<%>

interface

view<%> : interface?

A view<%> object is a wrapper around a GUI object that knows what its data dependecies are and how to respond to their changes.

method

(send a-view dependencies)  (listof obs?)

Returns the set of observers that this view depends on.

method

(send a-view create parent)  (is-a?/c gui:area<%>)

  parent : (is-a?/c gui:area-container<%>)
Instantiates the underlying GUI object, associates it with parent and returns it so that the parent of this view<%> can manage it.

method

(send a-view update v dep val)  void?

  v : (is-a?/c gui:area<%>)
  dep : obs?
  val : any/c
Responds to a change to the contents of dep. The val argument is the most recent value of dep and the v argument is the GUI object created by create.

method

(send a-view destroy v)  void?

  v : (is-a?/c gui:area<%>)
Destroys the GUI object v and performs any necessary cleanup.

5.2.6.2 window-view<%>

interface

window-view<%> : interface?

  implements: view<%>
A window-view<%> is like a regular view<%> but its create method has additional constraints placed on it.

method

(send a-window-view create parent)

  (is-a?/c gui:top-level-window<%>)
  parent : 
(or/c (is-a?/c gui:frame%)
      (is-a?/c gui:dialog%)
      #f)
Returns a new gui:top-level-window<%> belonging to parent.

method

(send a-window-view is-dialog?)  boolean?

Returns #t if this view is a dialog.

5.2.6.3 popup-menu-view<%>

interface

popup-menu-view<%> : interface?

  implements: view<%>
A popup-menu-view<%> is like a regular view<%> but its create method has additional constraints placed on it.

method

(send a-popup-menu-view create parent)

  (is-a?/c gui:popup-menu%)
  parent : #f
Returns a new gui:popup-menu%.

5.3 Observables

Observables are containers for values that may change over time. Their changes may be observed by arbitrary functions.

Examples:
> (define @ints (obs 0))
> (obs-observe! @ints (λ (v) (printf "observer 1 got ~s~n" v)))
> (obs-observe! @ints (λ (v) (printf "observer 2 got ~s~n" v)))
> (obs-update! @ints add1)

observer 1 got 1

observer 2 got 1

1

Derived observables are observables whose values depend on other observables. Derived observables cannot be updated using obs-update!.

Examples:
> (define @strs (obs-map @ints number->string))
> @strs

(obs "1" #:name 'anon #:derived? #t)

> (obs-update! @strs add1)

obs-update!: contract violation

  expected: (not/c obs-derived?)

  given: (obs "1" #:name 'anon #:derived? #t)

Internally, every observable has a unique handle and two observables are equal? when their handles are eq?. This means that equality (via equal?) is preserved for impersonated observables, such as those guarded by obs/c.

procedure

(obs? v)  boolean?

  v : any/c
Returns #t when v is an observable.

procedure

(obs v [#:name name #:derived? derived?])  obs?

  v : any/c
  name : symbol? = 'anon
  derived? : boolean? = #f
Returns a new observable, whose initial value is v.

The #:name of an observable is visible when the observable is printed so using a custom name can come in handy while debugging code.

The #:derived? argument controls whether or not the observable may be updated.

procedure

(obs-rename o name)  obs?

  o : obs?
  name : symbol?
Returns an impersonator of o whose name is changed to name.

procedure

(obs-observe! o f)  void?

  o : obs?
  f : (-> any/c any/c)
Registers f as an observer of o, applying it to the value contained by o every time it changes.

procedure

(obs-unobserve! o f)  void?

  o : obs?
  f : (-> any/c any/c)
Removes f from o’s set of observers.

procedure

(obs-update! o f)  any/c

  o : obs?
  f : (-> any/c any/c)
Updates the value within o by applying f to it and storing the result. Returns the new value. If o is a derived observable, raises an exn:fail:contract? error.

procedure

(obs-peek o)  any/c

  o : obs?
Returns the current value contained within o.

procedure

(obs-map o f)  obs?

  o : obs?
  f : (-> any/c any/c)
Returns a new derived observable whose value changes every time o’s value does. The values held by the new observable are mapped via f.

procedure

(obs-combine f o ...+)  obs?

  f : (-> any/c ...+ any/c)
  o : obs?
Returns a new derived observable whose value changes every time one of the os change. The values held by the new observable are the values of the os combined via f.

This combinator retains a strong reference to each of the last values of the respective observables that are being combined until they change.

procedure

(obs-debounce o [#:duration duration-ms])  obs?

  o : obs?
  duration-ms : exact-nonnegative-integer? = 200
Returns a new derived observable based on o, whose value changes when there is at least a duration-ms millisecond pause in changes to o.

procedure

(obs-throttle o [#:duration duration-ms])  obs?

  o : obs?
  duration-ms : exact-nonnegative-integer? = 200
Returns a new derived observable based on o, whose values change at most once every duration-ms milliseconds.

5.4 View Helpers

syntax

(case/dep what-expr
 [dep-expr body ...] ...+)
 
  what-expr : obs?
  dep-expr : obs?
Executes the body of the first clause body whose dep-expr is equal? to what-expr. Logs the dep-expr that matched to the 'gui-easy topic. Use this form to implement update methods.

5.5 Observable Operators

 (require racket/gui/easy/operator)
  package: gui-easy-lib

syntax

(define/obs name init-expr)

Binds name to an observable whose initial value is init-expr and whose name is 'name. If init-expr is already an observable, then it is locally renamed to 'name then bound to name.

procedure

(@ v)  obs?

  v : any/c
Converts v into an observable. If v is already an observable, it is returned unchanged.

procedure

(:= o v)  any/c

  o : obs?
  v : any/c
Changes the value of o to v.

procedure

((λ:= o [f]) v)  any/c

  o : obs?
  f : (-> any/c any/c) = values
  v : any/c
Changes the value of o to the result of (f v).

procedure

(<~ o f)  any/c

  o : obs?
  f : (-> any/c any/c)
An alias for obs-update!.

procedure

(~> o f)  obs?

  o : obs?
  f : (-> any/c any/c)
An alias for obs-map.

procedure

(λ<~ o f)  (-> any/c)

  o : obs?
  f : (-> any/c any/c)
Returns a function that updates o using f when applied.

5.6 Contracts

 (require racket/gui/easy/contract)
  package: gui-easy-lib

value

alignment/c : 
(list/c (or/c 'left 'center 'right)
        (or/c 'top  'center 'bottom))
The contract for container child alignment. Represents the horizontal and vertical alignment, respectively.

The contract for margins. Represents the horizontal and vertical margin, respectively.

The contract for optional labels.

value

position/c : 
(or/c 'center (list/c gui:position-integer?
                      gui:position-integer?))
The contract for positions. The first places windows and dialogs in the center of the screen.

value

size/c : 
(list/c (or/c #f gui:dimension-integer?)
        (or/c #f gui:dimension-integer?))
The contract for sizes. Represents the width and height, respectively. If either value is false, the view is allowed to stretch in that direction.

The contract for spacing.

The contract for stretch values. Represents whether or not a view can stretch horizontally and vertically, respectively.

procedure

(obs/c c)  contract?

  c : contract?
Returns a contract that accepts an obs? whose values conform to c. Checks the initial value of the observable as well as all subsequent updated values.

procedure

(maybe-obs/c c)  contract?

  c : contract?
A shorthand for (or/c c (obs/c c)).