Switches, radios, numbers, dates, completion, expanders, tooltips, flows, docks

A number input's numbers are decimals in Avalonia, so they are converted
where they are set. A date is "yyyy-MM-dd" both ways. An expander and a
completion box raise their own events from the parts of their templates, so
they listen to the property instead.

The cases of Prop and On are now PropLabel, OnClick and so on. They are
exported with their types, and as Label, Value, Selected and Click they
took names that a program's own messages want.

The panel is flow and not wrap, which is CML'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 18:05:12 +01:00
parent cfcb1851b7
commit 56a01efe76
3 changed files with 532 additions and 170 deletions

130
tests/inputs.bjo Normal file
View file

@ -0,0 +1,130 @@
;; 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/tests/inputs.bjo — switches, radios, numbers, dates, completion,
;; expanders, tooltips, flows and docks.
;;
;; bjo run tests/inputs.bjo
;;
;; No window is opened. A control is changed as a user's input would change
;; it, through its Avalonia property, and the message is checked.
(import (std prelude))
(import (std simpletest))
(import (bjogui view))
(import (bjogui app))
(import/class
(Object (: System.Object))
(ClrString (: System.String))
(Disposable (: System.IDisposable))
(AvaloniaObject (: Avalonia.AvaloniaObject))
(AvaloniaProperty (: Avalonia.AvaloniaProperty))
(Priority (: Avalonia.Data.BindingPriority))
(Control (: Avalonia.Controls.Control))
(Controls (: Avalonia.Controls.Controls))
(Panel (: Avalonia.Controls.Panel))
(ToggleButton (: Avalonia.Controls.Primitives.ToggleButton))
(NumericUpDown (: Avalonia.Controls.NumericUpDown))
(DatePicker (: Avalonia.Controls.DatePicker))
(DateTimeOffset (: System.DateTimeOffset))
(AutoCompleteBox (: Avalonia.Controls.AutoCompleteBox))
(Expander (: Avalonia.Controls.Expander))
(DockEdge (: Avalonia.Controls.Dock))
((Nullable %a) (: System.Nullable)))
(import/extern
(set-value (: Avalonia.AvaloniaObject.SetValue
(-> AvaloniaObject AvaloniaProperty Object Priority Disposable)))
(checked-property (: Avalonia.Controls.Primitives.ToggleButton.IsCheckedProperty #:get))
(number-property (: Avalonia.Controls.NumericUpDown.ValueProperty #:get))
(date-property (: Avalonia.Controls.DatePicker.SelectedDateProperty #:get))
(auto-property (: Avalonia.Controls.AutoCompleteBox.TextProperty #:get))
(expanded-property (: Avalonia.Controls.Expander.IsExpandedProperty #:get))
(is-checked (: Avalonia.Controls.Primitives.ToggleButton.IsChecked
(-> ToggleButton (Nullable bool)) #:get))
(number-value (: Avalonia.Controls.NumericUpDown.Value (-> NumericUpDown (Nullable decimal)) #:get))
(selected-date (: Avalonia.Controls.DatePicker.SelectedDate (-> DatePicker (Nullable DateTimeOffset)) #:get))
(parse-date (: System.DateTimeOffset.Parse (-> string DateTimeOffset)))
(expanded? (: Avalonia.Controls.Expander.IsExpanded (-> Expander bool) #:get))
(tip-of (: Avalonia.Controls.ToolTip.GetTip (-> Control Object)))
(dock-of (: Avalonia.Controls.DockPanel.GetDock (-> Control DockEdge)))
(children (: Avalonia.Controls.Panel.Children (-> Panel Controls) #:get))
(count (: Avalonia.Controls.Controls.Count (-> Controls int) #:get))
(clr-string (: System.Convert.ToString (-> string ClrString))))
(type (: Msg (Union (: Switched bool) (: Picked string) (: Amount double) (: Dated string)
(: Typed string) (: Opened bool))))
(type (: Model (Record (: on bool) (: pick string) (: amount double) (: date string)
(: typed string) (: open bool))))
(: (-> Msg Model (Tuple Model (List (Event Msg)))))
(defun (gui-update msg m)
(next (match msg
((Switched b) (record-set m (on b)))
((Picked s) (record-set m (pick s)))
((Amount d) (record-set m (amount d)))
((Dated s) (record-set m (date s)))
((Typed s) (record-set m (typed s)))
((Opened b) (record-set m (open b))))))
(: (-> Model (View Msg)))
(defun (view m)
(column
(toggle-switch "Wi-Fi" (.on m) Switched) ; 0
(row (radio "tea" (= (.pick m) "tea") (Picked "tea")) ; 1 0
(radio "coffee" (= (.pick m) "coffee") (Picked "coffee"))) ; 1 1
(number-input (.amount m) Amount #:minimum 0.0 #:maximum 10.0 #:step 0.5) ; 2
(date-picker (.date m) Dated) ; 3
(auto-complete (.typed m) ["apple" "apricot" "banana"] Typed) ; 4
(expander "More" (.open m) Opened (text "inside")) ; 5
(tooltip (text "hover me") "a tip") ; 6
(flow #:spacing 4.0 (text "a") (text "b") (text "c")) ; 7
(dock (at-top (text "top")) (text "fill")))) ; 8
(defun (main)
(def app (start-app (Model (on #f) (pick "tea") (amount 2.0) (date "2026-10-25")
(typed "") (open #f))
gui-update view (fun (c) unit)))
(defun (ctl path) (app-control-at app path))
(defun (put! path property value)
(ignore (set-value (cast AvaloniaObject (ctl path)) property value Priority.LocalValue)))
(defun (model) (app-model app))
;; --- switch and radios --------------------------------------------------------
(put! '(0) (cast AvaloniaProperty checked-property) (cast Object #t))
(expect "a switch sends its state" (.on (model)) #t)
(expect "a radio shows the model's choice"
(.GetValueOrDefault (is-checked (cast ToggleButton (ctl '(1 0))))) #t)
(put! '(1 1) (cast AvaloniaProperty checked-property) (cast Object #t))
(expect "choosing a radio sends its message" (.pick (model)) "coffee")
(expect "the other one is not chosen any more"
(.GetValueOrDefault (is-checked (cast ToggleButton (ctl '(1 0))))) #f)
;; --- number ---------------------------------------------------------------------
(expect "a number input's value" (cast System.Double (.GetValueOrDefault (number-value (cast NumericUpDown (ctl '(2)))))) 2.0)
(put! '(2) (cast AvaloniaProperty number-property) (cast Object (cast System.Decimal 3.5)))
(expect "a number sends its value" (.amount (model)) 3.5)
;; --- date -----------------------------------------------------------------------
(expect "a date picker's date" (.ToString (.-Value (selected-date (cast DatePicker (ctl '(3))))) "yyyy-MM-dd")
"2026-10-25")
(put! '(3) (cast AvaloniaProperty date-property) (cast Object (parse-date "2026-12-24")))
(expect "a date picked is sent" (.date (model)) "2026-12-24")
;; --- completion -------------------------------------------------------------------
(put! '(4) (cast AvaloniaProperty auto-property) (cast Object (clr-string "ap")))
(expect "completion sends what is typed" (.typed (model)) "ap")
;; --- expander ---------------------------------------------------------------------
(expect "an expander starts closed" (expanded? (cast Expander (ctl '(5)))) #f)
(put! '(5) (cast AvaloniaProperty expanded-property) (cast Object #t))
(expect "opening it sends a message" (.open (model)) #t)
;; --- tooltip, flow, dock ---------------------------------------------------------
(expect "a tooltip" (.ToString (tip-of (ctl '(6)))) "a tip")
(expect "a flow's children" (count (children (cast Panel (ctl '(7))))) 3)
(expect "a docked child" (written (dock-of (ctl '(8 0)))) "Top")
(println "bjogui inputs ok"))