The Readme on keyed commands, failed commands and many messages
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8) Co-Authored-By: eca-agent <git@eca.dev>
This commit is contained in:
parent
b13ff4369a
commit
3b06fc0488
1 changed files with 1 additions and 311 deletions
312
Readme.org
312
Readme.org
|
|
@ -241,314 +241,4 @@ value is.
|
|||
corner, its colours are strings, and its angles are radians, clockwise from
|
||||
the right, as ~sin~ and ~cos~ take them.
|
||||
|
||||
- Shapes: ~fill-rect~, ~stroke-rect~ (~#:radius~), ~fill-circle~,
|
||||
~stroke-circle~, ~fill-ellipse~, ~stroke-ellipse~, ~stroke-line~,
|
||||
~stroke-polyline~, ~fill-polygon~, ~stroke-arc~, and ~fill-path~ and
|
||||
~stroke-path~ with SVG path data. Lines take ~#:width~, ~#:cap~ and
|
||||
~#:dash~.
|
||||
- Text and pictures: ~draw-text~ (~#:size~, ~#:bold~, ~#:font~, ~#:align~,
|
||||
~#:max-width~), ~text-size~ to lay text out, and ~draw-image~.
|
||||
- ~translated~, ~scaled~, ~rotated~, ~clipped~ and ~faded~ change what a
|
||||
function given to them draws.
|
||||
|
||||
The drawing is vectors, so it is sharp at any scale.
|
||||
[[file:examples/mixer.bjo][examples/mixer.bjo]] has knobs and a timeline with
|
||||
a playhead, marks and a hover line. ~start-headless!~ sets Avalonia up with no
|
||||
screen, so that a test can draw components and read their pixels; see
|
||||
~tests/components.bjo~.
|
||||
|
||||
* Controls bjogui does not wrap: (bjogui native)
|
||||
|
||||
Any control of Avalonia's, or of another package's, can be put in a view.
|
||||
The program makes the control and hooks its events itself, so this is the
|
||||
one place where it names Avalonia's types.
|
||||
|
||||
#+BEGIN_SRC scheme
|
||||
(import (std interop))
|
||||
(import (bjogui native))
|
||||
(import/class (Control (: Avalonia.Controls.Control))
|
||||
(Calendar (: Avalonia.Controls.Calendar (-> Calendar)))
|
||||
(DateTime (: System.DateTime))
|
||||
((Nullable %a) (: System.Nullable)))
|
||||
(import/extern
|
||||
(selected-date (: Avalonia.Controls.Calendar.SelectedDate (-> Calendar (Nullable DateTime)) #:get))
|
||||
(selected-date! (: Avalonia.Controls.Calendar.SelectedDate (-> Calendar (Nullable DateTime) void) #:set))
|
||||
(parse-date (: System.DateTime.Parse (-> string DateTime))))
|
||||
|
||||
;; A calendar, for a date as "2026-10-25".
|
||||
(: calendar (Native string string))
|
||||
(def calendar
|
||||
(native (fun (send) ; makes one control
|
||||
(def c (Calendar.))
|
||||
(ignore (add-event-handler! c "SelectedDatesChanged"
|
||||
(: (fun (s e)
|
||||
(def d (selected-date c))
|
||||
(when (.-HasValue d) (send (.ToString (.-Value d) "yyyy-MM-dd"))))
|
||||
(-> Object Object void))))
|
||||
(cast Control c))
|
||||
(fun (control date) ; puts a value on it
|
||||
(selected-date! (cast Calendar control) (parse-date date)))))
|
||||
|
||||
;; In a view:
|
||||
(hosted calendar (.date model) Picked)
|
||||
#+END_SRC
|
||||
|
||||
- ~(make send)~ makes the control once for its place in the view. ~send~
|
||||
gives an output to the app, as a message that ~hosted~ makes with
|
||||
~on-output~. It may be called from any thread.
|
||||
- ~(put-value control value)~ is called after ~make~, and again each time the
|
||||
view gives another value. What the control sends while it is given a value
|
||||
is dropped, because it is not the user's.
|
||||
- ~#:width~, ~#:height~ and ~#:margin~ are set on the control, as on any
|
||||
view. A control measures itself, as Avalonia's do.
|
||||
- Make a ~native~ once, at the top of a module. A view in the place of
|
||||
another one is a new control.
|
||||
|
||||
* Styles and themes
|
||||
|
||||
[[file:docs/styles.png]]
|
||||
|
||||
~run-app~'s ~#:theme~ gives ~"light"~, ~"dark"~, or anything else for the
|
||||
system's, for a model, so a setting can switch it. ~#:styles~ is a list of
|
||||
rules made by ~style~: a kind, by the name of its constructor, a class
|
||||
given with ~classed~, and the look.
|
||||
|
||||
#+BEGIN_SRC scheme
|
||||
(run-app init gui-update view
|
||||
#:theme (fun (m) (if (.dark m) "dark" "light"))
|
||||
#:styles (list (style "text" #:class "title" #:font-size 22.0 #:colour "#f1c40f")
|
||||
(style "button" #:class "primary" #:background "#27ae60" #:colour "white"
|
||||
#:hover-background "#2ecc71" #:pressed-background "#1e8449")))
|
||||
#+END_SRC
|
||||
|
||||
A rule takes ~#:colour~, ~#:background~, ~#:font-size~, ~#:padding~ and
|
||||
~#:radius~. A button's rule also takes ~#:hover-colour~, ~#:hover-background~,
|
||||
~#:pressed-colour~ and ~#:pressed-background~, which are set inside its
|
||||
template, as the Fluent theme sets its own; no other kind has them.
|
||||
[[file:examples/styles.bjo][examples/styles.bjo]] is the picture above.
|
||||
|
||||
* Menus and keys
|
||||
|
||||
#+BEGIN_SRC scheme
|
||||
(menu-bar
|
||||
(submenu "File"
|
||||
(menu-item "New" New #:shortcut "Ctrl+N")
|
||||
(menu-separator)
|
||||
(menu-item "Quit" Quit #:shortcut "Ctrl+Q")))
|
||||
#+END_SRC
|
||||
|
||||
A menu bar is a view, so it goes where the program puts it, usually at the
|
||||
top of a ~dock~. A menu item's ~#:shortcut~ is shown beside it, and the keys
|
||||
send its message while it is in the view and enabled. ~run-app~'s ~#:keys~
|
||||
gives more, for a model: ~(fun (m) (list (key "F5" Refresh)))~. Keys are
|
||||
the window's after the control that has the keyboard had its chance, so a
|
||||
text input keeps ~Ctrl+C~.
|
||||
|
||||
* Work: commands and subscriptions
|
||||
|
||||
~gui-update~ is a plain function, so it cannot wait, and the compiler says so
|
||||
if it tries. Work is a value it returns, and the app runs it.
|
||||
|
||||
A *command* is an ~(Event %m)~: an event whose answer is a message. ~(next m
|
||||
command ...)~ is the next model and the commands to start; ~(next m)~ is the
|
||||
model alone. The app syncs each command in a fiber of its own and gives its
|
||||
message to ~gui-update~. Any event will do: a ~timeout~, a ~blocking~ call, a
|
||||
channel, a ~choose~ of several.
|
||||
|
||||
#+BEGIN_SRC scheme
|
||||
(Load (next (record-set m (loading #t))
|
||||
(wrap (blocking (fun () (file-read-text path))) Loaded)))
|
||||
#+END_SRC
|
||||
|
||||
A *subscription* is a source of messages that runs while the model asks for
|
||||
it. ~subscriptions~ gives the list for a model. After each update the app
|
||||
starts those that are new and cancels those that are gone; the others keep
|
||||
running. A subscription is known by its key.
|
||||
|
||||
- ~(every duration msg)~ sends ~msg~ each time ~duration~ has passed.
|
||||
- ~(subscription key next-event)~ is a source of your own: ~next-event~ gives
|
||||
the event of the next message, and is called again after each one.
|
||||
~(subscription "jobs" (fun () (wrap (chan-recv jobs) Got)))~ sends what
|
||||
comes on a channel.
|
||||
|
||||
#+BEGIN_SRC scheme
|
||||
(: subscriptions (-> Model (List (Sub Msg))))
|
||||
(defun (subscriptions m)
|
||||
(if (.running m) (list (every (milliseconds 100) Tick)) '()))
|
||||
#+END_SRC
|
||||
|
||||
The fibers belong to the app. They post their messages to the UI thread, so
|
||||
~gui-update~ and ~view~ always run there. When the window is closed they are
|
||||
cancelled, and the program ends.
|
||||
|
||||
** Commands that act on the controls and the clipboard
|
||||
|
||||
Some commands do work of their own and send no message. Each finds its
|
||||
control by the key of its view, given with ~keyed~, and runs once the
|
||||
controls of the update are laid out, so a view the same update made is
|
||||
there.
|
||||
|
||||
| Command | Does |
|
||||
|-------------------------------+---------------------------------------------------------|
|
||||
| ~(focus key)~ | gives the keyboard to the control |
|
||||
| ~(scroll-to-end key)~ | scrolls a ~scroll~ view to its end, as a log wants |
|
||||
| ~(scroll-to-start key)~ | scrolls a ~scroll~ view to its start |
|
||||
| ~(scroll-into-view key)~ | scrolls the views around the control until it is seen |
|
||||
| ~(copy-text text)~ | puts ~text~ on the clipboard |
|
||||
| ~(paste-text on-text)~ | sends ~(on-text (Some text))~, or ~(on-text None)~ |
|
||||
|
||||
#+BEGIN_SRC scheme
|
||||
(Add (next (record-set m (items (vec-add (.items m) (.draft m))) (draft ""))
|
||||
(focus "draft")
|
||||
(scroll-to-end "items")))
|
||||
|
||||
(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.
|
||||
|
||||
| Command | Sends |
|
||||
|-------------------------------------------+-----------------------------------------------|
|
||||
| ~(alert text msg)~ | ~msg~ when it is closed |
|
||||
| ~(confirm text on-answer)~ | ~(on-answer #t)~ for OK, ~#f~ otherwise |
|
||||
| ~(prompt text on-answer #:value)~ | ~(on-answer (Some text))~, or ~None~ |
|
||||
| ~(open-file on-answer #:filters)~ | ~(on-answer (Some path))~, or ~None~ |
|
||||
| ~(open-files on-answer)~ | ~(on-answer paths)~ |
|
||||
| ~(save-file on-answer #:name)~ | ~(on-answer (Some path))~, or ~None~ |
|
||||
| ~(open-folder on-answer)~ | ~(on-answer (Some path))~, or ~None~ |
|
||||
| ~(quit)~ | nothing: it closes the windows |
|
||||
|
||||
#+BEGIN_SRC scheme
|
||||
(Delete (next m (confirm "Delete it?" Deleted?)))
|
||||
((Deleted? #t) (next (remove m)))
|
||||
((Deleted? #f) (next m))
|
||||
#+END_SRC
|
||||
|
||||
~#:filters~ are kinds of file, each a name and patterns: ~(list (Tuple "Text"
|
||||
'("*.txt" "*.md")))~. ~run-app~'s ~#:on-close msg~ makes the close button
|
||||
send ~msg~ instead of closing, and the program closes with ~(quit)~ when it
|
||||
is ready, say after asking about unsaved work.
|
||||
|
||||
A dialog belongs to the main window. With none, as under ~start-app~, each
|
||||
answers as if it was cancelled. Avalonia's dialogs cannot be cancelled, so a
|
||||
command that is cancelled stops waiting, and the dialog stays until it is
|
||||
answered.
|
||||
|
||||
* Running: (bjogui app)
|
||||
|
||||
- ~(run-app init gui-update view #:subscriptions #:keys #:theme #:styles
|
||||
#: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. 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
|
||||
Avalonia's does.
|
||||
- ~app-stop!~ cancels the subscriptions and drops later messages.
|
||||
- ~app-press!~ presses keys, such as ~"Ctrl+S"~, as the window does.
|
||||
- ~app-input!~ gives an ~Input~ to the component at a path, as its control
|
||||
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.
|
||||
|
||||
* Requirements
|
||||
|
||||
- Avalonia 11.3, which ~bjo~ restores from NuGet. On Linux it needs X11 or
|
||||
XWayland.
|
||||
- A Bjolang with ~(std interop)~, which makes the delegates that Avalonia's
|
||||
events take.
|
||||
|
||||
* Tests
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
bjo run tests/app.bjo # prints "bjogui ok"
|
||||
bjo run tests/effects.bjo # prints "bjogui effects ok"
|
||||
bjo run tests/controls.bjo # prints "bjogui controls ok"
|
||||
bjo run tests/lists.bjo # prints "bjogui lists ok"
|
||||
bjo run tests/inputs.bjo # prints "bjogui inputs ok"
|
||||
bjo run tests/menus.bjo # prints "bjogui menus ok"
|
||||
bjo run tests/dialogs.bjo # prints "bjogui dialogs ok"
|
||||
bjo run tests/tree.bjo # prints "bjogui tree ok"
|
||||
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.
|
||||
- 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
|
||||
space it gets. Measuring itself, an accessibility tree, and text input
|
||||
inside a component would need a subclass.
|
||||
- A moved control is taken out of the panel and put back, so it may lose the
|
||||
keyboard focus.
|
||||
- A window's size and place are not kept from one run to the next.
|
||||
- The keys of a menu item are looked for in the views of all the windows,
|
||||
and not only in the window that has the keyboard.
|
||||
- A component is drawn again whole when its value, its state or its size
|
||||
changes. A few thousand shapes are fast; a very large drawing that changes
|
||||
at 60 frames a second is not.
|
||||
- Drag and drop, a tray icon, notifications and printing.
|
||||
- The tests click a window's close button through the callback that the
|
||||
platform calls, and not with a real pointer.
|
||||
- Shapes:
|
||||
Loading…
Add table
Add a link
Reference in a new issue