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>
This commit is contained in:
parent
e3c54c4348
commit
8941425f05
6 changed files with 321 additions and 10 deletions
72
src/app.bjo
72
src/app.bjo
|
|
@ -99,6 +99,8 @@
|
|||
(WrapPanel (: Avalonia.Controls.WrapPanel (-> WrapPanel)))
|
||||
(DockPanel (: Avalonia.Controls.DockPanel (-> DockPanel)))
|
||||
(DockEdge (: Avalonia.Controls.Dock))
|
||||
(TreeView (: Avalonia.Controls.TreeView (-> TreeView)))
|
||||
(TreeViewItem (: Avalonia.Controls.TreeViewItem (-> TreeViewItem)))
|
||||
(PropertyChange (: Avalonia.AvaloniaPropertyChangedEventArgs))
|
||||
(Classes (: Avalonia.Controls.Classes))
|
||||
(StyledElement (: Avalonia.StyledElement))
|
||||
|
|
@ -220,6 +222,10 @@
|
|||
(auto-watermark-property (: Avalonia.Controls.AutoCompleteBox.WatermarkProperty #:get))
|
||||
(auto-text (: Avalonia.Controls.AutoCompleteBox.Text (-> AutoCompleteBox string) #:get))
|
||||
(expanded? (: Avalonia.Controls.Expander.IsExpanded (-> Expander bool) #:get))
|
||||
(tree-header-property (: Avalonia.Controls.Primitives.HeaderedItemsControl.HeaderProperty #:get))
|
||||
(tree-expanded-property (: Avalonia.Controls.TreeViewItem.IsExpandedProperty #:get))
|
||||
(tree-selected-property (: Avalonia.Controls.TreeView.SelectedItemProperty #:get))
|
||||
(tree-selected (: Avalonia.Controls.TreeView.SelectedItem (-> TreeView Object) #:get))
|
||||
(classes-of (: Avalonia.StyledElement.Classes (-> StyledElement Classes) #:get))
|
||||
(classes-clear! (: Avalonia.Controls.Classes.Clear (-> Classes void)))
|
||||
(classes-add! (: Avalonia.Controls.Classes.Add (-> Classes string void)))
|
||||
|
|
@ -378,6 +384,8 @@
|
|||
(MenuBarKind #t)
|
||||
(MenuItemKind #t)
|
||||
(ContextMenuKind #t)
|
||||
(TreeKind #t)
|
||||
(TreeNodeKind #t)
|
||||
(_ #f)))
|
||||
|
||||
;; A kind whose children each have a place of their own: the content of a
|
||||
|
|
@ -430,6 +438,7 @@
|
|||
(ToggleSwitchKind (Some (cast AvaloniaProperty content-property)))
|
||||
(RadioKind (Some (cast AvaloniaProperty content-property)))
|
||||
(ExpanderKind (Some (cast AvaloniaProperty header-property)))
|
||||
(TreeNodeKind (Some (cast AvaloniaProperty tree-header-property)))
|
||||
(TabKind (Some (cast AvaloniaProperty header-property)))
|
||||
(MenuItemKind (Some (cast AvaloniaProperty menu-header-property)))
|
||||
(_ None)))
|
||||
|
|
@ -484,7 +493,12 @@
|
|||
((PropRadius _) (when-kind (= kind BorderKind) (cast AvaloniaProperty corner-radius-property)))
|
||||
((PropShortcut _) (when-kind (= kind MenuItemKind) (cast AvaloniaProperty input-gesture-property)))
|
||||
((PropTip _) (Some (cast AvaloniaProperty tip-property)))
|
||||
((PropExpanded _) (when-kind (= kind ExpanderKind) (cast AvaloniaProperty expanded-property)))
|
||||
((PropExpanded _)
|
||||
(cond ((= kind ExpanderKind) (Some (cast AvaloniaProperty expanded-property)))
|
||||
((= kind TreeNodeKind) (Some (cast AvaloniaProperty tree-expanded-property)))
|
||||
(else None)))
|
||||
;; Set after the children, by `sync-tree-choice!`.
|
||||
((PropChosenKey _) None)
|
||||
((PropDockSide _) (Some (cast AvaloniaProperty dock-property)))
|
||||
((PropClasses _) None)
|
||||
((PropDate _) (when-kind (= kind DateKind) (cast AvaloniaProperty date-property)))
|
||||
|
|
@ -573,6 +587,7 @@
|
|||
((PropExpanded b) (cast Object b))
|
||||
((PropDate s) (text s))
|
||||
((PropClasses _) (cast Object 0))
|
||||
((PropChosenKey s) (text s))
|
||||
((PropDockSide i)
|
||||
(cast Object (match i (0 DockEdge.Left) (1 DockEdge.Top) (2 DockEdge.Right) (_ DockEdge.Bottom))))))
|
||||
|
||||
|
|
@ -589,7 +604,7 @@
|
|||
((PropRows _) 20) ((PropRow _) 21) ((PropColumn _) 22) ((PropRowSpan _) 23) ((PropColumnSpan _) 24)
|
||||
((PropBackground _) 25) ((PropForeground _) 26) ((PropBorderColour _) 27) ((PropBorderWidth _) 28)
|
||||
((PropRadius _) 29) ((PropShortcut _) 30) ((PropTip _) 31) ((PropExpanded _) 32) ((PropDate _) 33)
|
||||
((PropDockSide _) 34) ((PropClasses _) 35)))
|
||||
((PropDockSide _) 34) ((PropClasses _) 35) ((PropChosenKey _) 36)))
|
||||
|
||||
(: (-> Control AvaloniaProperty Object void))
|
||||
(defun (put! control property value)
|
||||
|
|
@ -1009,6 +1024,13 @@
|
|||
(fun ()
|
||||
(def d (range-value (cast RangeBase control)))
|
||||
(fun (on) (match on ((OnSlide f) (Some (f d))) (_ None))))))
|
||||
(TreeKind
|
||||
(on-event (cast RoutedEvent selection-changed-event)
|
||||
(fun ()
|
||||
(def key (match (tree-node-key live (tree-selected (cast TreeView control)))
|
||||
((Some k) k)
|
||||
(None "")))
|
||||
(fun (on) (match on ((OnPick f) (Some (f key))) (_ None))))))
|
||||
((or DropdownKind ListBoxKind TabsKind)
|
||||
(on-event (cast RoutedEvent selection-changed-event)
|
||||
(fun ()
|
||||
|
|
@ -1060,6 +1082,8 @@
|
|||
(SeparatorKind (cast Control (Separator.)))
|
||||
(ContextKind (cast Control (Border.)))
|
||||
(ContextMenuKind (cast Control (ContextMenu.)))
|
||||
(TreeKind (cast Control (TreeView.)))
|
||||
(TreeNodeKind (cast Control (TreeViewItem.)))
|
||||
(ToggleSwitchKind (cast Control (ToggleSwitch.)))
|
||||
(RadioKind (cast Control (RadioButton.)))
|
||||
(NumberKind (cast Control (NumericUpDown.)))
|
||||
|
|
@ -1119,6 +1143,7 @@
|
|||
(mutablevec-add! (.children live) c)
|
||||
(adopt! kind control (- (mutablevec-length (.children live)) 1) (.control c))))
|
||||
(sync-selection! control kind (view-props v))
|
||||
(sync-tree-choice! live)
|
||||
live)
|
||||
|
||||
;; Changes the controls of `live` to show `next`. Gives `live` itself, or new
|
||||
|
|
@ -1136,8 +1161,51 @@
|
|||
((or (panel? kind) (items? kind)) (patch-children! app live kind (view-children next)))
|
||||
(else unit))
|
||||
(sync-selection! (.control live) kind (view-props next))
|
||||
(sync-tree-choice! live)
|
||||
live)))
|
||||
|
||||
;; The node of a tree whose control is `control`, among the live nodes below
|
||||
;; `live`, and its key.
|
||||
(: (-> (Live %m) Object (Option string)))
|
||||
(defun (tree-node-key live control)
|
||||
(: (-> (Live %m) (Option string)))
|
||||
(defun (walk l)
|
||||
(if (and (= (view-kind (.view l)) TreeNodeKind) (eq? (cast Object (.control l)) control))
|
||||
(view-key (.view l))
|
||||
(loop (:for c (mutablevec->vec (.children l)))
|
||||
(:when-let (Some k) (walk c))
|
||||
(:finish #t (Some k))
|
||||
=> None)))
|
||||
(walk live))
|
||||
|
||||
;; The control of the node with `key` below `live`.
|
||||
(: (-> (Live %m) string (Option Control)))
|
||||
(defun (tree-node-control live key)
|
||||
(if (and (= (view-kind (.view live)) TreeNodeKind) (= (view-key (.view live)) (Some key)))
|
||||
(Some (.control live))
|
||||
(loop (:for c (mutablevec->vec (.children live)))
|
||||
(:when-let (Some found) (tree-node-control c key))
|
||||
(:finish #t (Some found))
|
||||
=> None)))
|
||||
|
||||
;; Chooses the node of a tree whose key the view gives, once the nodes are
|
||||
;; there, if it is not chosen already.
|
||||
(: (-> (Live %m) void))
|
||||
(defun (sync-tree-choice! live)
|
||||
(when (= (view-kind (.view live)) TreeKind)
|
||||
(def control (.control live))
|
||||
(loop (:for p (view-props (.view live)))
|
||||
(:do (match p
|
||||
((PropChosenKey key)
|
||||
(match (if (string-empty? key) None (tree-node-control live key))
|
||||
((Some node)
|
||||
(unless (eq? (tree-selected (cast TreeView control)) (cast Object node))
|
||||
(put! control (cast AvaloniaProperty tree-selected-property) (cast Object node))))
|
||||
;; Null and not a clear: SelectedItem is a direct property,
|
||||
;; which a clear does not reset.
|
||||
(None (put! control (cast AvaloniaProperty tree-selected-property) (clr-default)))))
|
||||
(_ unit))))))
|
||||
|
||||
;; The children of a kind whose children each have a place of their own.
|
||||
(: (-> (App %model %m) (Live %m) Kind (Vec (View %m)) void))
|
||||
(defun (patch-single! app live kind views)
|
||||
|
|
|
|||
31
src/view.bjo
31
src/view.bjo
|
|
@ -22,6 +22,7 @@
|
|||
list-box tabs tab
|
||||
menu-bar submenu menu-item menu-separator
|
||||
toggle-switch radio number-input date-picker auto-complete expander
|
||||
tree-view tree-node
|
||||
column row grid scroll border flow dock
|
||||
;; Wrappers.
|
||||
keyed at paint with-context-menu tooltip at-top at-bottom at-left at-right classed
|
||||
|
|
@ -44,7 +45,7 @@
|
|||
ListBoxKind TabsKind TabKind
|
||||
MenuBarKind MenuItemKind SeparatorKind ContextKind ContextMenuKind
|
||||
ToggleSwitchKind RadioKind NumberKind DateKind AutoCompleteKind
|
||||
ExpanderKind WrapKind DockKind
|
||||
ExpanderKind WrapKind DockKind TreeKind TreeNodeKind
|
||||
ColumnKind RowKind GridKind ScrollKind BorderKind)))
|
||||
|
||||
;; A property of a control. A kind takes only some of them; the app ignores
|
||||
|
|
@ -84,14 +85,16 @@
|
|||
(: PropExpanded bool) ; whether an expander shows its content
|
||||
(: PropDate string) ; a date picker's date, as "2026-10-25"
|
||||
(: PropDockSide int) ; where a view is in its dock: 0 to 3
|
||||
(: PropClasses (Vec string))))) ; the classes that styles match
|
||||
(: PropClasses (Vec string)) ; the classes that styles match
|
||||
(: PropChosenKey string)))) ; the key of a tree's chosen node
|
||||
|
||||
;; An event, and the message it sends.
|
||||
(type (: (On %m) (Union (: OnClick %m)
|
||||
(: OnInput (-> string %m))
|
||||
(: OnToggle (-> bool %m))
|
||||
(: OnSlide (-> double %m))
|
||||
(: OnSelect (-> int %m)))))
|
||||
(: OnSelect (-> int %m))
|
||||
(: OnPick (-> string %m)))))
|
||||
|
||||
;; One control and what is in it.
|
||||
;;
|
||||
|
|
@ -393,6 +396,28 @@
|
|||
(children [child])
|
||||
(key None)))
|
||||
|
||||
;; A tree of nodes made by `tree-node`, with the node whose key is `chosen`
|
||||
;; chosen, or none for "". A click on a node sends `(on-pick key)`.
|
||||
(: (-> string (-> string %m) (#:width double) (#:height double) (#:margin double)
|
||||
#:rest (View %m) (View %m)))
|
||||
(defun (tree-view chosen on-pick #:width 0.0 #:height 0.0 #:margin 0.0 #:rest nodes)
|
||||
(View (kind TreeKind)
|
||||
(props (with-layout [(PropChosenKey chosen)] width height margin 0.0))
|
||||
(events [(OnPick on-pick)])
|
||||
(children (array->vec nodes))
|
||||
(key None)))
|
||||
|
||||
;; A node of a tree, known by `key`, which must be one of its own in the
|
||||
;; tree. It opens to show `children`, more nodes, and is open at first when
|
||||
;; `#:expanded`; after that it is opened and closed by the user.
|
||||
(: (-> string string (#:expanded bool) #:rest (View %m) (View %m)))
|
||||
(defun (tree-node key header #:expanded #f #:rest children)
|
||||
(View (kind TreeNodeKind)
|
||||
(props [(PropLabel header) (PropExpanded expanded)])
|
||||
(events [])
|
||||
(children (array->vec children))
|
||||
(key (Some key))))
|
||||
|
||||
;; A checkbox that is `checked?`. A click sends `(on-toggle checked)`, with
|
||||
;; the new state.
|
||||
(: (-> string bool (-> bool %m) (#:enabled bool) (#:margin double) (View %m)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue