Windows besides the main one, and dialogs of the program's own

run-app's #:windows gives the windows a model has. The app opens a window
when it is first in the list, patches its view and title while it stays,
and closes it when it is gone. A click on its close button sends its
on-close message, and the model closes it. A #:modal window is a dialog
over the main window, so a dialog of the program's own is a window whose
state is in the model.

Windows wait for the main window to be shown, because a dialog cannot
belong to a hidden one, and the program ends when the main window closes.
Key shortcuts and commands such as focus reach the other windows too, and
#:screenshot saves a picture of each one beside the main window's.

🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)

Co-Authored-By: eca-agent <git@eca.dev>
This commit is contained in:
Linus Björnstam 2026-10-25 21:21:09 +01:00
parent 708256d0b3
commit 5127fbac13
7 changed files with 583 additions and 45 deletions

View file

@ -405,6 +405,40 @@ there.
(keyed "draft" (text-input (.draft m) Draft))
#+END_SRC
* Windows and dialogs of your own
[[file:docs/contacts.png]] [[file:docs/contacts-edit.png]]
A program can have windows besides the main one. ~run-app~'s ~#:windows~
gives the windows a model has, as ~view~ gives the main window's view, and
the app opens, changes and closes them to match.
#+BEGIN_SRC scheme
(: windows (-> Model (List (WindowView Msg))))
(defun (windows m)
(match (.draft m)
((Some d) (list (window "edit" "Edit contact" Cancel (draft-view d) #:modal #t)))
(None '())))
(run-app init gui-update view #:windows windows)
#+END_SRC
- ~(window key title on-close content #:width #:height #:modal)~ is a window,
known by its key. It is opened when it is first in the list, its view and
title are changed as the main window's view is while it stays, and it is
closed when it is gone.
- A click on its close button does not close it: it sends ~on-close~, and the
window closes when the next model has it no more.
- With no ~#:width~ or ~#:height~ it is as big as its content.
- A ~#:modal~ window is a dialog: the main window takes no input until it is
closed. So a dialog of one's own is a window whose state, such as a draft
being edited, is in the model, and whose view is a view like any other.
- ~window-map~ maps a window's messages, as ~view-map~ does a view's.
- The program ends when the main window is closed, and the others with it.
[[file:examples/contacts.bjo][examples/contacts.bjo]] edits a contact in a
modal dialog, and shows the chosen one in a second window.
* Dialogs: (bjogui dialogs)
A dialog is a command, and its answer comes back as a message.
@ -439,16 +473,18 @@ answered.
* Running: (bjogui app)
- ~(run-app init gui-update view #:subscriptions #:keys #:theme #:styles
#:on-close #:title #:width #:height)~ opens a window and runs until it is
closed.
#:windows #:on-close #:title #:width #:height)~ opens a window and runs
until it is closed.
- ~#:screenshot path~ makes ~run-app~ save a PNG of the window once it is
shown, at twice its size, and close it. It is the environment variable
~BJOGUI_SCREENSHOT~ unless the program says otherwise, so any program can
be pictured with no change: ~BJOGUI_SCREENSHOT=docs/todo.png bjo run
examples/todo.bjo~ made the picture above.
- ~(start-app init gui-update view on-root #:subscriptions)~ makes the controls
with no window, and gives ~on-root~ the control to show. Its fibers belong
to the scope it is started in.
shown, at twice its size, and close it. Each other window that is open is
saved beside it, with its key after the name: ~shot-edit.png~. It is the
environment variable ~BJOGUI_SCREENSHOT~ unless the program says
otherwise, so any program can be pictured with no change:
~BJOGUI_SCREENSHOT=docs/todo.png bjo run examples/todo.bjo~ made the
picture above.
- ~(start-app init gui-update view on-root #:subscriptions #:windows)~ makes
the controls with no window, and gives ~on-root~ the control to show. Its
fibers belong to the scope it is started in.
- ~app-dispatch!~ gives a message on the UI thread, and ~app-send!~ from any
thread.
- ~app-run-pending!~ runs what fibers have posted, where no loop of
@ -459,6 +495,8 @@ answered.
does.
- ~app-model~ reads the model, and ~app-control-at~ finds a control by its
path of child indices.
- ~app-windows~ gives the keys of the other windows that are open, and
~app-window-control~ finds a control in one of them.
~tests/app.bjo~ and ~tests/effects.bjo~ use these to drive an app through
its real controls.
@ -485,18 +523,19 @@ bjo run tests/canvas.bjo # prints "bjogui canvas ok"
bjo run tests/components.bjo # prints "bjogui components ok"
bjo run tests/native.bjo # prints "bjogui native ok"
bjo run tests/commands.bjo # prints "bjogui commands ok"
bjo run tests/windows.bjo # prints "bjogui windows ok"
bjo run examples/counter.bjo
bjo run examples/stopwatch.bjo
bjo run examples/todo.bjo
bjo run examples/styles.bjo
bjo run examples/sketch.bjo
bjo run examples/mixer.bjo
bjo run examples/contacts.bjo
#+END_SRC
* Not yet
- A data grid, and lists that make controls only for the items on screen.
- Dialogs of one's own, with a view and a state.
- The macOS menu bar.
- Pointer-over and pressed colours for kinds other than buttons.
- A component asks for no size of its own: it is given one, or fills the