From 909757972f07ec02fcd8139ab6d194918bcb1b5d Mon Sep 17 00:00:00 2001 From: Athanasios Anastasiou Date: Sun, 27 Jul 2025 20:46:32 +0000 Subject: [PATCH 1/3] Minor documentation touch-up by transfering useful snippets from the FUNARCH23 publication to the documentation, adding links to github and a simple citation to the FUNARCH23 paper. --- .../gui/easy/scribblings/escape-hatches.scrbl | 7 ++++- gui-easy/gui/easy/scribblings/gui-easy.scrbl | 4 +-- .../gui/easy/scribblings/quickstart.scrbl | 18 ++++++++++- gui-easy/gui/easy/scribblings/reference.scrbl | 30 +++++++++++++++++-- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/gui-easy/gui/easy/scribblings/escape-hatches.scrbl b/gui-easy/gui/easy/scribblings/escape-hatches.scrbl index 255955d..f42141d 100644 --- a/gui-easy/gui/easy/scribblings/escape-hatches.scrbl +++ b/gui-easy/gui/easy/scribblings/escape-hatches.scrbl @@ -6,6 +6,11 @@ racket/gui/easy racket/gui/easy/operator)) +; Example links cited within this section +@(define example-link-close-window + (link "https://github.com/Bogdanp/racket-gui-easy/blob/master/examples/close-window.rkt" + @filepath{examples/close-window.rkt})) + @title{Escape Hatches} Some views take a @racket[#:mixin] argument that can be used to alter @@ -14,5 +19,5 @@ 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 +See @|example-link-close-window| for an example of using a mixin to programmatically toggle a window's visibility. diff --git a/gui-easy/gui/easy/scribblings/gui-easy.scrbl b/gui-easy/gui/easy/scribblings/gui-easy.scrbl index 575df1e..aaac3b5 100644 --- a/gui-easy/gui/easy/scribblings/gui-easy.scrbl +++ b/gui-easy/gui/easy/scribblings/gui-easy.scrbl @@ -11,8 +11,8 @@ @title{@tt{gui-easy}: Declarative GUIs} @author[(author+email "Bogdan Popa" "bogdan@defn.io")] -This library provides a declarative API on top of -@racketmodname[racket/gui]. +The goal of GUI Easy is to simplify user interface construction in Racket +by wrapping the existing imperative API (@racketmodname[racket/gui]) in a functional shell. @(define-runtime-path youtubestub.tex "youtubestub.tex") @(define embed-style diff --git a/gui-easy/gui/easy/scribblings/quickstart.scrbl b/gui-easy/gui/easy/scribblings/quickstart.scrbl index 7fb72b5..4839ad0 100644 --- a/gui-easy/gui/easy/scribblings/quickstart.scrbl +++ b/gui-easy/gui/easy/scribblings/quickstart.scrbl @@ -9,6 +9,16 @@ @title{Quickstart} +gui-easy can be broadly split up into two parts: observables and views. + +Observables contain values and notify subscribed observers of changes to +their contents and Views are representations of Racket GUI widget trees +that, when rendered, produce concrete instances of those trees and handle +the details of wiring state and widgets together. + +The core abstractions of observables and views correspond to a model-view-controller +(MVC) architecture for graphical applications as popularized by Smalltalk-80. + @section{Hello, World!} @centered[@image[(build-path media "1.1-hello-world.png") #:scale 0.5]] @@ -154,5 +164,11 @@ individual counter by passing in a derived observable to its @(define repo-link (link "https://github.com/Bogdanp/racket-gui-easy" "Git repository")) +@(define easygui-publications-funarch23 + (link "https://arxiv.org/abs/2308.16024" """D. B. Knoble and B. Popa, ‘Functional Shell and +Reusable Components for Easy GUIs’""")) + -For more examples, see the "examples" directory in the @|repo-link|. +For more information about the core concepts of gui-easy and its design, +see @|easygui-publications-funarch23|. For more examples, see the "examples" +directory in the @|repo-link|. diff --git a/gui-easy/gui/easy/scribblings/reference.scrbl b/gui-easy/gui/easy/scribblings/reference.scrbl index 6424788..38054e7 100644 --- a/gui-easy/gui/easy/scribblings/reference.scrbl +++ b/gui-easy/gui/easy/scribblings/reference.scrbl @@ -11,6 +11,17 @@ racket/gui/easy/operator (prefix-in gui: racket/gui))) +; Example links cited within this section +@(define example-link-tabs + (link "https://github.com/Bogdanp/racket-gui-easy/blob/master/examples/tabs.rkt" + @filepath{examples/tabs.rkt})) +@(define example-link-list + (link "https://github.com/Bogdanp/racket-gui-easy/blob/master/examples/list.rkt" + @filepath{examples/list.rkt})) + + + + @title{Reference} @defmodule[racket/gui/easy] @@ -64,6 +75,19 @@ @section{Views} +@deftech{View}s are functions that return a @racket[view<%>] instance. + +Views might wrap a specific GUI widget, like a text message or button, or +they might construct a tree of smaller views, forming a larger component. + +Views are typically @tech{Observable}-aware in ways that make sense for ach individual view. +For instance the text view takes as input an observable string and the rendered text label updates +with changes to that observable. + +Many @racketmodname[racket/gui] widgets are already wrapped by GUI Easy, but programmers can +implement the @racket[view<%>] interface themselves in order to integrate arbitrary widgets, such as +those from 3rd-party packages in the Racket ecosystem, into their projects. + @subsection[#:tag "windows&dialogs"]{Windows & Dialogs} @defproc[(window [#:title title (maybe-obs/c string?) "Untitled"] @@ -285,7 +309,7 @@ selection changes to an adjacent tab). When tabs are reordered, the choices provided to the action represent the new tab order. - See @filepath{examples/tabs.rkt} for an example. + See @|example-link-tabs| for an example. @history[ #:changed "0.3" @elem{Added the @racket[#:choice=?] argument.} @@ -354,7 +378,7 @@ procedure must return a unique value for each entry in the list, as compared using @racket[equal?]. - See @filepath{examples/list.rkt} for an example. + See @|example-link-list| for an example. } @defproc[(observable-view [data (obs/c any/c)] @@ -818,7 +842,7 @@ @section{Observables} -@deftech{Observables} are containers for values that may change over +@deftech{Observable}s are containers for values that may change over time. Their changes may be observed by arbitrary functions. @; Require the private module to avoid requiring racket/gui/base. From a4fd979f903f50cce6cb5319aa2205e1e8c717d0 Mon Sep 17 00:00:00 2001 From: Athanasios Anastasiou Date: Mon, 28 Jul 2025 07:03:19 +0000 Subject: [PATCH 2/3] Minor typos flagged in the notes of the PR and addition of the bibliography section in Quickstart --- .../gui/easy/scribblings/quickstart.scrbl | 183 +++++++++--------- gui-easy/gui/easy/scribblings/reference.scrbl | 2 +- 2 files changed, 97 insertions(+), 88 deletions(-) diff --git a/gui-easy/gui/easy/scribblings/quickstart.scrbl b/gui-easy/gui/easy/scribblings/quickstart.scrbl index 4839ad0..bbee9b6 100644 --- a/gui-easy/gui/easy/scribblings/quickstart.scrbl +++ b/gui-easy/gui/easy/scribblings/quickstart.scrbl @@ -12,7 +12,7 @@ gui-easy can be broadly split up into two parts: observables and views. Observables contain values and notify subscribed observers of changes to -their contents and Views are representations of Racket GUI widget trees +their contents. Views are representations of Racket GUI widget trees that, when rendered, produce concrete instances of those trees and handle the details of wiring state and widgets together. @@ -24,12 +24,12 @@ The core abstractions of observables and views correspond to a model-view-contro @centered[@image[(build-path media "1.1-hello-world.png") #:scale 0.5]] @codeblock|{ - #lang racket/base +#lang racket/base - (require racket/gui/easy) +(require racket/gui/easy) - (window - (text "Hello, World!")) +(window +(text "Hello, World!")) }| The code above describes a view hierarchy rooted in a window that @@ -38,13 +38,13 @@ but you can take it and pass it to @racket[render] to convert it into a native GUI: @codeblock|{ - #lang racket/base +#lang racket/base - (require racket/gui/easy) +(require racket/gui/easy) - (render - (window - (text "Hello, World!"))) +(render +(window +(text "Hello, World!"))) }| @section{Counter} @@ -54,18 +54,18 @@ a native GUI: State in @tt{gui-easy} is held by @tech{observables}. @codeblock|{ - #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)))))) +#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 @racket[|@count|] that holds the @@ -82,25 +82,25 @@ Since views are at their core just descriptions of a GUI, it's easy to abstract over them and make them reusable. @codeblock|{ - #lang racket/base +#lang racket/base - (require racket/gui/easy - racket/gui/easy/operator) +(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 @count action) +(hpanel +(button "-" (λ () (action sub1))) +(text (@count . ~> . number->string)) +(button "+" (λ () (action add1))))) - (define @counter-1 (@ 0)) - (define @counter-2 (@ 0)) +(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)))))) +(render +(window +(vpanel +(counter @counter-1 (λ (proc) (@counter-1 . <~ . proc))) +(counter @counter-2 (λ (proc) (@counter-2 . <~ . proc)))))) }| @section{Dynamic Counters} @@ -111,46 +111,46 @@ Taking the previous example further, we can render a dynamic list of counters. @codeblock|{ - #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)))))))))) +#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 @racket[|@counters|] observable holds a list of pairs where @@ -161,14 +161,23 @@ individual counter by passing in a derived observable to its @racket[make-view] argument. @section{More} - -@(define repo-link - (link "https://github.com/Bogdanp/racket-gui-easy" "Git repository")) -@(define easygui-publications-funarch23 - (link "https://arxiv.org/abs/2308.16024" """D. B. Knoble and B. Popa, ‘Functional Shell and -Reusable Components for Easy GUIs’""")) - - + For more information about the core concepts of gui-easy and its design, -see @|easygui-publications-funarch23|. For more examples, see the "examples" -directory in the @|repo-link|. +see @cite["knoblepopa23"]. For more examples, see the "examples" +directory in the @cite["repo-link"]. + + +@(bibliography + @(bib-entry #:key "knoblepopa23" + #:title "Functional Shell and Reusable Components for Easy GUIs" + #:author "D. B. Knoble and B. Popa" + #:location "FUNARCH 2023: Proceedings of the 1st ACM SIGPLAN International Workshop on +Functional Software Architecture" + #:date "31 August 2023 " + #:url "https://arxiv.org/abs/2308.16024") + @(bib-entry #:key "repo-link" + #:title "Declarative GUIs in Racket" + #:author "B. Popa" + #:location "Online" + #:date "Accessed: 28 July 2025" + #:url "https://github.com/Bogdanp/racket-gui-easy")) \ No newline at end of file diff --git a/gui-easy/gui/easy/scribblings/reference.scrbl b/gui-easy/gui/easy/scribblings/reference.scrbl index 38054e7..fc6c058 100644 --- a/gui-easy/gui/easy/scribblings/reference.scrbl +++ b/gui-easy/gui/easy/scribblings/reference.scrbl @@ -80,7 +80,7 @@ Views might wrap a specific GUI widget, like a text message or button, or they might construct a tree of smaller views, forming a larger component. -Views are typically @tech{Observable}-aware in ways that make sense for ach individual view. +Views are typically @tech{Observable}-aware in ways that make sense for each individual view. For instance the text view takes as input an observable string and the rendered text label updates with changes to that observable. From 3a3924a4656fb67543719e3709a12823c8437384 Mon Sep 17 00:00:00 2001 From: Athanasios Anastasiou Date: Mon, 28 Jul 2025 08:59:20 +0000 Subject: [PATCH 3/3] Correction of the quickstart.scrbl lost indentation of codeblocks --- .../gui/easy/scribblings/quickstart.scrbl | 152 +++++++++--------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/gui-easy/gui/easy/scribblings/quickstart.scrbl b/gui-easy/gui/easy/scribblings/quickstart.scrbl index bbee9b6..f63cff7 100644 --- a/gui-easy/gui/easy/scribblings/quickstart.scrbl +++ b/gui-easy/gui/easy/scribblings/quickstart.scrbl @@ -24,12 +24,12 @@ The core abstractions of observables and views correspond to a model-view-contro @centered[@image[(build-path media "1.1-hello-world.png") #:scale 0.5]] @codeblock|{ -#lang racket/base + #lang racket/base -(require racket/gui/easy) + (require racket/gui/easy) -(window -(text "Hello, World!")) + (window + (text "Hello, World!")) }| The code above describes a view hierarchy rooted in a window that @@ -38,13 +38,13 @@ but you can take it and pass it to @racket[render] to convert it into a native GUI: @codeblock|{ -#lang racket/base + #lang racket/base -(require racket/gui/easy) + (require racket/gui/easy) -(render -(window -(text "Hello, World!"))) + (render + (window + (text "Hello, World!"))) }| @section{Counter} @@ -54,18 +54,18 @@ a native GUI: State in @tt{gui-easy} is held by @tech{observables}. @codeblock|{ -#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)))))) + #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 @racket[|@count|] that holds the @@ -82,25 +82,25 @@ Since views are at their core just descriptions of a GUI, it's easy to abstract over them and make them reusable. @codeblock|{ -#lang racket/base + #lang racket/base -(require racket/gui/easy -racket/gui/easy/operator) + (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 @count action) + (hpanel + (button "-" (λ () (action sub1))) + (text (@count . ~> . number->string)) + (button "+" (λ () (action add1))))) -(define @counter-1 (@ 0)) -(define @counter-2 (@ 0)) + (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)))))) + (render + (window + (vpanel + (counter @counter-1 (λ (proc) (@counter-1 . <~ . proc))) + (counter @counter-2 (λ (proc) (@counter-2 . <~ . proc)))))) }| @section{Dynamic Counters} @@ -111,46 +111,46 @@ Taking the previous example further, we can render a dynamic list of counters. @codeblock|{ -#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)))))))))) + #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 @racket[|@counters|] observable holds a list of pairs where