Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
#+TITLE: bjogui
|
|
|
|
|
|
2026-10-25 17:02:02 +01:00
|
|
|
Desktop programs in Bjolang, written as a model, a ~gui-update~ and a ~view~,
|
|
|
|
|
and shown with [[https://avaloniaui.net][Avalonia]].
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
|
2026-10-25 17:24:45 +01:00
|
|
|
[[file:docs/todo.png]]
|
|
|
|
|
|
|
|
|
|
The todo list above is [[file:examples/todo.bjo][examples/todo.bjo]].
|
|
|
|
|
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
#+BEGIN_SRC scheme
|
|
|
|
|
(import (bjogui view))
|
|
|
|
|
(import (bjogui app))
|
|
|
|
|
|
|
|
|
|
(type (: Msg (Union Inc Dec)))
|
|
|
|
|
|
2026-10-25 17:02:02 +01:00
|
|
|
(: gui-update (-> Msg int (Tuple int (List (Event Msg)))))
|
|
|
|
|
(defun (gui-update msg n)
|
|
|
|
|
(next (match msg (Inc (+ n 1)) (Dec (- n 1)))))
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
|
|
|
|
|
(: view (-> int (View Msg)))
|
|
|
|
|
(defun (view n)
|
|
|
|
|
(row #:spacing 8.0 #:margin 16.0
|
|
|
|
|
(button "-" Dec)
|
|
|
|
|
(text #"${n}")
|
|
|
|
|
(button "+" Inc)))
|
|
|
|
|
|
|
|
|
|
(defun (main)
|
2026-10-25 17:02:02 +01:00
|
|
|
(run-app 0 gui-update view #:title "Counter"))
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
A program never names an Avalonia type. A view is a Bjolang value, an event
|
|
|
|
|
is a message, and the controls are made and changed by the package.
|
|
|
|
|
|
|
|
|
|
* How it works
|
|
|
|
|
|
|
|
|
|
An event finds its message in the view its control was last given.
|
2026-10-25 17:02:02 +01:00
|
|
|
~gui-update~ makes the next model from the message, and ~view~ makes the next
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
view. The next view is compared with the last one, control by control, and
|
|
|
|
|
only what differs is changed. So a control keeps its focus, its caret and its
|
|
|
|
|
scroll position when the model changes.
|
|
|
|
|
|
Keyed children, and eight more controls
A child is now known by its key, or by its place among the children with no
key. One whose identity was there before keeps its control, and the
controls are put in the new order by moving only those out of place. So a
list that is sorted, or has an item put in front, keeps its controls.
Text areas, sliders, progress bars, dropdowns, images, grids with cells and
spans, and scroll views. A grid's sizes are .NET properties and not
Avalonia's, so they are set by a setter of their own. A text is centred in
its height, so a label lines up with the control beside it.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 17:13:35 +01:00
|
|
|
Children are matched by key, and those with no key by their place among
|
|
|
|
|
the others with none. Give each item of a list that can change order a key
|
|
|
|
|
with ~(keyed id view)~: a child that moves then keeps its control, and the
|
|
|
|
|
control is moved rather than made again. A child whose kind changes gets a
|
|
|
|
|
new control.
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
|
|
|
|
|
A control that is changed raises events of its own: a text input whose text
|
|
|
|
|
is set says that its text changed. Those events come while the controls are
|
|
|
|
|
being changed, and they are dropped, so only the user's events are messages.
|
|
|
|
|
|
|
|
|
|
* The views: (bjogui view)
|
|
|
|
|
|
Keyed children, and eight more controls
A child is now known by its key, or by its place among the children with no
key. One whose identity was there before keeps its control, and the
controls are put in the new order by moving only those out of place. So a
list that is sorted, or has an item put in front, keeps its controls.
Text areas, sliders, progress bars, dropdowns, images, grids with cells and
spans, and scroll views. A grid's sizes are .NET properties and not
Avalonia's, so they are set by a setter of their own. A text is centred in
its height, so a label lines up with the control beside it.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 17:13:35 +01:00
|
|
|
| Constructor | Control | Sends |
|
|
|
|
|
|------------------------------------------------------+---------------+-------------------------------|
|
|
|
|
|
| ~(text s)~ | ~TextBlock~ | nothing |
|
|
|
|
|
| ~(button label msg)~ | ~Button~ | ~msg~ on a click |
|
|
|
|
|
| ~(text-input value on-input)~ | ~TextBox~ | ~(on-input text)~ on a change |
|
|
|
|
|
| ~(text-area value on-input)~ | ~TextBox~, several lines | ~(on-input text)~ |
|
|
|
|
|
| ~(checkbox label checked? on-toggle)~ | ~CheckBox~ | ~(on-toggle checked)~ |
|
|
|
|
|
| ~(slider value minimum maximum on-change)~ | ~Slider~ | ~(on-change value)~ |
|
|
|
|
|
| ~(progress-bar value)~ | ~ProgressBar~ | nothing |
|
|
|
|
|
| ~(dropdown options selected on-select)~ | ~ComboBox~ | ~(on-select index)~ |
|
|
|
|
|
| ~(image path)~ | ~Image~ | nothing |
|
Commands for focus, scrolling and the clipboard, and pictures from bytes
focus, scroll-to-end, scroll-to-start and scroll-into-view find their
control by the key of its view, and run once the update's controls are
laid out. copy-text puts text on the clipboard, and paste-text sends what
it holds. A command that sends no message ends its fiber when its work is
done, so none is left waiting until the app stops.
image-data shows a picture from the bytes of a file, read again only when
it is given another array.
start-headless! now adds the Fluent theme, as run-app does, so controls
are templated and laid out as in a window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 21:15:12 +01:00
|
|
|
| ~(image-data bytes)~, the bytes of a picture file | ~Image~ | nothing |
|
2026-10-25 17:21:40 +01:00
|
|
|
| ~(list-box views selected on-select)~ | ~ListBox~ | ~(on-select index)~ |
|
|
|
|
|
| ~(tabs selected on-select (tab header view) ...)~ | ~TabControl~ | ~(on-select index)~ |
|
Keyed children, and eight more controls
A child is now known by its key, or by its place among the children with no
key. One whose identity was there before keeps its control, and the
controls are put in the new order by moving only those out of place. So a
list that is sorted, or has an item put in front, keeps its controls.
Text areas, sliders, progress bars, dropdowns, images, grids with cells and
spans, and scroll views. A grid's sizes are .NET properties and not
Avalonia's, so they are set by a setter of their own. A text is centred in
its height, so a label lines up with the control beside it.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 17:13:35 +01:00
|
|
|
| ~(column child ...)~, ~(row child ...)~ | ~StackPanel~ | nothing |
|
|
|
|
|
| ~(grid #:columns #:rows child ...)~ | ~Grid~ | nothing |
|
|
|
|
|
| ~(scroll child)~ | ~ScrollViewer~ | nothing |
|
2026-10-25 17:21:40 +01:00
|
|
|
| ~(border child)~ | ~Border~ | nothing |
|
Tree views, a styles example, and the Readme for all of it
A tree node is a TreeViewItem: a header and items, patched by key as a
list's items are. The chosen node is chosen by its key, once the nodes are
there, and a click sends the key. SelectedItem is a direct property, which
a clear does not reset, so no choice is set as null.
The Readme now covers menus and keys, dialogs, styles and themes, and the
new controls. docs/styles.png is examples/styles.bjo.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 18:10:53 +01:00
|
|
|
| ~(toggle-switch label on? on-toggle)~ | ~ToggleSwitch~ | ~(on-toggle on)~ |
|
|
|
|
|
| ~(radio label chosen? msg)~ | ~RadioButton~ | ~msg~ when it is chosen |
|
|
|
|
|
| ~(number-input value on-change)~ | ~NumericUpDown~ | ~(on-change value)~ |
|
|
|
|
|
| ~(date-picker "2026-10-25" on-change)~ | ~DatePicker~ | ~(on-change date)~ |
|
|
|
|
|
| ~(auto-complete value options on-input)~ | ~AutoCompleteBox~ | ~(on-input text)~ |
|
|
|
|
|
| ~(expander header expanded? on-toggle child)~ | ~Expander~ | ~(on-toggle expanded)~ |
|
|
|
|
|
| ~(tree-view chosen on-pick (tree-node key header ...) ...)~ | ~TreeView~ | ~(on-pick key)~ |
|
|
|
|
|
| ~(flow child ...)~ | ~WrapPanel~ | nothing |
|
|
|
|
|
| ~(dock child ...)~, with ~at-top~, ~at-left~ … | ~DockPanel~ | nothing |
|
|
|
|
|
| ~(menu-bar (submenu label (menu-item label msg) ...))~ | ~Menu~ | ~msg~ when an item is chosen |
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
|
|
|
|
|
They take keywords where they apply: ~#:size~ (the font size of a text),
|
Keyed children, and eight more controls
A child is now known by its key, or by its place among the children with no
key. One whose identity was there before keeps its control, and the
controls are put in the new order by moving only those out of place. So a
list that is sorted, or has an item put in front, keeps its controls.
Text areas, sliders, progress bars, dropdowns, images, grids with cells and
spans, and scroll views. A grid's sizes are .NET properties and not
Avalonia's, so they are set by a setter of their own. A text is centred in
its height, so a label lines up with the control beside it.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 17:13:35 +01:00
|
|
|
~#:wrap~ (a text breaks its lines at its width), ~#:width~, ~#:height~,
|
|
|
|
|
~#:margin~, ~#:padding~, ~#:spacing~ (between the children of a column, a row
|
|
|
|
|
or a grid), ~#:enabled~, ~#:placeholder~, ~#:step~ (a slider's arrow key) and
|
|
|
|
|
~#:maximum~ (a progress bar's full, 1 unless told otherwise). A size of zero,
|
|
|
|
|
the default, leaves the size to the control.
|
|
|
|
|
|
|
|
|
|
A grid's ~#:columns~ and ~#:rows~ are sizes as Avalonia writes them: ~"Auto"~
|
|
|
|
|
fits the content, ~"*"~ shares what is left, ~"2*"~ takes two shares, and a
|
|
|
|
|
number is pixels. ~(at row column view)~ puts a view in a cell, counting from
|
|
|
|
|
0, with ~#:row-span~ and ~#:column-span~.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC scheme
|
|
|
|
|
(grid #:columns "Auto,*" #:rows "Auto,Auto" #:spacing 6.0
|
|
|
|
|
(at 0 0 (text "Volume")) (at 0 1 (slider (.volume m) 0.0 10.0 Volume))
|
|
|
|
|
(at 1 0 (text "Colour")) (at 1 1 (dropdown ["red" "green" "blue"] (.colour m) Colour)))
|
|
|
|
|
|
|
|
|
|
(scroll (apply column (vec->array (vec-map (fun (t) (keyed (.id t) (text (.title t))))
|
|
|
|
|
(.todos m))))
|
|
|
|
|
#:height 200.0)
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
Commands for focus, scrolling and the clipboard, and pictures from bytes
focus, scroll-to-end, scroll-to-start and scroll-into-view find their
control by the key of its view, and run once the update's controls are
laid out. copy-text puts text on the clipboard, and paste-text sends what
it holds. A command that sends no message ends its fiber when its work is
done, so none is left waiting until the app stops.
image-data shows a picture from the bytes of a file, read again only when
it is given another array.
start-headless! now adds the Fluent theme, as run-app does, so controls
are templated and laid out as in a window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 21:15:12 +01:00
|
|
|
An image that cannot be read shows nothing. ~image-data~ reads its bytes
|
|
|
|
|
again only when it is given another array, so keep the array in the model.
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
|
2026-10-25 17:21:40 +01:00
|
|
|
A list box's items are views, so an item can be anything: a text, a row with
|
|
|
|
|
a checkbox, a dropdown. Give them keys if the list can change order. Each
|
|
|
|
|
item is a control, so a list of thousands makes thousands of controls.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC scheme
|
|
|
|
|
(tabs (.tab m) Tab
|
|
|
|
|
(tab "Todo" (list-box (vec-map (fun (t) (keyed (.id t) (text (.title t)))) (.todos m))
|
|
|
|
|
(.chosen m) Chose))
|
|
|
|
|
(tab "Settings" (checkbox "Dark" (.dark m) Dark)))
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
* Colours and borders
|
|
|
|
|
|
|
|
|
|
~(paint view #:colour #:background)~ colours any view: ~#:colour~ is the
|
|
|
|
|
text, ~#:background~ what is behind it. A colour is a name such as ~"red"~,
|
|
|
|
|
or ~"#rgb"~, ~"#rrggbb"~ or ~"#aarrggbb"~. The empty string, the default,
|
|
|
|
|
leaves the colour to the theme, and so does a colour that cannot be read.
|
|
|
|
|
|
|
|
|
|
~(border child #:colour #:thickness #:radius #:background #:padding)~ draws
|
|
|
|
|
a line around a view, with round corners if ~#:radius~ is more than zero.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC scheme
|
|
|
|
|
(border (paint (text "Saved") #:colour "#c0392b")
|
|
|
|
|
#:colour "#2980b9" #:radius 8.0 #:background "#ecf0f1" #:padding 8.0)
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
The Fluent theme gives a button its own colours when the pointer is over it
|
|
|
|
|
or it is pressed, so a painted button shows the theme's colour then.
|
|
|
|
|
|
Tree views, a styles example, and the Readme for all of it
A tree node is a TreeViewItem: a header and items, patched by key as a
list's items are. The chosen node is chosen by its key, once the nodes are
there, and a click sends the key. SelectedItem is a direct property, which
a clear does not reset, so no choice is set as null.
The Readme now covers menus and keys, dialogs, styles and themes, and the
new controls. docs/styles.png is examples/styles.bjo.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 18:10:53 +01:00
|
|
|
More wrappers: ~(tooltip view "text")~, ~(with-context-menu view item ...)~
|
|
|
|
|
for a menu on the right button, and ~(classed view "name" ...)~ for styles.
|
|
|
|
|
|
|
|
|
|
A tree node is known by its key, which must be its own in the tree. A node
|
|
|
|
|
is open at first when it says ~#:expanded~; after that the user opens and
|
|
|
|
|
closes it.
|
|
|
|
|
|
2026-10-25 19:26:56 +01:00
|
|
|
* Components: view-map
|
|
|
|
|
|
|
|
|
|
A part of a program can have a message type, an update and a view of its
|
|
|
|
|
own, and know nothing of the program it is in. ~(view-map f view)~ puts its
|
|
|
|
|
view in a larger one: each message it sends is given to ~f~ first, which is
|
|
|
|
|
usually the case of the larger message that carries it.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC scheme
|
|
|
|
|
(type (: Msg (Union (: Colour PickerMsg) (: Width PickerMsg) ...)))
|
|
|
|
|
|
|
|
|
|
(column (view-map Colour (picker-view (.colour m)))
|
|
|
|
|
(view-map Width (picker-view (.width m))))
|
|
|
|
|
|
|
|
|
|
((Colour pm) (next (record-set m (colour (picker-update pm (.colour m))))))
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
* Drawing: canvas
|
|
|
|
|
|
|
|
|
|
[[file:docs/sketch.png]]
|
|
|
|
|
|
|
|
|
|
~(canvas width height shape ...)~ is a surface to draw on. A shape is placed
|
|
|
|
|
by its own numbers, and any other view by ~(at-point x y view)~.
|
|
|
|
|
|
|
|
|
|
| Shape | Takes |
|
|
|
|
|
|------------------------------------------+-----------------------------------------------|
|
|
|
|
|
| ~(rect x y width height)~ | ~#:fill~, ~#:stroke~, ~#:stroke-width~, ~#:radius~ |
|
|
|
|
|
| ~(ellipse x y width height)~ | ~#:fill~, ~#:stroke~, ~#:stroke-width~ |
|
|
|
|
|
| ~(line x1 y1 x2 y2)~ | ~#:stroke~, ~#:stroke-width~ |
|
|
|
|
|
| ~(path "M 10 10 L 50 10 L 30 40 Z")~ | ~#:fill~, ~#:stroke~, ~#:stroke-width~ |
|
|
|
|
|
|
|
|
|
|
A path is written as SVG writes one. ~(on-press canvas f)~, ~(on-move canvas
|
|
|
|
|
f)~ and ~(on-release canvas f)~ send ~(f x y)~, the place on the canvas, when
|
|
|
|
|
a button is pressed, when the pointer moves, and when the button is let go.
|
|
|
|
|
A press takes the pointer, so a drag that leaves the canvas still reaches it.
|
|
|
|
|
Shapes are views like any other, so keyed ones are kept as their list
|
|
|
|
|
changes. [[file:examples/sketch.bjo][examples/sketch.bjo]] is the picture
|
|
|
|
|
above, and uses two components of its own.
|
|
|
|
|
|
Controls of the program's own, drawn with a pen
A component draws itself with (bjogui draw), a pen over Avalonia's
DrawingContext, and hears the pointer, the wheel, the keys and the focus.
It keeps a state of its own between inputs, and sends outputs that drawn
makes the app's messages. It is drawn again only when its value, its state
or its size changes, and no Avalonia class is subclassed: the picture is a
DrawingImage on a canvas.
start-headless! sets Avalonia up with no screen, drawing with Skia, so
tests/components.bjo draws components and reads their pixels. The mixer
example has knobs and a timeline.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 20:34:19 +01:00
|
|
|
* Controls of your own: component and (bjogui draw)
|
|
|
|
|
|
|
|
|
|
[[file:docs/mixer.png]]
|
|
|
|
|
|
|
|
|
|
~(component state draw #:input #:cursor #:focusable)~ makes a control that
|
|
|
|
|
draws itself and hears the pointer and the keys. It keeps a state of its own,
|
|
|
|
|
such as where a drag started or where the pointer hovers, which the model
|
|
|
|
|
never sees. ~(drawn c value on-output)~ puts it in a view: it shows ~value~,
|
|
|
|
|
and each output it sends is made a message with ~on-output~, as a slider's
|
|
|
|
|
value is.
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC scheme
|
|
|
|
|
(import (bjogui draw))
|
|
|
|
|
|
|
|
|
|
;; Where a drag started, and the value then.
|
|
|
|
|
(type (: Drag (Union Idle (: Dragging double double))))
|
|
|
|
|
|
|
|
|
|
(: knob (Component double Drag double))
|
|
|
|
|
(def knob
|
|
|
|
|
(component Idle
|
|
|
|
|
(fun (pen w h s v)
|
|
|
|
|
(fill-circle pen (/ w 2.0) (/ h 2.0) (/ w 2.0) "#2c3e50")
|
|
|
|
|
(stroke-arc pen (/ w 2.0) (/ h 2.0) (- (/ w 2.0) 6.0) 2.4 (* v 4.7) "orange"
|
|
|
|
|
#:width 5.0 #:cap "round"))
|
|
|
|
|
#:input (fun (in w h s v)
|
|
|
|
|
(match (Tuple in s)
|
|
|
|
|
((Tuple (PointerDown p) _) (Tuple (Dragging (.y p) v) None))
|
|
|
|
|
((Tuple (PointerMove p) (Dragging y0 v0))
|
|
|
|
|
(Tuple s (Some (max 0.0 (min 1.0 (+ v0 (/ (- y0 (.y p)) 150.0)))))))
|
|
|
|
|
((Tuple (PointerUp _) _) (Tuple Idle None))
|
|
|
|
|
(_ (Tuple s None))))
|
|
|
|
|
#:cursor (fun (s v) "Hand")))
|
|
|
|
|
|
|
|
|
|
;; In a view:
|
|
|
|
|
(drawn knob (.volume model) Volume #:width 80.0 #:height 80.0)
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
- ~(draw pen width height state value)~ draws the component at its size. It
|
|
|
|
|
is called again when the value, the state or the size changes, and not
|
|
|
|
|
otherwise.
|
|
|
|
|
- ~(input in width height state value)~ gives the next state, and maybe an
|
|
|
|
|
output. ~in~ is an ~Input~: ~(PointerDown p)~, ~(PointerMove p)~,
|
|
|
|
|
~(PointerUp p)~, ~PointerEnter~, ~PointerLeave~, ~(Wheel p dx dy)~,
|
|
|
|
|
~(KeyDown "Ctrl+Z")~, ~FocusIn~ and ~FocusOut~. A ~Pointer~ has ~x~ and ~y~
|
|
|
|
|
from the component's corner, ~button~ (~"left"~, ~"right"~, ~"middle"~),
|
|
|
|
|
~clicks~ (2 for a double click) and ~mods~ (~"Ctrl+Shift"~).
|
|
|
|
|
- A press takes the pointer, so a drag that leaves the component still
|
|
|
|
|
reaches it. A ~#:focusable~ component takes the keys when it is pressed.
|
|
|
|
|
- An input that changes the state or sends an output is the component's, and
|
|
|
|
|
goes no further. Any other goes on to the parents, so a key the component
|
|
|
|
|
does not use still works as a shortcut.
|
|
|
|
|
- With no ~#:width~ or ~#:height~, a component fills the space it is given,
|
|
|
|
|
and is drawn again when that changes.
|
|
|
|
|
- Make a component once, at the top of a module. A view in the place of
|
|
|
|
|
another component is a new control, whose state starts again.
|
|
|
|
|
- A component that sends nothing needs its type written, because its output
|
|
|
|
|
type is not known: ~(: lamp (Component bool Unit Unit))~.
|
|
|
|
|
|
|
|
|
|
(bjogui draw) is the pen. Its coordinates start at the component's top left
|
|
|
|
|
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~.
|
|
|
|
|
|
2026-10-25 21:02:17 +01:00
|
|
|
* 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.
|
|
|
|
|
|
Tree views, a styles example, and the Readme for all of it
A tree node is a TreeViewItem: a header and items, patched by key as a
list's items are. The chosen node is chosen by its key, once the nodes are
there, and a click sends the key. SelectedItem is a direct property, which
a clear does not reset, so no choice is set as null.
The Readme now covers menus and keys, dialogs, styles and themes, and the
new controls. docs/styles.png is examples/styles.bjo.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 18:10:53 +01:00
|
|
|
* 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~.
|
|
|
|
|
|
2026-10-25 17:02:02 +01:00
|
|
|
* 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 for focus, scrolling and the clipboard, and pictures from bytes
focus, scroll-to-end, scroll-to-start and scroll-into-view find their
control by the key of its view, and run once the update's controls are
laid out. copy-text puts text on the clipboard, and paste-text sends what
it holds. A command that sends no message ends its fiber when its work is
done, so none is left waiting until the app stops.
image-data shows a picture from the bytes of a file, read again only when
it is given another array.
start-headless! now adds the Fluent theme, as run-app does, so controls
are templated and laid out as in a window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 21:15:12 +01:00
|
|
|
** 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
|
|
|
|
|
|
2026-10-25 21:21:09 +01:00
|
|
|
* 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.
|
|
|
|
|
|
Tree views, a styles example, and the Readme for all of it
A tree node is a TreeViewItem: a header and items, patched by key as a
list's items are. The chosen node is chosen by its key, once the nodes are
there, and a click sends the key. SelectedItem is a direct property, which
a clear does not reset, so no choice is set as null.
The Readme now covers menus and keys, dialogs, styles and themes, and the
new controls. docs/styles.png is examples/styles.bjo.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 18:10:53 +01:00
|
|
|
* 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.
|
|
|
|
|
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
* Running: (bjogui app)
|
|
|
|
|
|
Tree views, a styles example, and the Readme for all of it
A tree node is a TreeViewItem: a header and items, patched by key as a
list's items are. The chosen node is chosen by its key, once the nodes are
there, and a click sends the key. SelectedItem is a direct property, which
a clear does not reset, so no choice is set as null.
The Readme now covers menus and keys, dialogs, styles and themes, and the
new controls. docs/styles.png is examples/styles.bjo.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 18:10:53 +01:00
|
|
|
- ~(run-app init gui-update view #:subscriptions #:keys #:theme #:styles
|
2026-10-25 21:21:09 +01:00
|
|
|
#:windows #:on-close #:title #:width #:height)~ opens a window and runs
|
|
|
|
|
until it is closed.
|
2026-10-25 17:24:45 +01:00
|
|
|
- ~#:screenshot path~ makes ~run-app~ save a PNG of the window once it is
|
2026-10-25 21:21:09 +01:00
|
|
|
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.
|
2026-10-25 17:02:02 +01:00
|
|
|
- ~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.
|
Tree views, a styles example, and the Readme for all of it
A tree node is a TreeViewItem: a header and items, patched by key as a
list's items are. The chosen node is chosen by its key, once the nodes are
there, and a click sends the key. SelectedItem is a direct property, which
a clear does not reset, so no choice is set as null.
The Readme now covers menus and keys, dialogs, styles and themes, and the
new controls. docs/styles.png is examples/styles.bjo.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 18:10:53 +01:00
|
|
|
- ~app-press!~ presses keys, such as ~"Ctrl+S"~, as the window does.
|
Controls of the program's own, drawn with a pen
A component draws itself with (bjogui draw), a pen over Avalonia's
DrawingContext, and hears the pointer, the wheel, the keys and the focus.
It keeps a state of its own between inputs, and sends outputs that drawn
makes the app's messages. It is drawn again only when its value, its state
or its size changes, and no Avalonia class is subclassed: the picture is a
DrawingImage on a canvas.
start-headless! sets Avalonia up with no screen, drawing with Skia, so
tests/components.bjo draws components and reads their pixels. The mixer
example has knobs and a timeline.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 20:34:19 +01:00
|
|
|
- ~app-input!~ gives an ~Input~ to the component at a path, as its control
|
|
|
|
|
does.
|
2026-10-25 17:02:02 +01:00
|
|
|
- ~app-model~ reads the model, and ~app-control-at~ finds a control by its
|
|
|
|
|
path of child indices.
|
2026-10-25 21:21:09 +01:00
|
|
|
- ~app-windows~ gives the keys of the other windows that are open, and
|
|
|
|
|
~app-window-control~ finds a control in one of them.
|
2026-10-25 17:02:02 +01:00
|
|
|
|
|
|
|
|
~tests/app.bjo~ and ~tests/effects.bjo~ use these to drive an app through
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
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
|
2026-10-25 17:02:02 +01:00
|
|
|
bjo run tests/app.bjo # prints "bjogui ok"
|
|
|
|
|
bjo run tests/effects.bjo # prints "bjogui effects ok"
|
Keyed children, and eight more controls
A child is now known by its key, or by its place among the children with no
key. One whose identity was there before keeps its control, and the
controls are put in the new order by moving only those out of place. So a
list that is sorted, or has an item put in front, keeps its controls.
Text areas, sliders, progress bars, dropdowns, images, grids with cells and
spans, and scroll views. A grid's sizes are .NET properties and not
Avalonia's, so they are set by a setter of their own. A text is centred in
its height, so a label lines up with the control beside it.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 17:13:35 +01:00
|
|
|
bjo run tests/controls.bjo # prints "bjogui controls ok"
|
2026-10-25 17:21:40 +01:00
|
|
|
bjo run tests/lists.bjo # prints "bjogui lists ok"
|
Tree views, a styles example, and the Readme for all of it
A tree node is a TreeViewItem: a header and items, patched by key as a
list's items are. The chosen node is chosen by its key, once the nodes are
there, and a click sends the key. SelectedItem is a direct property, which
a clear does not reset, so no choice is set as null.
The Readme now covers menus and keys, dialogs, styles and themes, and the
new controls. docs/styles.png is examples/styles.bjo.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 18:10:53 +01:00
|
|
|
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"
|
2026-10-25 19:26:56 +01:00
|
|
|
bjo run tests/canvas.bjo # prints "bjogui canvas ok"
|
Controls of the program's own, drawn with a pen
A component draws itself with (bjogui draw), a pen over Avalonia's
DrawingContext, and hears the pointer, the wheel, the keys and the focus.
It keeps a state of its own between inputs, and sends outputs that drawn
makes the app's messages. It is drawn again only when its value, its state
or its size changes, and no Avalonia class is subclassed: the picture is a
DrawingImage on a canvas.
start-headless! sets Avalonia up with no screen, drawing with Skia, so
tests/components.bjo draws components and reads their pixels. The mixer
example has knobs and a timeline.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 20:34:19 +01:00
|
|
|
bjo run tests/components.bjo # prints "bjogui components ok"
|
2026-10-25 21:02:17 +01:00
|
|
|
bjo run tests/native.bjo # prints "bjogui native ok"
|
Commands for focus, scrolling and the clipboard, and pictures from bytes
focus, scroll-to-end, scroll-to-start and scroll-into-view find their
control by the key of its view, and run once the update's controls are
laid out. copy-text puts text on the clipboard, and paste-text sends what
it holds. A command that sends no message ends its fiber when its work is
done, so none is left waiting until the app stops.
image-data shows a picture from the bytes of a file, read again only when
it is given another array.
start-headless! now adds the Fluent theme, as run-app does, so controls
are templated and laid out as in a window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 21:15:12 +01:00
|
|
|
bjo run tests/commands.bjo # prints "bjogui commands ok"
|
2026-10-25 21:21:09 +01:00
|
|
|
bjo run tests/windows.bjo # prints "bjogui windows ok"
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
bjo run examples/counter.bjo
|
2026-10-25 17:02:02 +01:00
|
|
|
bjo run examples/stopwatch.bjo
|
2026-10-25 17:24:45 +01:00
|
|
|
bjo run examples/todo.bjo
|
Tree views, a styles example, and the Readme for all of it
A tree node is a TreeViewItem: a header and items, patched by key as a
list's items are. The chosen node is chosen by its key, once the nodes are
there, and a click sends the key. SelectedItem is a direct property, which
a clear does not reset, so no choice is set as null.
The Readme now covers menus and keys, dialogs, styles and themes, and the
new controls. docs/styles.png is examples/styles.bjo.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 18:10:53 +01:00
|
|
|
bjo run examples/styles.bjo
|
2026-10-25 19:26:56 +01:00
|
|
|
bjo run examples/sketch.bjo
|
Controls of the program's own, drawn with a pen
A component draws itself with (bjogui draw), a pen over Avalonia's
DrawingContext, and hears the pointer, the wheel, the keys and the focus.
It keeps a state of its own between inputs, and sends outputs that drawn
makes the app's messages. It is drawn again only when its value, its state
or its size changes, and no Avalonia class is subclassed: the picture is a
DrawingImage on a canvas.
start-headless! sets Avalonia up with no screen, drawing with Skia, so
tests/components.bjo draws components and reads their pixels. The mixer
example has knobs and a timeline.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 20:34:19 +01:00
|
|
|
bjo run examples/mixer.bjo
|
2026-10-25 21:21:09 +01:00
|
|
|
bjo run examples/contacts.bjo
|
Views, events as messages, and a patch, over plain Avalonia
A program is a model, an update and a view. A view is a Bjolang value; an
event finds its message in the view its control was last given, and the next
view is compared with the last so only what differs is changed. Controls are
made with plain constructors and changed with SetValue, so nothing is
subclassed, and a program never names an Avalonia type.
Text, button, text input, checkbox, column and row. tests/app.bjo drives an
app through its real controls with no window.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 16:18:09 +01:00
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
* Not yet
|
|
|
|
|
|
Tree views, a styles example, and the Readme for all of it
A tree node is a TreeViewItem: a header and items, patched by key as a
list's items are. The chosen node is chosen by its key, once the nodes are
there, and a click sends the key. SelectedItem is a direct property, which
a clear does not reset, so no choice is set as null.
The Readme now covers menus and keys, dialogs, styles and themes, and the
new controls. docs/styles.png is examples/styles.bjo.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 18:10:53 +01:00
|
|
|
- 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.
|
Controls of the program's own, drawn with a pen
A component draws itself with (bjogui draw), a pen over Avalonia's
DrawingContext, and hears the pointer, the wheel, the keys and the focus.
It keeps a state of its own between inputs, and sends outputs that drawn
makes the app's messages. It is drawn again only when its value, its state
or its size changes, and no Avalonia class is subclassed: the picture is a
DrawingImage on a canvas.
start-headless! sets Avalonia up with no screen, drawing with Skia, so
tests/components.bjo draws components and reads their pixels. The mixer
example has knobs and a timeline.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 20:34:19 +01:00
|
|
|
- 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.
|
Keyed children, and eight more controls
A child is now known by its key, or by its place among the children with no
key. One whose identity was there before keeps its control, and the
controls are put in the new order by moving only those out of place. So a
list that is sorted, or has an item put in front, keeps its controls.
Text areas, sliders, progress bars, dropdowns, images, grids with cells and
spans, and scroll views. A grid's sizes are .NET properties and not
Avalonia's, so they are set by a setter of their own. A text is centred in
its height, so a label lines up with the control beside it.
🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)
Co-Authored-By: eca-agent <git@eca.dev>
2026-10-25 17:13:35 +01:00
|
|
|
- A moved control is taken out of the panel and put back, so it may lose the
|
|
|
|
|
keyboard focus.
|
2026-10-25 22:17:57 +01:00
|
|
|
- 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.
|