1     type ListItem =
2         inherit Wrapper
3     
4         var obs text = ""
5         var obs foreground = Vector3.new
6         var obs background = Vector3.new
7         var obs is_selected = false
8         var obs data : Option<DataItem> = None
9     
10        let rectangle = Rectangle.new
11    
12        is_selected@atom.bind { rectangle.is_visible = _ }
13        |> push_token
14    
15        let aligner = Aligner
16            rectangle
17                color@obs = background@obs
18            TextBlock
19                text@obs = text@obs
20                color@obs = foreground@obs
21                align_h = AlignH/Center
22    
23        content = aligner
24    
25    type ListBox =
26        inherit Wrapper
27    
28        var obs foreground = Vector3.color "333333"
29        var obs selected_foreground = Vector3.color "DDDDDD"
30        var obs selected_background = Vector3.color "444444"
31        var obs selected_background_unfocused = Vector3.color "AFAFAF"
32        var obs selected_item : Option<DataItem> = None
33        var obs selected_list_item : Option<ListItem> = None
34    
35        val items = obs/List<DataItem>.new
36        let stack = Stack.new
37    
38        is_focusable = true
39        content = stack
40    
41        let hide_selection (list_item : ListItem) =
42            list_item.foreground = foreground
43                      is_selected = false
44    
45        let select (list_item : ListItem) =
46            let maybe_prev_item = selected_list_item
47            if maybe_prev_item <> Some list_item then
48                selected_item = list_item.data
49                selected_list_item = Some list_item
50    
51                list_item.foreground = selected_foreground
52                          is_selected = true
53                          background = if is_focused
54                                       then selected_background
55                                       else selected_background_unfocused
56    
57                if maybe_prev_item ? Some prev_item then
58                    hide_selection prev_item
59    
60        let data_list_items = Map.new
61        let token = items
62            |> Obs.map { item ->
63                let list_item = ListItem
64                    text = item.name
65                    data = item
66                    foreground = foreground
67                    background = selected_background
68    
69                let name_token = item.name@atom.bind { list_item.text = _ }
70                list_item.push_token name_token
71    
72                list_item }
73    
74            |> bind { index list_item ->
75                          let data_item = list_item.data.unwrap
76                          data_list_items.add data_item list_item
77                          stack.items.add index list_item }
78    
79                    { index list_item ->
80                          stack.items.remove_at index
81                          let data_item = list_item.data.unwrap
82                          data_list_items.remove data_item }
83    
84        let selected_item_token = selected_item@atom.bind
85            { maybe_item -> case maybe_item of
86                  None -> if selected_list_item ? Some list_item then
87                      selected_list_item = None
88                      selected_item = None
89                      hide_selection list_item
90    
91                  Some item ->
92                      let list_item = data_list_items[item]
93                      select list_item }
94    
95        push_token token
96        push_token selected_item_token
97    
98    def find_parent (control : Control) (f : Control -> bool) : Control =
99        if f control
100       then control
101       else find_parent control.parent.unwrap f
102   
103   type ListItem
104       subscribe { _, event -> if event.is_mouse_press then
105           let list_box = find_parent self { _ is ListBox } as ListBox
106           list_box.select self }
107