Skip to content

Commit de7ed61

Browse files
authored
Merge pull request #230 from h0lg/treeview-with-node-changes
Improve TreeView samples
2 parents 3b1bd08 + 7826232 commit de7ed61

File tree

5 files changed

+232
-70
lines changed

5 files changed

+232
-70
lines changed

samples/Gallery/Gallery.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@
111111
<Compile Include="Pages\ToggleSwitchPage.fs" />
112112
<Compile Include="Pages\ThemeAwarePage.fs" />
113113
<Compile Include="Pages\ToolTipPage.fs" />
114+
<Compile Include="Pages\TreeView\SimpleTreeView.fs" />
115+
<Compile Include="Pages\TreeView\TreeViewWithNodeInteraction.fs" />
114116
<Compile Include="Pages\TreeViewPage.fs" />
115117
<Compile Include="Pages\TreeDataGrid\CountriesPage.fs" />
116118
<Compile Include="Pages\TreeDataGrid\FileTreeNodeModel.fs" />
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
namespace Gallery
2+
3+
open System.Diagnostics
4+
open Avalonia.Controls
5+
open Avalonia.Layout
6+
open Avalonia.Media
7+
open Fabulous.Avalonia
8+
open Fabulous
9+
10+
open type Fabulous.Avalonia.View
11+
12+
module SimpleTreeView =
13+
type Node = { Name: string; Children: Node list }
14+
15+
type Model = { Nodes: Node list }
16+
17+
type Msg = SelectionItemChanged of SelectionChangedEventArgs
18+
19+
let branch name chidren = { Name = name; Children = chidren }
20+
21+
let leaf name = branch name []
22+
23+
let init () =
24+
let nodes =
25+
[ branch
26+
"Animals"
27+
[ branch "Mammals" [ leaf "Lion"; leaf "Cat"; leaf "Zebra" ]
28+
branch
29+
"Birds"
30+
[ leaf "Eagle"
31+
leaf "Sparrow"
32+
leaf "Dove"
33+
leaf "Owl"
34+
leaf "Parrot"
35+
leaf "Pigeon" ]
36+
leaf "Platypus" ]
37+
branch
38+
"Aliens"
39+
[ branch "pyramid-building terrestrial" [ leaf "Camel"; leaf "Lama"; leaf "Alpaca" ]
40+
branch "extra-terrestrial" [ leaf "Alf"; leaf "E.T."; leaf "Klaatu" ] ] ]
41+
42+
{ Nodes = nodes }, []
43+
44+
let update msg model =
45+
match msg with
46+
| SelectionItemChanged args -> model, Cmd.none
47+
48+
let program =
49+
Program.statefulWithCmd init update
50+
|> Program.withTrace(fun (format, args) -> Debug.WriteLine(format, box args))
51+
|> Program.withExceptionHandler(fun ex ->
52+
#if DEBUG
53+
printfn $"Exception: %s{ex.ToString()}"
54+
false
55+
#else
56+
true
57+
#endif
58+
)
59+
60+
let view () =
61+
Component(program) {
62+
let! model = Mvu.State
63+
64+
VStack() {
65+
TreeView(
66+
model.Nodes,
67+
(_.Children),
68+
(fun x ->
69+
Border(TextBlock(x.Name))
70+
.background(Brushes.Gray)
71+
.horizontalAlignment(HorizontalAlignment.Left)
72+
.borderThickness(1.0)
73+
.cornerRadius(5.0)
74+
.padding(15.0, 3.0))
75+
)
76+
.onSelectionChanged(SelectionItemChanged)
77+
}
78+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
namespace Gallery
2+
3+
open System.Diagnostics
4+
open Avalonia.Controls
5+
open Avalonia.Interactivity
6+
open Avalonia.Layout
7+
open Avalonia.Media
8+
open Fabulous.Avalonia
9+
open Fabulous
10+
11+
open type Fabulous.Avalonia.View
12+
13+
module NodeView =
14+
type Model = { Name: string; Counter: int }
15+
16+
type Msg = Increment of RoutedEventArgs
17+
18+
let init (name: string) = { Name = name; Counter = 0 }
19+
20+
let update msg model =
21+
match msg with
22+
| Increment args ->
23+
{ model with
24+
Counter = model.Counter + 1 }
25+
26+
let program =
27+
Program.stateful init update
28+
|> Program.withTrace(fun (format, args) -> Debug.WriteLine(format, box args))
29+
|> Program.withExceptionHandler(fun ex ->
30+
#if DEBUG
31+
printfn $"Exception: %s{ex.ToString()}"
32+
false
33+
#else
34+
true
35+
#endif
36+
)
37+
38+
let view (name) =
39+
Component(program, name) {
40+
let! model = Mvu.State
41+
42+
Border(
43+
HStack(5) {
44+
TextBlock(model.Counter.ToString())
45+
TextBlock(model.Name)
46+
}
47+
)
48+
.onTapped(Increment)
49+
}
50+
51+
module TreeViewWithNodeInteraction =
52+
type Node(name, children) =
53+
member this.Name = name
54+
member this.Children = children
55+
56+
type Model =
57+
{ Nodes: Node list
58+
Selected: Node option }
59+
60+
type Msg = SelectionItemChanged of SelectionChangedEventArgs
61+
62+
let branch name (children: Node list) = Node(name, children)
63+
64+
let leaf name = branch name []
65+
66+
let init () =
67+
let nodes =
68+
[ branch
69+
"Animals"
70+
[ branch "Mammals" [ leaf "Lion"; leaf "Cat"; leaf "Zebra" ]
71+
branch
72+
"Birds"
73+
[ leaf "Eagle"
74+
leaf "Sparrow"
75+
leaf "Dove"
76+
leaf "Owl"
77+
leaf "Parrot"
78+
leaf "Pigeon" ]
79+
leaf "Platypus" ]
80+
branch
81+
"Aliens"
82+
[ branch "pyramid-building terrestrial" [ leaf "Camel"; leaf "Lama"; leaf "Alpaca" ]
83+
branch "extra-terrestrial" [ leaf "Alf"; leaf "E.T."; leaf "Klaatu" ] ] ]
84+
85+
{ Nodes = nodes; Selected = None }, []
86+
87+
let rec findNodes (predicate: Node -> bool) (nodes: Node list) =
88+
let rec matches (node: Node) =
89+
if predicate node then
90+
seq { node }
91+
else
92+
node.Children |> Seq.collect matches
93+
94+
nodes |> Seq.collect matches
95+
96+
let update msg model =
97+
match msg with
98+
| SelectionItemChanged args ->
99+
let node = args.AddedItems[0] :?> Node
100+
let modelNode = findNodes (fun n -> n = node) model.Nodes |> Seq.tryExactlyOne
101+
{ model with Selected = modelNode }, Cmd.none
102+
103+
let program =
104+
Program.statefulWithCmd init update
105+
|> Program.withTrace(fun (format, args) -> Debug.WriteLine(format, box args))
106+
|> Program.withExceptionHandler(fun ex ->
107+
#if DEBUG
108+
printfn $"Exception: %s{ex.ToString()}"
109+
false
110+
#else
111+
true
112+
#endif
113+
)
114+
115+
let view () =
116+
Component(program) {
117+
let! model = Mvu.State
118+
119+
HStack() {
120+
TreeView(
121+
model.Nodes,
122+
(_.Children),
123+
(fun x ->
124+
NodeView
125+
.view(x.Name)
126+
.background(Brushes.Gray)
127+
.horizontalAlignment(HorizontalAlignment.Left)
128+
.borderThickness(1.0)
129+
.cornerRadius(5.0)
130+
.padding(15.0, 3.0))
131+
)
132+
.onSelectionChanged(SelectionItemChanged)
133+
134+
if model.Selected.IsSome then
135+
TextBlock(model.Selected.Value.Name + " selected")
136+
}
137+
}

samples/Gallery/Pages/TreeViewPage.fs

Lines changed: 3 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,12 @@
11
namespace Gallery
22

3-
open System.Diagnostics
4-
open Avalonia.Controls
5-
open Avalonia.Layout
6-
open Avalonia.Media
73
open Fabulous.Avalonia
8-
open Fabulous
94

105
open type Fabulous.Avalonia.View
116

127
module TreeViewPage =
13-
type Node = { Name: string; Children: Node list }
14-
15-
type Model = { Nodes: Node list }
16-
17-
type Msg = SelectionItemChanged of SelectionChangedEventArgs
18-
19-
let init () =
20-
{ Nodes =
21-
[ { Name = "Animals"
22-
Children =
23-
[ { Name = "Mammals"
24-
Children =
25-
[ { Name = "Lion"; Children = [] }
26-
{ Name = "Cat"; Children = [] }
27-
{ Name = "Zebra"; Children = [] } ] } ] }
28-
29-
{ Name = "Birds"
30-
Children =
31-
[ { Name = "Eagle"; Children = [] }
32-
{ Name = "Sparrow"; Children = [] }
33-
{ Name = "Dove"; Children = [] }
34-
{ Name = "Owl"; Children = [] }
35-
{ Name = "Parrot"; Children = [] }
36-
{ Name = "Pigeon"; Children = [] } ] } ] },
37-
[]
38-
39-
let update msg model =
40-
match msg with
41-
| SelectionItemChanged args -> model, Cmd.none
42-
43-
let program =
44-
Program.statefulWithCmd init update
45-
|> Program.withTrace(fun (format, args) -> Debug.WriteLine(format, box args))
46-
|> Program.withExceptionHandler(fun ex ->
47-
#if DEBUG
48-
printfn $"Exception: %s{ex.ToString()}"
49-
false
50-
#else
51-
true
52-
#endif
53-
)
54-
558
let view () =
56-
Component(program) {
57-
let! model = Mvu.State
58-
59-
VStack() {
60-
TreeView(
61-
model.Nodes,
62-
(_.Children),
63-
(fun x ->
64-
Border(TextBlock(x.Name))
65-
.background(Brushes.Gray)
66-
.horizontalAlignment(HorizontalAlignment.Left)
67-
.borderThickness(1.0)
68-
.cornerRadius(5.0)
69-
.padding(15.0, 3.0))
70-
)
71-
.onSelectionChanged(SelectionItemChanged)
72-
}
9+
TabControl() {
10+
TabItem("Simple", SimpleTreeView.view())
11+
TabItem("With node interaction", TreeViewWithNodeInteraction.view())
7312
}

src/Fabulous.Avalonia/Views/Panels/WrapPanel.fs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,31 @@ module WrapPanel =
2424
module WrapPanelBuilders =
2525
type Fabulous.Avalonia.View with
2626

27-
/// <summary>Creates a VWrap widget.</summary>
27+
/// <summary>Creates a <see cref="WrapPanel" /> with <see cref="WrapPanel.Orientation" /> set to <see cref="Orientation.Vertical" />
28+
/// rendering child elements from left to right while they fit the width and starting a new line when there is no space left
29+
/// (including any margins and borders). See <seealso href="https://docs.avaloniaui.net/docs/reference/controls/detailed-reference/wrappanel" />.</summary>
2830
static member VWrap<'msg>() =
2931
CollectionBuilder<'msg, IFabWrapPanel, IFabControl>(WrapPanel.WidgetKey, Panel.Children, WrapPanel.Orientation.WithValue(Orientation.Vertical))
3032

31-
/// <summary>Creates a HWrap widget.</summary>
33+
/// <summary>Creates a <see cref="WrapPanel" /> with <see cref="WrapPanel.Orientation" /> set to <see cref="Orientation.Horizontal" />
34+
/// rendering child elements from top to bottom while they fit the height and starting a new column when there is no space left
35+
/// (including any margins and borders). See <seealso href="https://docs.avaloniaui.net/docs/reference/controls/detailed-reference/wrappanel" />.</summary>
3236
static member HWrap<'msg>() =
3337
CollectionBuilder<'msg, IFabWrapPanel, IFabControl>(WrapPanel.WidgetKey, Panel.Children, WrapPanel.Orientation.WithValue(Orientation.Horizontal))
3438

3539
type WrapPanelModifiers =
36-
/// <summary>Sets the ItemWidth property.</summary>
40+
/// <summary>Sets the <see cref="WrapPanel.ItemWidth" /> property, i.e. the width of all items in the <see cref="WrapPanel" />.
41+
/// See <seealso href="https://reference.avaloniaui.net/api/Avalonia.Controls/WrapPanel/B89757B8" />.</summary>
3742
/// <param name="this">Current widget.</param>
38-
/// <param name="value">The ItemWidth value.</param>
43+
/// <param name="value">The <see cref="WrapPanel.ItemWidth" /> value.</param>
3944
[<Extension>]
4045
static member inline itemWidth(this: WidgetBuilder<'msg, #IFabWrapPanel>, value: float) =
4146
this.AddScalar(WrapPanel.ItemWidth.WithValue(value))
4247

43-
/// <summary>Sets the ItemHeight property.</summary>
48+
/// <summary>Sets the <see cref="WrapPanel.ItemHeight" /> property, i.e. the height of all items in the <see cref="WrapPanel" />.
49+
/// See <seealso href="https://reference.avaloniaui.net/api/Avalonia.Controls/WrapPanel/3AAE129B" />.</summary>
4450
/// <param name="this">Current widget.</param>
45-
/// <param name="value">The ItemHeight value.</param>
51+
/// <param name="value">The <see cref="WrapPanel.ItemHeight" /> value.</param>
4652
[<Extension>]
4753
static member inline itemHeight(this: WidgetBuilder<'msg, #IFabWrapPanel>, value: float) =
4854
this.AddScalar(WrapPanel.ItemHeight.WithValue(value))

0 commit comments

Comments
 (0)