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
renderer-destroy
5.2 Views
5.2.1 Windows & Dialogs
window
dialog
5.2.2 Menus & Menu Items
popup-menu
menu-bar
menu
menu-item
checkable-menu-item
menu-item-separator
5.2.3 Containers
hpanel
vpanel
group
tabs
if-view
cond-view
case-view
list-view
observable-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 Combinators
add-hooks
5.2.7 Interfaces
5.2.7.1 view<%>
view<%>
dependencies
create
update
destroy
5.2.7.2 window-view<%>
window-view<%>
create
is-dialog?
5.2.7.3 popup-menu-view<%>
popup-menu-view<%>
create
5.2.7.4 context<%>
context-mixin
context<%>
set-context
set-context*
get-context
get-context!
remove-context
clear-context
5.3 Observables
obs?
obs
obs-rename
obs-observe!
obs-unobserve!
obs-update!
obs-set!
obs-peek
obs-map
obs-filter-map
obs-filter
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.12

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)]))
 
    ...))

Windows are a special case: the resources they manage only get disposed of when renderer-destroy is called, or when the program exits.

Finally, when a view is no longer visible, its destroy method is typically 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 when 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.

procedure

(renderer-destroy r)  void?

  r : renderer?
Destroys the render tree managed by r.

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-or-item ...)  (is-a?/c popup-menu-view<%>)

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

(popup-menu
 (menu
  "File"
  (menu-item "Open...")
  (menu-item "Save"))
 (menu-item-separator)
 (menu-item "Quit"))

procedure

