1 /**
2 Copyright: Copyright (c) 2014 Andrey Penechko.
3 License: a$(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 
8 module anchovy.gui.behaviors.listbehavior;
9 
10 import std.stdio;
11 import std.math : ceil;
12 
13 import anchovy.gui;
14 import anchovy.gui.interfaces.iwidgetbehavior;
15 import anchovy.gui.interfaces.ilayout;
16 import anchovy.gui.databinding.list;
17 
18 class ViewportLayout : ILayout
19 {
20 	/// Called by widget when MinimizeLayout event occurs.
21 	void minimize(Widget root)
22 	{
23 		Widget[] children = root.getPropertyAs!("children", Widget[]);
24 
25 		if (children.length > 0)
26 		{
27 			ivec2 childSize = children[0].getPropertyAs!("prefSize", ivec2);
28 
29 			root.setProperty!("prefSize")(ivec2(childSize.x, 0));
30 		}
31 	}
32 
33 	/// Called by widget when ExpandLayout event occurs.
34 	void expand(Widget root)
35 	{
36 		Widget[] children = root.getPropertyAs!("children", Widget[]);
37 
38 		if (children.length > 0)
39 		{
40 			ivec2 childSize = children[0].getPropertyAs!("prefSize", ivec2);
41 			ivec2 childMinSize = children[0].getPropertyAs!("minSize", ivec2);
42 			childSize = ivec2(max(childSize.x, childMinSize.x), max(childSize.y, childMinSize.y));
43 
44 			children[0].setProperty!("size")(childSize);
45 		}
46 	}
47 
48 	/// Called by container to update its children positions and sizes.
49 	void onContainerResized(Widget root, ivec2 oldSize, ivec2 newSize)
50 	{
51 
52 	}
53 }
54 
55 class BasicListBehavior : IWidgetBehavior
56 {
57 protected:
58 
59 	Widget _viewport;
60 	Widget _canvas;
61 	Widget _vertscroll;
62 
63 public:
64 
65 	override void attachTo(Widget widget)
66 	{
67 		_viewport = widget["subwidgets"].get!(Widget[string]).get("viewport", null);
68 		_canvas = widget["subwidgets"].get!(Widget[string]).get("canvas", null);
69 		_vertscroll = widget["subwidgets"].get!(Widget[string]).get("vert-scroll", null);
70 	}
71 }
72 
73 class WidgetListBehavior : BasicListBehavior
74 {
75 	override void attachTo(Widget widget)
76 	{
77 		super.attachTo(widget);
78 
79 		if (_viewport && _canvas && _vertscroll)
80 		{
81 			_viewport.setProperty!("layout", ILayout)(new ViewportLayout);
82 			_viewport.property("size").valueChanged.connect(&updateSize);
83 			_canvas.property("size").valueChanged.connect(&updateSize);
84 			_vertscroll.property("sliderPos").valueChanged.connect(&sliderMoved);
85 
86 			sliderMoved(null, _vertscroll.property("sliderPos").value);
87 		}
88 	}
89 
90 	void sliderMoved(FlexibleObject widget, Variant position)
91 	{
92 		ivec2 viewSize = _viewport.getPropertyAs!("size", ivec2);
93 		ivec2 canvasSize = _canvas.getPropertyAs!("size", ivec2);
94 
95 		int avalPos = canvasSize.y - viewSize.y;
96 		int newCanvasPos = avalPos < 0 ? 0 : -cast(int)(position.get!double * avalPos);
97 
98 		_canvas.setProperty!("position")(ivec2(0, newCanvasPos));
99 	}
100 
101 	void updateSize(FlexibleObject widget, Variant size)
102 	{
103 		_vertscroll.setProperty!"sliderSize"(scrollSize());
104 	}
105 
106 	double scrollSize()
107 	{
108 		ivec2 viewSize = _viewport.getPropertyAs!("size", ivec2);
109 		ivec2 canvasSize = _canvas.getPropertyAs!("size", ivec2);
110 
111 		return cast(double)viewSize.y / canvasSize.y;
112 	}
113 }
114 
115 
116 class StringListBehavior : BasicListBehavior
117 {
118 	alias StringList = List!dstring;
119 
120 protected:
121 	StringList _list;
122 	size_t itemsPerPage; // Number of items that are visible in viewport.
123 	size_t firstVisible;
124 
125 	uint _itemHeight = 16;
126 	size_t numLabels = 0;
127 	GuiContext context;
128 
129 public:
130 
131 	override void attachPropertiesTo(Widget widget)
132 	{
133 		widget.property("list").valueChanged.connect(&listAssigned);
134 	}
135 
136 	void listAssigned(FlexibleObject widget, Variant newList)
137 	{
138 		if (newList.type !is typeid(StringList)) return;
139 
140 		auto oldList = _list;
141 
142 		_list = newList.get!StringList;
143 
144 		if (_list is null && oldList !is null)
145 		{
146 			oldList.listChangedSignal.disconnectAll();
147 		}
148 
149 		refreshItems();
150 
151 		if (_list is null) return;
152 
153 		_list.listChangedSignal.connect((){refreshItems();});
154 	}
155 
156 	override void attachTo(Widget widget)
157 	{
158 		super.attachTo(widget);
159 
160 		//writeln(_viewport , _canvas , _vertscroll);
161 
162 		if (_viewport && _canvas && _vertscroll)
163 		{
164 			context = _viewport.getPropertyAs!("context", GuiContext);
165 
166 			_viewport.setProperty!("layout", ILayout)(new ViewportLayout);
167 			_viewport.property("size").valueChanged.connect(&updateSize);
168 			_vertscroll.property("sliderPos").valueChanged.connect(&sliderMoved);
169 
170 			sliderMoved(null, _vertscroll.property("sliderPos").value);
171 
172 			refreshItems();
173 		}
174 	}
175 
176 	void refreshPageSize()
177 	{
178 		if (_list is null)
179 		{
180 			itemsPerPage = 0;
181 		}
182 
183 		ivec2 viewSize = _viewport.getPropertyAs!("size", ivec2);
184 
185 		itemsPerPage = cast(size_t)(cast(real)viewSize.y / _itemHeight).ceil;
186 
187 		if (itemsPerPage > numLabels)
188 		{
189 			foreach (_; 0..itemsPerPage - numLabels)
190 			{
191 				context.createWidget("label", _canvas);
192 			}
193 		}
194 		else if (itemsPerPage < numLabels)
195 		{
196 			Widget[] labels = _canvas.getPropertyAs!("children", Widget[]);
197 
198 			labels.length = itemsPerPage;
199 
200 			_canvas.setProperty!("position")(ivec2(0, 0));
201 		}
202 
203 		numLabels = itemsPerPage;
204 
205 		if (_list is null) return;
206 
207 		double sliderSize = cast(double)itemsPerPage / _list.length;
208 		_vertscroll.setProperty!("sliderSize", double)(sliderSize);
209 	}
210 
211 	void refreshPagePos()
212 	{
213 		if (_list is null) return;
214 
215 		double sliderPos = _vertscroll.getPropertyAs!("sliderPos", double);
216 
217 		ptrdiff_t visible = cast(ptrdiff_t)(sliderPos * (_list.length - itemsPerPage));
218 		firstVisible = visible < 0 ? 0 : visible;
219 
220 		if (_list.length > itemsPerPage)
221 		{
222 			ivec2 viewSize = _viewport.getPropertyAs!("size", ivec2);
223 			long canvasSize = _list.length * _itemHeight;
224 			long viewPos = cast(long)(sliderPos * cast(double)(canvasSize - viewSize.y));
225 
226 			_canvas.setProperty!("position")(ivec2(0, cast(int)(firstVisible*_itemHeight - viewPos)));
227 		}
228 		else
229 		{
230 			_canvas.setProperty!("position")(ivec2(0, 0));
231 		}
232 	}
233 
234 	void addItems()
235 	{
236 		if (_list is null) return;
237 
238 		size_t listLength = _list.length;
239 		size_t itemsToShow = itemsPerPage < listLength ? itemsPerPage : listLength;
240 
241 		Widget[] labels = _canvas.getPropertyAs!("children", Widget[]);
242 
243 		foreach(itemIndex; firstVisible..firstVisible + itemsToShow)
244 		{
245 			labels[itemIndex - firstVisible].setProperty!"text"(_list[itemIndex]);
246 		}
247 	}
248 
249 	void refreshItems()
250 	{
251 		refreshPageSize();
252 		refreshPagePos();
253 		addItems();
254 	}
255 
256 	void sliderMoved(FlexibleObject widget, Variant position)
257 	{
258 		if (_list is null) return;
259 
260 		ivec2 viewSize = _viewport.getPropertyAs!("size", ivec2);
261 
262 		long avalPos = _list.length * _itemHeight - viewSize.y;
263 
264 		refreshItems();
265 	}
266 
267 	void updateSize(FlexibleObject widget, Variant size)
268 	{
269 		refreshItems();
270 	}
271 
272 	double scrollSize()
273 	{
274 		ivec2 viewSize = _viewport.getPropertyAs!("size", ivec2);
275 
276 		return cast(double)viewSize.y / _list.length * _itemHeight;
277 	}
278 }