Surprisingly compact GUI programming

Today, I came across Scheme and then Racket. The latter not only sounds interesting but looks so, too. On wikipedia (Racket features - Wikipedia) I found this neat interactive GUI demo, which shows toolkit API simplicity and compactness compared to other toolkit bindings out there. Historically I have been programming GUIs using Tcl/Tk. Here, doing GUI-programming is as simple as... with Racket/gui. What I want to tell here! See the code comparison between Racket demo code and the same functionality written in Tcl/Tk:

#lang racket/gui
;; A GUI guessing game

(define secret (random 5))

(define f (new frame% [label "Guessing game"])) ; toplevel window
(define t (new message% [parent f]
               [label "Can you guess the number I'm thinking about?"]))
(define p (new horizontal-pane% [parent f]))    ; horizontal container

(define ((make-check i) btn evt)
  (message-box "." (cond [(< i secret) "Too small"]
                         [(> i secret) "Too big"]
                         [else         "Exactly!"]))
  (when (= i secret) (send f show #f)))         ; success => close window

(for ([i (in-range 10)])                        ; create all buttons
  (make-object button% (format "~a" i) p (make-check i)))

(send f show #t) ; show the window to start the application

=============== Tcl/Tk: ================

# A GUI guessing game
package require Tk                    ;# also creates toplevel window .
ttk::setTheme clam
set secret [expr int(rand()*5)]

wm title . "Guessing game"            ;# toplevel window title
pack [label .t -text "Can you guess the number I'm thinking about?"]

proc message {i} {
  tk_messageBox -type ok -message [ expr {$i < $::secret ? "Too small"
                                        : $i > $::secret ? "Too big"
                                        : "Exactly!" }]
  if {$i == $::secret} exit           ;# success => close window
}

for {set i 0} {$i < 10} {incr i} {    ;# create all buttons
  pack [ttk::button .b$i -text $i -width 3 -command "message $i" ] \
                        -side left    ;# left to right packing
}
# now enters event loop

Overall, quite comparable code size and in both cases simple object handling.
In Linux, the GUI appearance in Racket looks better than in Tcl/Tk. Bindings of Tk exists for other languages as well, but the code looks clearly more complex than in the original Tcl-binding.

Insofar: a good job done with Racket!

3 Likes

GUI posts deserve screen shots. Here is one from macOS.

You might be interested in GUI Easy! FUNARCH 2023: Functional Shell and Reusable Components for Easy GUIs

2 Likes

@rob The Racket GUI system is great, but the gui-easy library has many advantages and is worth looking into. In my experience the code is often shorter and it is always more natural to read and write - it just makes sense to me.

In your original program you will notice that GUIs are structured like a tree (well, the computer science version of a tree, at least). You have a window, which contains multiple panels, which contain multiple buttons.

window
 |- text
 \- panel
      |- button 0
      |- button 1
      |- ...etc...

In Racket GUI you have to use an imperative style where you create the objects and then, later, add the children to them.

Lisp languages have a huge advantage here that Racket GUI doesn't take advantage of: Lisp is based around nested s-expressions. This allows them to represent the tree structure of the GUI directly in the nature of the code. This allows for writing GUIs in real code in a declarative style. Here are the two most obvious advantages of this approach:

  1. No need to give every container a variable name just to assign its children - this scales very badly for large programs
  2. Indentation of the code matches the tree structure of the visible GUI

The Racket gui-easy library is built on top of the default Racket GUI system and it uses this exact technique. Here's a Racket gui-easy version of the program:

#lang racket
(require racket/format
         racket/gui/easy)
;; A GUI guessing game

(define secret (random 5))

(define ((show-feedback num))
  (render
   (dialog #:size '(200 50)
           (text (cond [(num . < . secret) "Too small"]
                       [(num . > . secret) "Too big"]
                       [else               "Exactly!"])))))

(define r
  (render
   (window                              ; toplevel window
    (text "Can you guess the number I'm thinking about?")
    (list-view #:style '(horizontal auto-hscroll)
               (range 10)               ; 10 buttons
               (λ (num _)
                 (button (~a num)       ; each button looks like this
                         (curry show-feedback num)))))))

You can see the layout of the window from the indentation of the code:

(render
 (window
  (text ...)
  (list-view ... (range 10) ...
   (button ...

You can see this looks really similar to the ASCII art tree near the top of my post.

The benefits of this tree structure become even more obvious with larger programs, like my Fandom Archiver: breezewiki/archiver/archiver-gui.rkt at a52d131b936037fdd7847bf8d58c1662227101ad - cadence/breezewiki - Gitdab

The linked top-level s-expr compactly describes the entire appearance of my GUI and reveals its structure. Here's what it looks like when displayed:

image

Zooming in on a section of that tree:

     (hpanel
      #:stretch '(#t #f)
      #:spacing 10
      (hpanel
       (text "https://")
       (input @input
              (λ (event data) (cond
                                [(eq? event 'input) (:= @input data)]
                                [(eq? event 'return) (do-add-to-queue)])))
       (text ".fandom.com"))
      (button "Download Wiki" do-add-to-queue))

Looking at the first word of each line of code, you can tell that this section is a horizontally arranged input box, label, and button. It is much easier to understand than the Racket GUI alternative.

My program also uses 13 panels used for alignment and I didn't have to assign variable names to any of them.

Illustration of how the code maps to the GUI

gui-easy really shines when you get to use the Observables feature to update parts of the GUI... but I'll let you find that out by yourself :slight_smile:

Here is a short video by Bogdan Popa, the library author, that I found really helpful for getting started with gui-easy: https://www.youtube.com/watch?v=AXJ9tTVGDwUe

2 Likes