1 /+
2 The MIT License (MIT)
3 
4     Copyright (c) <2013> <Oleg Butko (deviator), Anton Akzhigitov (Akzwar)>
5 
6     Permission is hereby granted, free of charge, to any person obtaining a copy
7     of this software and associated documentation files (the "Software"), to deal
8     in the Software without restriction, including without limitation the rights
9     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10     copies of the Software, and to permit persons to whom the Software is
11     furnished to do so, subject to the following conditions:
12 
13     The above copyright notice and this permission notice shall be included in
14     all copies or substantial portions of the Software.
15 
16     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22     THE SOFTWARE.
23 +/
24 module desgui.core.layout;
25 
26 import desgui.core.widget;
27 import desutil.helpers : lim_t;
28 
29 class DiLineLayout : DiLayout
30 {
31     enum Type { HORISONTAL, VERTICAL }
32     enum Align { START, CENTER, END }
33 
34     Align alignDirect = Align.START; 
35     Align alignInderect = Align.CENTER;
36 
37     Type type;
38     int border;
39     int space;
40 
41     bool stretchDirect = true;
42     bool stretchInderect = true;
43 
44     pure this( Type t = Type.HORISONTAL ) { type = t; }
45 
46     void opCall( in irect pr, DiWidget[] wlist )
47     {
48         import std.stdio;
49         DiWidget[] nl;
50         foreach( w; wlist ) 
51             if( w !is null && w.visible )
52                 nl ~= w;
53         wlist = nl;
54 
55         bool hor = type == Type.HORISONTAL;
56         int full_size = hor ? pr.w : pr.h;
57 
58         int ind_size = hor ? pr.h : pr.w;
59 
60         int dim_dir( DiWidget z ) { return hor ? z.rect.w : z.rect.h; }
61         int dim_ind( DiWidget z ) { return hor ? z.rect.h : z.rect.w; }
62 
63         lim_t!int lim_dir( DiWidget z ) { return hor ? z.lims.w : z.lims.h; }
64         lim_t!int lim_ind( DiWidget z ) { return hor ? z.lims.h : z.lims.w; }
65 
66         void new_rect( DiWidget widget, int dir_p, int dir_s )
67         {
68             int b2 = border * 2;
69             auto li = lim_ind(widget);
70             int ind_s = stretchInderect ? ind_size - b2 : dim_ind(widget);
71 
72             ind_s = li( dim_ind(widget), ind_s );
73 
74             auto size = hor ? ivec2( dir_s, ind_s ) : ivec2( ind_s, dir_s );
75             int ind_p;
76             final switch( alignInderect ) 
77             {
78                 case Align.START:  ind_p = border; break;
79                 case Align.CENTER: ind_p = ( ind_size - ind_s ) / 2; break;
80                 case Align.END:    ind_p = ind_size - ind_s - border; break; 
81             }
82             auto pos = hor ? ivec2( dir_p, ind_p ) : ivec2( ind_p, dir_p );
83 
84             widget.reshape( irect( pos, size ) );
85         }
86 
87         int calcStartOffset( int summ_size )
88         {
89             final switch( alignDirect )
90             {
91                 case Align.START:  return border;
92                 case Align.CENTER: return ( full_size - summ_size ) / 2;
93                 case Align.END:    return full_size - summ_size - border;
94             }
95         }
96 
97         int summ_size = full_size;
98         int sess; // single element stretch size
99         int space_size = space * (cast(int)wlist.length - 1);
100 
101         if( stretchDirect )
102         {
103             int fix_size = 0;
104             int fix_cnt = 0;
105 
106             foreach( widget; wlist )
107             {
108                 int fix = cast(int)( lim_dir(widget).fix );
109                 fix_cnt += fix;
110                 fix_size += dim_dir( widget ) * fix;
111             }
112 
113             summ_size = space_size + fix_size;
114 
115             int stretch_cnt = cast(int)( wlist.length - fix_cnt );
116             int stretch_size_base = full_size - fix_size - space_size - border*2;
117 
118             int[2][size_t] limit_sizes;
119 
120             bool calcFunc( bool recalcSESS=false )
121             {
122                 int limit_size = 0;
123                 foreach( key, val; limit_sizes )
124                     limit_size += val[0];
125 
126                 int stretch_size = stretch_size_base - limit_size;
127 
128                 if( stretch_size < 0 ) 
129                 {
130                     sess = 0;
131                     return true;
132                 }
133 
134                 int limit_cnt = cast(int)(limit_sizes.length);
135 
136                 if( (stretch_cnt - limit_cnt) > 0 ) 
137                     sess = stretch_size / (stretch_cnt - limit_cnt);
138                 else return true;
139 
140                 if( recalcSESS ) return true; 
141 
142                 summ_size = space_size;
143 
144                 bool ok = true;
145                 foreach( i, widget; wlist )
146                 {
147                     int dir_s = (lim_dir(widget))( dim_dir(widget), sess );
148                     summ_size += dir_s;
149                     if( !( lim_dir(widget).fix ) )
150                     {
151                         auto diff = sess - dir_s;
152                         if( i in limit_sizes && diff == limit_sizes[i][1] )
153                             continue;
154                         if( diff != 0 )
155                         {
156                             limit_sizes[i] = [ dir_s, diff ];
157                             ok = false;
158                         }
159                     }
160                 }
161                 if( limit_sizes.length == stretch_cnt ) return true;
162                 return ok;
163             }
164 
165             while( !calcFunc() ){}
166         }
167         else
168         {
169             summ_size = space_size;
170             foreach( widget; wlist ) 
171                 summ_size += dim_dir(widget);
172         }
173 
174         int offset = calcStartOffset( summ_size );
175         foreach( widget; wlist )
176         {
177             new_rect( widget, offset, 
178                     ( !stretchDirect || lim_dir(widget).fix ) ? 
179                                               dim_dir(widget) : sess );
180             offset += space + dim_dir(widget);
181         }
182     }
183 }
184 
185 version( unittest )
186 {
187     private class MyWidget: DiWidget
188     {
189         this( DiWidget par )
190         {
191             super( par );
192             reshape(irect( 0, 0, 20, 25 ));
193             size_lim.h.fix = true; 
194         }
195     }
196 }
197 
198 unittest
199 {
200     auto ctx = new TestContext;
201     auto l = new DiLineLayout();
202     scope par = new DiWidget( ctx );
203     par.reshape( irect( 0, 0, 500, 50 ) );
204 
205     par.layout = l;
206 
207     DiWidget[5] ch;
208     foreach( ref c; ch )
209         c = new DiWidget( par );
210 
211     assert( ch[0].rect == irect(   0, 0, 100, 50 ) );
212     assert( ch[1].rect == irect( 100, 0, 100, 50 ) );
213     assert( ch[2].rect == irect( 200, 0, 100, 50 ) );
214     assert( ch[3].rect == irect( 300, 0, 100, 50 ) );
215 
216     import std.string;
217     pragma( msg, format( "%s at %s #%d", "breaking tests", __FILE__, __LINE__ ) );
218     //assert( ch[4].rect == irect( 400, 0, 100, 50 ) );
219 
220     l.type = l.Type.VERTICAL;
221     par.reshape( irect( 0, 0, 50, 500 ) );
222 
223     assert( ch[0].rect == irect( 0,   0, 50, 100 ) );
224     assert( ch[1].rect == irect( 0, 100, 50, 100 ) );
225     assert( ch[2].rect == irect( 0, 200, 50, 100 ) );
226     assert( ch[3].rect == irect( 0, 300, 50, 100 ) );
227     assert( ch[4].rect == irect( 0, 400, 50, 100 ) );
228 
229     foreach( c; ch )
230         c.reshape( irect( 0, 0, 10, 15 ) );
231 
232     l.stretchDirect = false;
233     l.stretchInderect = false;
234 
235     l.alignInderect = l.Align.START;
236     l.alignDirect = l.Align.START;
237     par.relayout();
238 
239     assert( ch[0].rect == irect( 0,  0, 10, 15 ) );
240     assert( ch[1].rect == irect( 0, 15, 10, 15 ) );
241     assert( ch[2].rect == irect( 0, 30, 10, 15 ) );
242     assert( ch[3].rect == irect( 0, 45, 10, 15 ) );
243     assert( ch[4].rect == irect( 0, 60, 10, 15 ) );
244 
245     l.alignInderect = l.Align.CENTER;
246     l.alignDirect = l.Align.CENTER;
247     par.relayout();
248 
249     assert( ch[0].rect == irect( 20, 212, 10, 15 ) );
250     assert( ch[1].rect == irect( 20, 227, 10, 15 ) );
251     assert( ch[2].rect == irect( 20, 242, 10, 15 ) );
252     assert( ch[3].rect == irect( 20, 257, 10, 15 ) );
253     assert( ch[4].rect == irect( 20, 272, 10, 15 ) );
254 
255     l.alignInderect = l.Align.END;
256     l.alignDirect = l.Align.END;
257     par.relayout();
258 
259     assert( ch[0].rect == irect( 40, 425, 10, 15 ) );
260     assert( ch[1].rect == irect( 40, 440, 10, 15 ) );
261     assert( ch[2].rect == irect( 40, 455, 10, 15 ) );
262     assert( ch[3].rect == irect( 40, 470, 10, 15 ) );
263     assert( ch[4].rect == irect( 40, 485, 10, 15 ) );
264 
265     l.space = 2;
266     par.relayout();
267 
268     assert( ch[0].rect == irect( 40, 417, 10, 15 ) );
269     assert( ch[1].rect == irect( 40, 434, 10, 15 ) );
270     assert( ch[2].rect == irect( 40, 451, 10, 15 ) );
271     assert( ch[3].rect == irect( 40, 468, 10, 15 ) );
272     assert( ch[4].rect == irect( 40, 485, 10, 15 ) );
273 
274     l.border = 2;
275     par.relayout();
276 
277     assert( ch[0].rect == irect( 38, 415, 10, 15 ) );
278     assert( ch[1].rect == irect( 38, 432, 10, 15 ) );
279     assert( ch[2].rect == irect( 38, 449, 10, 15 ) );
280     assert( ch[3].rect == irect( 38, 466, 10, 15 ) );
281     assert( ch[4].rect == irect( 38, 483, 10, 15 ) );
282 
283     l.stretchDirect = true;
284     l.stretchInderect = true;
285     l.space = 0;
286     l.border = 0;
287 
288     clear( ch[0] );
289     clear( ch[2] );
290     clear( ch[4] );
291 
292     ch[0] = new MyWidget( par );
293     ch[2] = new MyWidget( par );
294     ch[4] = new MyWidget( par );
295 
296     par.relayout();
297 
298     assert( ch[1].rect == irect( 0,   1, 50, 212 ) );
299     assert( ch[3].rect == irect( 0, 213, 50, 212 ) );
300     assert( ch[0].rect == irect( 0, 425, 50, 25 ) );
301     assert( ch[2].rect == irect( 0, 450, 50, 25 ) );
302     assert( ch[4].rect == irect( 0, 475, 50, 25 ) );
303 }