LISP GUI Examples

I recently ran across Matthew D. Miller's "Survey of the State of GUI Programming in Lisp" series on implementing a small GUI application across various LISP implementations. The first article in that series uses racket/gui, so I figured I'd take a stab at porting that implementation to gui-easy. You can find my port here.

Porting the code was straightforward, but it uncovered a common problem with bidirectional inputs: updating the field's value observable on every change meant that the text (and cursor position) changed as the user typed because every change would trigger an update (and thus a re-rendering of the text) to the underlying text field. To work around those sorts of problems, I introduced the #:value=? and #:value->text arguments in commit ce190608. Input views with a #:value=? function only re-render the text field's contents when the current value of the input observable is different (according to the #:value=? function) from the previous one. This means that you can use that argument to control whether or not partial edits end up triggering a re-rendering of the text, so instead of:

(define/obs @n 42)
(render
 (window
  #:size '(200 #f)
  (input
   (@n . ~> . number->string)
   (λ (_event text)
     (cond [(string->number text) => (λ:= @n)])))))

You can write:

(define/obs @n 42)
(render
 (window
  #:size '(200 #f)
  (input
   @n
   #:value=? =
   #:value->text number->string
   (λ (_event text)
     (cond [(string->number text) => (λ:= @n)])))))

In the first example, typing a . after 42 re-renders the text as 42.0 and places the cursor at the end. In the second, it doesn't re-render the text at all since 42.0 and 42 are =. Still, the second example isn't perfect since string->number parses 42. to 42.0 so, if you type 42.5 into the text field and then delete the 5, it will re-render the value as 42.0. You can work around this problem by avoiding partial updates in the input's action:

   (λ (_event text)
--   (cond [(string->number text) => (λ:= @n)]))
++   (unless (string-suffix? text ".")
++     (cond [(string->number text) => (λ:= @n)])))

Perhaps a better way to handle this would be to make the #:value->text argument smarter and have it pass the current text to the rendering function when it has an arity of two. That way the rendering function can decide whether or not it needs to change the text. I'll have to experiment with that.