(menu-bar [#:enabled? enabled?]    
  menu-or-item ...)  (is-a?/c view<%>)
  enabled? : (maybe-obs/c any/c) = #t
  menu-or-item : (is-a?/c view<%>)
Returns a representation of a menu-bar menu.

(menu-bar
 (menu
  "File"
  (menu-item "Open...")
  (menu-item "Save")
  (menu-item-separator)
  (menu-item "Quit"))
 (menu
  "Help"
  (menu-item "Getting Started")))

Changed in version 0.15 of package gui-easy-lib: The #:enabled? argument.

procedure

(menu label    
  [#:enabled? enabled?    
  #:help help-text]    
  item ...)  (is-a?/c view<%>)
  label : (maybe-obs/c maybe-label/c)
  enabled? : (maybe-obs/c any/c) = #t
  help-text : (maybe-obs/c (or/c #f string?)) = #f
  item : (is-a?/c view<%>)
Returns a representation of a menu with items as children.

Changed in version 0.15 of package gui-easy-lib: The #:enabled? and #:help arguments.

procedure

(menu-item label    
  [action    
  #:enabled? enabled?    
  #:help help-text    
  #:shortcut shortcut])  (is-a?/c view<%>)
  label : (maybe-obs/c maybe-label/c)
  action : (-> any) = void
  enabled? : (maybe-obs/c any/c) = #t
  help-text : (maybe-obs/c (or/c #f string?)) = #f
  shortcut : 
(maybe-obs/c (or/c #f (*list/c
                       (or/c 'alt 'cmd 'meta 'ctl 'shift 'option)
                       (or/c 'alt 'cmd 'meta 'ctl 'shift 'option)
                       (or/c char? symbol?))))
   = #f
Returns a representation of a menu item that calls action when clicked.

Changed in version 0.15 of package gui-easy-lib: The #:enabled?, #:help and #:shortcut arguments.

procedure

(checkable-menu-item label    
  [action    
  #:checked? checked?    
  #:enabled? enabled?    
  #:help help-text    
  #:shortcut shortcut])  (is-a?/c view<%>)
  label : (maybe-obs/c maybe-label/c)
  action : (-> boolean? any) = void
  checked? : (maybe-obs/c any/c) = #f
  enabled? : (maybe-obs/c any/c) = #t
  help-text : (maybe-obs/c (or/c #f string?)) = #f
  shortcut : 
(maybe-obs/c (or/c #f (*list/c
                       (or/c 'alt 'cmd 'meta 'ctl 'shift 'option)
                       (or/c 'alt 'cmd 'meta 'ctl 'shift 'option)
                       (or/c char? symbol?))))
   = #f
Returns a representation of a menu item with a checkbox. The action callback is called with the current checked state when the menu item is clicked. Use #:checked? to set or update the checkbox programmatically.

Added in version 0.18 of package gui-easy-lib.

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    
  #:mixin mix]    
  child ...+)  (is-a?/c view<%>)
  alignment : (maybe-obs/c alignment/c) = '(left center)
  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)
  mix : (make-mixin-contract gui:panel%) = values
  child : (is-a?/c view<%>)
Returns a representation of a panel that lays out its children horizontally.

Changed in version 0.13 of package gui-easy-lib: Added the #:mixin argument.

procedure

(vpanel [#:alignment alignment    
  #:style style    
  #:enabled? enabled?    
  #:spacing spacing    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch    
  #:mixin mix]    
  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)
  mix : (make-mixin-contract gui:panel%) = values
  child : (is-a?/c view<%>)
Returns a representation of a panel that lays out its children vertically.

Changed in version 0.13 of package gui-easy-lib: Added the #:mixin argument.

procedure

(group label    
  [#:alignment alignment    
  #:style style    
  #:enabled? enabled?    
  #:spacing spacing    
  #:margin margin    
  #:min-size min-size    
  #:stretch stretch    
  #:mixin mix]    
  child ...+)  (is-a?/c view<%>)
  label : (maybe-obs/c gui:label-string?)
  alignment : (maybe-obs/c alignment/c) = '(center top)
  style : (listof (or/c 'deleted)) = 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)
  mix : (make-mixin-contract gui:panel%) = values
  child : (is-a?/c view<%>)
Returns a representation of a labeled vertical panel.

Changed in version 0.13 of package gui-easy-lib: Added the #:mixin argument.

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-e when the current-value of cond-e is truthy and else-e 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))
   = '(vertical auto-vscroll)
  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.

procedure

(observable-view data    
  [make-view    
  #:equal? equal?-proc])  (is-a?/c view<%>)
  data : (obs/c any/c)
  make-view : (-> any/c (is-a?/c view<%>)) = values
  equal?-proc : (-> any/c any/c boolean?) = equal?
Returns a representation of a pane whose content is the result of applying make-view to the value of data. The content of the pane changes every time data changes and its current value is not equal (according to equal?-proc) to the previous value. The pane automatically adjusts its area properties when its child’s area properties change to match.

Added in version 0.9 of package gui-easy-lib.

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
 (or/c gui:label-string?
       (is-a?/c gui:bitmap%)
       (list/c (is-a?/c gui:bitmap%)
               gui:label-string?
               (or/c 'left 'top 'right 'bottom))))
  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-or-bitmap    
  [#:size size    
  #:mode mode])  (is-a?/c view<%>)
  path-or-bitmap : (maybe-obs/c (or/c path-string? (is-a?/c gui:bitmap%)))
  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.

Changed in version 0.11.1 of package gui-easy-lib: The canvas background is now 'transparent. Now passes #t to the #:try-@2x? argument of gui:read-bitmap.
Changed in version 0.17: The first argument may now be a gui:bitmap%.

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    
  #:mixin mix])  (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
  mix : (make-mixin-contract gui:list-box%) = values
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.

Changed in version 0.13 of package gui-easy-lib: Added the #:mixin argument.

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 Combinators🔗

procedure

(add-hooks [#:on-create create-proc    
  #:on-destroy destroy-proc]    
  v)  (is-a?/c view<%>)
  create-proc : (-> any) = void
  destroy-proc : (-> any) = void
  v : (is-a?/c view<%>)
Returns a proxy of v that calls create-proc and destroy-proc when a GUI widget is created and destroyed, respectively, from the view.

Added in version 0.14 of package gui-easy-lib.

5.2.7 Interfaces🔗
5.2.7.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.
A single view<%> object may be used to manage multiple GUI widgets. Consequently, when implementing custom views, it’s best not to store any state within the view object itself. Instead, associate any internal state with the GUI widgets returned by create, possibly via context-mixin.

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.7.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.7.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.2.7.4 context<%>🔗

mixin

context-mixin : (class? . -> . class?)

  argument extends/implements: view<%>
  result implements: context<%>
Specializes a class to implement the context<%> interface. Compares keys using eq?.

Examples:
> (define ob (new (context-mixin object%)))
> (send ob set-context 'a 42)
> (send ob get-context 'a)

42

interface

context<%> : interface?

A context<%> object allows the user of an object to associate arbitrary values with it. Many of the view<%>s implemented by this library wrap their underlying GUI widgets using context-mixin in order to associate internal state with them.

method

(send a-context set-context k v)  void?

  k : any/c
  v : any/c
Stores v under k within the context, overriding any existing values.

method

(send a-context set-context* k v ... ...)  void?

  k : any/c
  v : any/c
Stores each v under each k within the context.

method

(send a-context get-context k [default])  any/c

  k : any/c
  default : any/c
   = (λ () (error 'get-context "no entry for ~a" k))
Returns the value stored under k from the context. If there is no value, the result is determined by default:

  • If default is a procedure?, it is called with no arguments to produce a result.

  • Otherwise, default is returned unchanged.

method

(send a-context get-context! k default)  any/c

  k : any/c
  default : any/c
Like get-context, but if there is no value stored under k, the default value is computed as in get-context, stored in the context under k and then returned.

method

(send a-context remove-context k)  void?

  k : any/c
Removes the value stored under k from the context.

method

(send a-context clear-context)  void?

Removes all stored values from the context.

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-set! o v)  void?

  o : obs?
  v : any/c
Sets the value of o to v. Equivalent to (void (obs-update! o (λ (old-v) v))).

Added in version 0.16 of package gui-easy-lib.

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-filter-map o p [d])  obs?

  o : obs?
  p : (-> any/c any/c)
  d : any/c = #f
Returns a new derived observable that applies p to every new value of o. The derived observable updates when the result of applying p to the new value of o is not #f. The initial value of the derived observable is (or (p (obs-peek o)) d).

Added in version 0.11 of package gui-easy-lib.

procedure

(obs-filter o p [d])  obs?

  o : obs?
  p : (-> any/c any/c)
  d : any/c = #f
Equivalent to (obs-filter-map o (λ (v) (and (p v) v)) d).

Added in version 0.11 of package gui-easy-lib.

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 p [d])  obs?

  o : obs?
  p : (-> any/c any/c)
  d : any/c = #f
An alias for obs-filter.

Added in version 0.11 of package gui-easy-lib.

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)).