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 }