gui-easy: Declarative GUIs
This library provides a declarative API on top of racket/gui.
1 Quickstart
1.1 Hello, World!
#lang racket/base (require racket/gui/easy) (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:
#lang racket/base (require racket/gui/easy) (render (window (text "Hello, World!")))
1.2 Counter
State in gui-easy is held by observables.
#lang racket/base (require racket/gui/easy racket/gui/easy/operator) (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.
#lang racket/base (require racket/gui/easy racket/gui/easy/operator) (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.
#lang racket/base (require racket/gui/easy racket/gui/easy/operator) (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.
Use this function when you need to embed one or more view<%>s within an existing racket/gui application. Otherwise, use render.
procedure
view : (is-a?/c window-view<%>) parent : (or/c #f renderer?) = #f
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?
procedure
(renderer-root r) → any/c
r : renderer?
procedure
(renderer-destroy r) → void?
r : renderer?
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<%>)
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<%>)
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<%>)
(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<%>)
(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<%>)
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
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
Added in version 0.18 of package gui-easy-lib.
procedure
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<%>)
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<%>)
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<%>)
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)
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<%>)
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<%>)
syntax
(case-view e [(case-lit ...+) view-e] ...+ [else view-e])
e : (obs/c any/c)
view-e : (is-a?/c view<%>)
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
See "examples/list.rkt" for an example.
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?
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
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
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
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
5.2.5 Controls
procedure
(button label action [ #:enabled? enabled? #:style style #:font font #: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 font : (is-a?/c gui:font%) = gui:normal-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)
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
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)
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
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
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))
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)
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))
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) mix : (make-mixin-contract gui:list-box%) = values
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
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
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<%>)
Added in version 0.14 of package gui-easy-lib.
5.2.7 Interfaces
5.2.7.1 view<%>
|
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.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
v : (is-a?/c gui:area<%>) Destroys the GUI object v and performs any necessary cleanup.
5.2.7.2 window-view<%>
| ||
|
method
→ (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<%>
| ||
|
method
→ (is-a?/c gui:popup-menu%) parent : #f Returns a new gui:popup-menu%.
5.2.7.4 context<%>
| ||
| ||
|
|
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.
> (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!.
> (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.
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?
Added in version 0.16 of package gui-easy-lib.
Added in version 0.11 of package gui-easy-lib.
Added in version 0.11 of package gui-easy-lib.
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
procedure
(obs-throttle o [#:duration duration-ms]) → obs?
o : obs? duration-ms : exact-nonnegative-integer? = 200
procedure
(impersonate-obs o [ #:ref ref-proc #:set set-proc]) → obs? o : obs? ref-proc : (or/c #f (-> obs? any/c any/c)) = #f set-proc : (or/c #f (-> obs? any/c any/c)) = #f
procedure
(chaperone-obs o [ #:ref ref-proc #:set set-proc]) → obs? o : obs? ref-proc : (or/c #f (-> obs? any/c any/c)) = #f set-proc : (or/c #f (-> obs? any/c any/c)) = #f
Added in version 0.19 of package gui-easy-lib.
5.4 View Helpers
5.5 Observable Operators
(require racket/gui/easy/operator) | package: gui-easy-lib |
syntax
(define/obs name init-expr)
Added in version 0.11 of package gui-easy-lib.
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))
value
maybe-label/c : (or/c #f gui:label-string?)
value
position/c :
(or/c 'center (list/c gui:position-integer? gui:position-integer?))
value
size/c :
(list/c (or/c #f gui:dimension-integer?) (or/c #f gui:dimension-integer?))
value
procedure
(maybe-obs/c c) → contract?
c : contract?