gui-update is a plain function, so it cannot wait. It returns the work to do as values. A command is an (Event %m), synced in a fiber of its own, whose answer is the next message. A subscription is a source of messages that the model asks for: after each update the app starts the new ones and cancels the ones that are gone, by key. every sends a message on a timer, and subscription takes a source of one's own. The fibers belong to the app's scope and post their messages to the UI thread, so gui-update and view only run there. Closing the window cancels them. The prelude's path macros already use the name update, hence gui-update. 🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8) Co-Authored-By: eca-agent <git@eca.dev>
55 lines
2.2 KiB
Text
55 lines
2.2 KiB
Text
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
;; bjogui/examples/stopwatch.bjo — a subscription and a command.
|
|
;;
|
|
;; bjo run examples/stopwatch.bjo
|
|
;;
|
|
;; The stopwatch ticks while it runs: `subscriptions` asks for a tick only
|
|
;; then, so stopping it stops the fiber. "Lap" saves the time after a pause,
|
|
;; to show a command: the pause runs in a fiber, and the window does not wait
|
|
;; for it.
|
|
|
|
(import (std prelude))
|
|
(import (bjogui view))
|
|
(import (bjogui app))
|
|
|
|
(type (: Msg (Union Toggle Tick Reset Lap (: Saved int))))
|
|
(type (: Model (Record (: tenths int) (: running bool) (: saving bool) (: laps (List int)))))
|
|
|
|
(: gui-update (-> Msg Model (Tuple Model (List (Event Msg)))))
|
|
(defun (gui-update msg m)
|
|
(match msg
|
|
(Toggle (next (record-set m (running (not (.running m))))))
|
|
(Tick (next (record-set m (tenths (+ (.tenths m) 1)))))
|
|
(Reset (next (record-set m (tenths 0) (laps '()))))
|
|
;; The time is taken now, and saved half a second later.
|
|
(Lap (next (record-set m (saving #t))
|
|
(wrap (timeout (milliseconds 500)) (fun (_) (Saved (.tenths m))))))
|
|
((Saved t) (next (record-set m (saving #f) (laps (cons t (.laps m))))))))
|
|
|
|
(: subscriptions (-> Model (List (Sub Msg))))
|
|
(defun (subscriptions m)
|
|
(if (.running m) (list (every (milliseconds 100) Tick)) '()))
|
|
|
|
(: (-> int string))
|
|
(defun (show tenths)
|
|
#"${(/ tenths 10)}.${(% tenths 10)} s")
|
|
|
|
(: view (-> Model (View Msg)))
|
|
(defun (view m)
|
|
(column #:spacing 8.0 #:margin 16.0
|
|
(text (show (.tenths m)) #:size 32.0)
|
|
(row #:spacing 8.0
|
|
(button (if (.running m) "Stop" "Start") Toggle #:width 80.0)
|
|
(button "Lap" Lap #:enabled (not (.saving m)) #:width 80.0)
|
|
(button "Reset" Reset #:width 80.0))
|
|
(apply column (vec->array (vec-map (fun (t) (text #"lap: ${(show t)}"))
|
|
(list->vec (.laps m)))))))
|
|
|
|
(defun (main)
|
|
(run-app (Model (tenths 0) (running #f) (saving #f) (laps '()))
|
|
gui-update view
|
|
#:subscriptions subscriptions
|
|
#:title "Stopwatch" #:width 320.0 #:height 300.0))
|