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 }