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>
This commit is contained in:
parent
5379269f43
commit
5237bf3273
9 changed files with 1482 additions and 33 deletions
88
Readme.org
88
Readme.org
|
|
@ -177,6 +177,84 @@ 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 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~.
|
||||
|
||||
* Styles and themes
|
||||
|
||||
[[file:docs/styles.png]]
|
||||
|
|
@ -304,6 +382,8 @@ answered.
|
|||
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.
|
||||
|
||||
|
|
@ -329,11 +409,13 @@ 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 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
|
||||
#+END_SRC
|
||||
|
||||
* Not yet
|
||||
|
|
@ -342,7 +424,9 @@ bjo run examples/sketch.bjo
|
|||
- 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.
|
||||
- Controls of Avalonia's or another package's that bjogui does not wrap, and
|
||||
controls that draw themselves, which would need a subclass.
|
||||
- Controls of Avalonia's or another package's that bjogui does not wrap.
|
||||
- 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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue