1 module app;
2 
3 import dlangui;
4 
5 mixin APP_ENTRY_POINT;
6 
7 import dlangui.widgets.scroll;
8 
9 class DragonView : ScrollWidget {
10 
11     private {
12         int _scaleX;
13         int _scaleY;
14         int _middleX;
15         int _middleY;
16         int _dx;
17         int _dy;
18         int _x0;
19         int _y0;
20         int _length = 256;
21         int _dir0 = 0; // either 0 or 1
22         int _straightLen = 10;
23         int _roundLen = 4;
24         uint _bgcolor = 0x101010;
25         uint _grid1color = 0x303030;
26         uint _grid2color = 0x202020;
27         uint _grid3color = 0x181818;
28         uint _curve1color = 0x4050FF;
29         uint _curve2color = 0xFF4040;
30         uint _curve3color = 0x30C020;
31         uint _curve4color = 0xC000D0;
32         bool[4] _partVisible = [true, true, true, true];
33 
34         bool _needRepaint = true;
35         Point[8] _directionVectors;
36     }
37 
38 
39     ColorDrawBuf _drawBuf;
40 
41     this(string ID) {
42         super(ID);
43         fillParent();
44         //_fullScrollableArea.right = 2048;
45         //_fullScrollableArea.bottom = 2048;
46         setVectors();
47         resize(2048, 2048);
48     }
49 
50     void resize(int dx, int dy) {
51         _dx = dx;
52         _dy = dy;
53         _fullScrollableArea.right = dx;
54         _fullScrollableArea.bottom = dy;
55         _visibleScrollableArea.left = dx / 2 - 400;
56         _visibleScrollableArea.top = dy / 2 - 300;
57         _visibleScrollableArea.right = _visibleScrollableArea.left + 400;
58         _visibleScrollableArea.bottom = _visibleScrollableArea.top + 400;
59 
60         if (!_drawBuf) {
61             _drawBuf = new ColorDrawBuf(_fullScrollableArea.width, _fullScrollableArea.height);
62         } else {
63             _drawBuf.resize(dx, dy);
64         }
65         _middleX = _fullScrollableArea.width / 2;
66         _middleY = _fullScrollableArea.height / 2;
67         _needRepaint = true;
68         drawCurve();
69     }
70 
71     void setVectors() {
72         setVectors(_straightLen, _roundLen);
73     }
74     void setVectors(int straightLen, int roundLen) {
75         if (!straightLen && !roundLen)
76             straightLen = 1;
77         setVectors([
78             Point(straightLen, 0),
79             Point(roundLen, -roundLen),
80             Point(0, -straightLen),
81             Point(-roundLen, -roundLen),
82             Point(-straightLen, 0),
83             Point(-roundLen, roundLen),
84             Point(0, straightLen),
85             Point(roundLen, roundLen),
86         ]);
87     }
88 
89     void setVectors(Point[8] vectors) {
90         import std.math : abs;
91         _directionVectors = vectors;
92         int maxx1, maxx2, maxy1, maxy2;
93         for(int i = 0; i < 8; i += 2) {
94             if (maxx1 == 0 || maxx1 < abs(_directionVectors[i].x))
95                 maxx1 = abs(_directionVectors[i].x);
96             if (maxy1 == 0 || maxy1 < abs(_directionVectors[i].y))
97                 maxy1 = abs(_directionVectors[i].y);
98             if (maxx2 == 0 || maxx1 < abs(_directionVectors[i + 1].x))
99                 maxx2 = abs(_directionVectors[i + 1].x);
100             if (maxy2 == 0 || maxy1 < abs(_directionVectors[i + 1].y))
101                 maxy2 = abs(_directionVectors[i + 1].y);
102         }
103         if (_dir0 == 0) {
104             _scaleX = maxx1 + maxx2 * 2;
105             _scaleY = maxy1 + maxy2 * 2;
106         } else {
107             _scaleX = maxx1 + maxx2;
108             _scaleY = maxy1 + maxy2;
109         }
110         _x0 = vectors[1].x;
111         _y0 = vectors[1].y;
112         repaint();
113     }
114 
115     bool getPartVisible(int n) {
116         return _partVisible[n & 3];
117     }
118     void setPartVisible(int n, bool flgVisible) {
119         n = n & 3;
120         if (_partVisible[n] != flgVisible) {
121             _partVisible[n] = flgVisible;
122             repaint();
123         }
124     }
125 
126     @property int straightLen() {
127         return _straightLen;
128     }
129     @property DragonView straightLen(int n) {
130         if (_straightLen != n) {
131             _straightLen = n;
132             setVectors();
133         }
134         return this;
135     }
136     @property int roundLen() {
137         return _roundLen;
138     }
139     @property DragonView roundLen(int n) {
140         if (_roundLen != n) {
141             _roundLen = n;
142             setVectors();
143         }
144         return this;
145     }
146     @property int length() {
147         return _length;
148     }
149     @property DragonView length(int n) {
150         if (_length != n) {
151             _length = n;
152             repaint();
153         }
154         return this;
155     }
156     void repaint() {
157         _needRepaint = true;
158         invalidate();
159     }
160     @property int rotation() {
161         return _dir0;
162     }
163     @property DragonView rotation(int angle) {
164         if (_dir0 != (angle & 7)) {
165             _dir0 = angle & 7;
166             _needRepaint = true;
167             repaint();
168         }
169         return this;
170     }
171 
172     /// Draw widget at its position to buffer
173     override void onDraw(DrawBuf buf) {
174         if (_needRepaint)
175             drawCurve();
176         super.onDraw(buf);
177     }
178 
179     void drawLine(Point pt1, Point pt2, uint color) {
180         pt1.x += _middleX;
181         pt2.x += _middleX;
182         pt1.y = _middleY - pt1.y;
183         pt2.y = _middleY - pt2.y;
184         _drawBuf.drawLine(pt1, pt2, color);
185     }
186 
187     void drawBackground() {
188         _drawBuf.fill(_bgcolor);
189         int i = 0;
190         for (int x = 0; x < _middleX; x += _scaleX) {
191             uint color = _scaleX > 2 ? _grid3color : COLOR_TRANSPARENT;
192             if (i == 0)
193                 color = _grid1color;
194             else if ((i & 15) == 0)
195                 color = _grid2color;
196             if (color != COLOR_TRANSPARENT) {
197                 drawLine(Point(x, -_middleY), Point(x, _middleY), color);
198                 drawLine(Point(-x, -_middleY), Point(-x, _middleY), color);
199                 if (x == 0) {
200                     drawLine(Point(x - 1, -_middleY), Point(x - 1, _middleY), color);
201                     drawLine(Point(x + 1, -_middleY), Point(x + 1, _middleY), color);
202                 }
203             }
204             i++;
205         }
206         i = 0;
207         for (int y = 0; y < _middleY; y += _scaleY) {
208             uint color = _scaleY > 2 ? _grid3color : COLOR_TRANSPARENT;
209             if (i == 0)
210                 color = _grid1color;
211             else if ((i & 15) == 0)
212                 color = _grid2color;
213             if (color != COLOR_TRANSPARENT) {
214                 drawLine(Point(-_middleX, y), Point(_middleX, y), color);
215                 drawLine(Point(-_middleX, -y), Point(_middleX, -y), color);
216                 if (y == 0) {
217                     drawLine(Point(-_middleX, y - 1), Point(_middleX, y - 1), color);
218                     drawLine(Point(-_middleX, y + 1), Point(_middleX, y + 1), color);
219                 }
220             }
221             i++;
222         }
223     }
224 
225     int getDirectionDelta(int n) {
226         if (n == 0)
227             return -1;
228         for (int i = 0; i < 30; i++) {
229             if (n & (1 << i)) {
230                 return (n & (2 << i)) ? 1 : -1;
231             }
232         }
233         return 0;
234     }
235 
236     void drawSegment(ref Point currentPoint, ref int currentDir, int n, uint color, int mirror) {
237         int mx = _middleX + _scaleX * 2;
238         int my = _middleY + _scaleY * 2;
239         bool insideView = currentPoint.x >= -mx  && currentPoint.x <= mx && currentPoint.y >= -my && currentPoint.y <= my;
240         int delta = getDirectionDelta(n) * mirror;
241         Point nextPoint = currentPoint + _directionVectors[currentDir];
242         if (insideView)
243             drawLine(currentPoint, nextPoint, color);
244         currentPoint = nextPoint;
245         currentDir = (currentDir + delta) & 7;
246         nextPoint = currentPoint + _directionVectors[currentDir];
247         if (insideView)
248             drawLine(currentPoint, nextPoint, color);
249         currentPoint = nextPoint;
250         currentDir = (currentDir + delta) & 7;
251     }
252 
253     void drawLines() {
254         int dir;
255         Point p0;
256         Point pt;
257         if (_dir0 == 0)
258             p0 = Point(0, _directionVectors[_dir0 + 1].y);
259         else
260             p0 = Point(-_directionVectors[0].x / 2, _directionVectors[_dir0 + 1].y / 2);
261         // segment 1
262         if (_partVisible[0]) {
263             dir = 0 + _dir0;
264             pt = p0 - (_directionVectors[dir] + _directionVectors[dir + 1]);
265             for(int i = 0; i < _length; i++)
266                 drawSegment(pt, dir, i, _curve1color, 1);
267         }
268         // segment 2
269         if (_partVisible[1]) {
270             dir = 4 + _dir0;
271             pt = p0 + _directionVectors[dir + 1];
272             for(int i = -1; i > -_length; i--)
273                 drawSegment(pt, dir, i, _curve2color, -1);
274         }
275         // segment 3
276         if (_partVisible[2]) {
277             dir = 4 + _dir0;
278             pt = p0 - (_directionVectors[dir - 1] + _directionVectors[dir]);
279             for(int i = 0; i < _length; i++)
280                 drawSegment(pt, dir, i, _curve3color, 1);
281         }
282         // segment 4
283         if (_partVisible[3]) {
284             dir = 0 + _dir0;
285             pt = p0 + _directionVectors[(dir - 1) & 7];
286             for(int i = -1; i > -_length; i--)
287                 drawSegment(pt, dir, i, _curve4color, -1);
288         }
289     }
290     void drawCurve() {
291         drawBackground();
292         drawLines();
293         _needRepaint = false;
294     }
295 
296     /// calculate full content size in pixels
297     override Point fullContentSize() {
298         Point sz = Point(_fullScrollableArea.width, _fullScrollableArea.height);
299         return sz;
300     }
301 
302     override protected void drawClient(DrawBuf buf) {
303         Point sz = fullContentSize();
304         Point p = scrollPos;
305         //_contentWidget.layout(Rect(_clientRect.left - p.x, _clientRect.top - p.y, _clientRect.left + sz.x - p.x, _clientRect.top + sz.y - p.y));
306         //_contentWidget.onDraw(buf);
307         /// draw source buffer rectangle contents to destination buffer
308         buf.drawFragment(_clientRect.left, _clientRect.top, _drawBuf, 
309                          Rect(_visibleScrollableArea.left, _visibleScrollableArea.top, 
310                               _visibleScrollableArea.left + _clientRect.width, _visibleScrollableArea.top + _clientRect.height));
311         //Rect rc = _clientRect;
312         //rc.shrink(5, 5);
313         //buf.fillRect(rc, 0xFF8080);
314     }
315 
316 
317 }
318 
319 immutable SLIDER_ACCELERATION_STEP = 64;
320 
321 int sliderToSize(int n) {
322     int limit = 0;
323     int total = 0;
324     int factor = 1;
325     for(;;) {
326         int newlimit = limit + SLIDER_ACCELERATION_STEP;
327         if (n < newlimit) {
328             return total + (n - limit) * factor;
329         }
330         total += SLIDER_ACCELERATION_STEP * factor;
331         limit = newlimit;
332         factor *= 2;
333     }
334 }
335 
336 int sizeToSlider(int n) {
337     int limit = 0;
338     int total = 0;
339     int factor = 1;
340     for(;;) {
341         int newlimit = limit + SLIDER_ACCELERATION_STEP;
342         int newtotal = total + SLIDER_ACCELERATION_STEP * factor;
343         if (n < newtotal) {
344             return limit + (n - total) / factor;
345         }
346         total = newtotal;
347         limit = newlimit;
348         factor *= 2;
349     }
350 }
351 
352 /// entry point for dlangui based application
353 extern (C) int UIAppMain(string[] args) {
354 
355     // create window
356     Window window = Platform.instance.createWindow("DlangUI example : Dragon Curve"d, null, WindowFlag.Resizable, 800, 600);
357     int n = sliderToSize(1000);
358     int n2 = sizeToSlider(n);
359 
360     DragonView dragon = new DragonView("DRAGON_VIEW");
361 
362     auto onScrollEvent = delegate(AbstractSlider source, ScrollEvent event) {
363         if (event.action == ScrollAction.SliderMoved) {
364             switch(source.id) {
365                 case "straight":
366                     dragon.straightLen = event.position;
367                     break;
368                 case "round":
369                     dragon.roundLen = event.position;
370                     break;
371                 case "size":
372                     dragon.length = sliderToSize(event.position);
373                     break;
374                 default:
375                     break;
376             }
377         }
378         return true;
379     };
380 
381     auto content = new VerticalLayout().fillParent;
382     auto controls1 = new HorizontalLayout().fillHorizontal.padding(3.pointsToPixels).backgroundColor(0xD8D8D8);
383 
384     controls1.addChild(new TextWidget(null," Straight"d));
385     auto sliderStraight = new SliderWidget("straight");
386     sliderStraight.setRange(0, 20).position(dragon.straightLen).layoutWeight(1).fillHorizontal;
387     sliderStraight.scrollEvent = onScrollEvent;
388     controls1.addChild(sliderStraight);
389 
390     controls1.addChild(new TextWidget(null," Rounding"d));
391     auto sliderRound = new SliderWidget("round");
392     sliderRound.setRange(0, 20).position(dragon.roundLen).layoutWeight(1).fillHorizontal;
393     sliderRound.scrollEvent = onScrollEvent;
394     controls1.addChild(sliderRound);
395 
396     auto cbRotate = new CheckBox(null, " Rotate 45`"d);
397     controls1.addChild(cbRotate).checked(dragon.rotation ? true : false);
398     cbRotate.checkChange = delegate(Widget w, bool check) { 
399             dragon.rotation(check ? 1 : 0); return true; 
400     };
401     auto cbPartVisible0 = new CheckBox(null, " A1"d);
402     controls1.addChild(cbPartVisible0).checked(dragon.getPartVisible(0));
403     cbPartVisible0.checkChange = delegate(Widget w, bool check) { 
404         dragon.setPartVisible(0, check); return true; 
405     };
406     auto cbPartVisible1 = new CheckBox(null, " A2"d);
407     controls1.addChild(cbPartVisible1).checked(dragon.getPartVisible(1));
408     cbPartVisible1.checkChange = delegate(Widget w, bool check) { 
409         dragon.setPartVisible(1, check); return true; 
410     };
411     auto cbPartVisible2 = new CheckBox(null, " B1"d);
412     controls1.addChild(cbPartVisible2).checked(dragon.getPartVisible(2));
413     cbPartVisible2.checkChange = delegate(Widget w, bool check) { 
414         dragon.setPartVisible(2, check); return true; 
415     };
416     auto cbPartVisible3 = new CheckBox(null, " B2"d);
417     controls1.addChild(cbPartVisible3).checked(dragon.getPartVisible(3));
418     cbPartVisible3.checkChange = delegate(Widget w, bool check) { 
419         dragon.setPartVisible(3, check); return true; 
420     };
421 
422     controls1.addChild(new TextWidget(null," Size"d));
423     auto sliderSize = new SliderWidget("size");
424     sliderSize.setRange(2, 1000).position(sizeToSlider(dragon.length)).layoutWeight(10).fillHorizontal;
425     sliderSize.scrollEvent = onScrollEvent;
426     controls1.addChild(sliderSize);
427 
428     content.addChildren([controls1, dragon]);
429 
430     window.mainWidget = content;
431 
432     // show window
433     window.show();
434 
435     // run message loop
436     return Platform.instance.enterMessageLoop();
437 }