1 // Written in the D programming language.
2 /++
3  + Authors: KanzakiKino
4  + Copyright: KanzakiKino 2018
5  + License: LGPL-3.0
6 ++/
7 module w4d.widget.list;
8 import w4d.layout.placer.lineup,
9        w4d.layout.fill,
10        w4d.parser.colorset,
11        w4d.style.rect,
12        w4d.style.scalar,
13        w4d.style.widget,
14        w4d.task.window,
15        w4d.widget.base,
16        w4d.widget.wrapper,
17        w4d.event,
18        w4d.exception;
19 import gl3n.linalg;
20 import std.algorithm,
21        std.array,
22        std.conv;
23 
24 /// A handler that handles changing selection.
25 alias SelectChangeHandler = EventHandler!( void, ListItemWidget[] );
26 
27 /// A widget of list.
28 class ListWidget : Widget
29 {
30     protected ListItemWidget[] _items;
31     /// Child items.
32     @property items () { return _items; }
33 
34     /// All selected child items.
35     @property selectedItems ()
36     {
37         ListItemWidget[] result;
38         foreach ( item; items ) {
39             if ( item.isSelected ) {
40                 result ~= item;
41             }
42             result ~= item.selectedItems;
43         }
44         return result;
45     }
46     ///
47     override @property Widget[] children ()
48     {
49         return items.to!( Widget[] );
50     }
51 
52     protected bool _multiselect;
53     /// Whether the list widget is multi-selectable.
54     @property multiselectable () { return _multiselect; }
55 
56     protected Widget _dragging;
57 
58     ///
59     SelectChangeHandler onSelectChange;
60 
61     ///
62     override bool handleMouseButton ( MouseButton btn, bool status, vec2 pos )
63     {
64         if ( super.handleMouseButton(btn,status,pos) ) return true;
65 
66         if ( btn == MouseButton.Left && status ) {
67             if ( auto child = findChildAt( pos ) ) {
68                 _dragging = child;
69 
70                 toggleItem( child.to!ListItemWidget );
71                 return true;
72             }
73         } else if ( btn == MouseButton.Left && !status ) {
74             _dragging = null;
75         }
76         return false;
77     }
78 
79     ///
80     this ()
81     {
82         super();
83         parseColorSetsFromFile!"colorset/list.yaml"( style );
84         setLayout!( FillLayout, VerticalLineupPlacer );
85 
86         _multiselect = false;
87     }
88 
89     /// Changes multi-selectable.
90     void setMultiselectable ( bool b )
91     {
92         deselect();
93         _multiselect = b;
94     }
95 
96     /// Adds an item.
97     void addItem ( ListItemWidget w )
98     {
99         enforce( w, "Null is invalid." );
100         _items ~= w;
101         w.setParent( this );
102     }
103     /// Removes an item.
104     void removeItem ( ListItemWidget w )
105     {
106         _items = _items.remove!( x => x is w );
107     }
108     /// Removes all items.
109     void removeAllItems ()
110     {
111         _items = [];
112     }
113 
114     /// Unselects all items.
115     void deselect ()
116     {
117         foreach ( i; items ) {
118             unselectItem( i );
119             i.deselect();
120         }
121     }
122 
123     /// Selects the item.
124     void selectItem ( ListItemWidget w )
125     {
126         if ( w.isSelected ) return;
127         if ( !multiselectable ) deselect();
128 
129         w.enableState( WidgetState.Selected );
130         onSelectChange.call( selectedItems );
131     }
132     /// Unselects the item.
133     void unselectItem ( ListItemWidget w )
134     {
135         if ( !w.isSelected ) return;
136 
137         w.disableState( WidgetState.Selected );
138         onSelectChange.call( selectedItems );
139     }
140     /// Toggles selected state of the item.
141     void toggleItem ( ListItemWidget w )
142     {
143         if ( w.isSelected ) {
144             unselectItem( w );
145         } else {
146             selectItem( w );
147         }
148     }
149 
150     ///
151     override @property bool trackable () { return false; }
152     ///
153     override @property bool focusable () { return true; }
154 }
155 
156 /// A widget of list item.
157 class ListItemWidget : WrapperWidget
158 {
159     ///
160     this ()
161     {
162         super();
163 
164         parseColorSetsFromFile!"colorset/listitem.yaml"( style );
165         style.box.size.width = Scalar.Auto;
166         style.box.paddings   = Rect( 1.mm );
167     }
168 
169     /// Checks if the item is selected.
170     @property isSelected ()
171     {
172         return !!(status & WidgetState.Selected);
173     }
174     /// Selected child items. (for tree item widget).
175     @property ListItemWidget[] selectedItems ()
176     {
177         return [];
178     }
179 
180     /// Changes the parent list widget.
181     void setParent ( ListWidget ) { }
182     /// Unselects all child items. (for tree item widget).
183     void deselect () { }
184 
185     ///
186     override @property bool trackable () { return false; }
187     ///
188     override @property bool focusable () { return false; }
189 }