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