1 // Written in the D programming language. 2 3 /** 4 DLANGUI library. 5 6 This module contains common layouts implementations. 7 8 Layouts are similar to the same in Android. 9 10 LinearLayout - either VerticalLayout or HorizontalLayout. 11 FrameLayout - children occupy the same place, usually one one is visible at a time 12 13 14 Synopsis: 15 16 ---- 17 import dlangui.widgets.layouts; 18 19 ---- 20 21 Copyright: Vadim Lopatin, 2014 22 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 23 Authors: $(WEB coolreader.org, Vadim Lopatin) 24 */ 25 module dlangui.widgets.layouts; 26 27 public import dlangui.widgets.widget; 28 29 /// helper for layouts 30 struct LayoutItem { 31 Widget _widget; 32 Orientation _orientation; 33 int _measuredSize; // primary size for orientation 34 int _secondarySize; // other measured size 35 int _layoutSize; // layout size for primary dimension 36 int _minSize; // min size for primary dimension 37 int _maxSize; // max size for primary dimension 38 int _weight; // weight 39 bool _fillParent; 40 @property int measuredSize() { return _measuredSize; } 41 @property int minSize() { return _measuredSize; } 42 @property int maxSize() { return _maxSize; } 43 @property int layoutSize() { return _layoutSize; } 44 @property int secondarySize() { return _layoutSize; } 45 @property bool fillParent() { return _fillParent; } 46 @property int weight() { return _weight; } 47 // just to help GC 48 void clear() { 49 _widget = null; 50 } 51 /// sets item for widget 52 void set(Widget widget, Orientation orientation) { 53 _widget = widget; 54 _orientation = orientation; 55 } 56 /// set item and measure it 57 void measure(int parentWidth, int parentHeight) { 58 _widget.measure(parentWidth, parentHeight); 59 _weight = _widget.layoutWeight; 60 if (_orientation == Orientation.Horizontal) { 61 _secondarySize = _widget.measuredHeight; 62 _measuredSize = _widget.measuredWidth; 63 _minSize = _widget.minWidth; 64 _maxSize = _widget.maxWidth; 65 _layoutSize = _widget.layoutWidth; 66 } else { 67 _secondarySize = _widget.measuredWidth; 68 _measuredSize = _widget.measuredHeight; 69 _minSize = _widget.minHeight; 70 _maxSize = _widget.maxHeight; 71 _layoutSize = _widget.layoutHeight; 72 } 73 _fillParent = _layoutSize == FILL_PARENT; 74 } 75 void layout(ref Rect rc) { 76 _widget.layout(rc); 77 } 78 } 79 80 /// helper class for layouts 81 class LayoutItems { 82 Orientation _orientation; 83 LayoutItem[] _list; 84 int _count; 85 int _totalSize; 86 int _maxSecondarySize; 87 Point _measureParentSize; 88 89 int _layoutWidth; 90 int _layoutHeight; 91 92 void setLayoutParams(Orientation orientation, int layoutWidth, int layoutHeight) { 93 _orientation = orientation; 94 _layoutWidth = layoutWidth; 95 _layoutHeight = layoutHeight; 96 } 97 98 /// fill widget layout list with Visible or Invisible items, measure them 99 Point measure(int parentWidth, int parentHeight) { 100 _totalSize = 0; 101 _maxSecondarySize = 0; 102 _measureParentSize.x = parentWidth; 103 _measureParentSize.y = parentHeight; 104 // measure 105 for (int i = 0; i < _count; i++) { 106 LayoutItem * item = &_list[i]; 107 item.measure(parentWidth, parentHeight); 108 if (_maxSecondarySize < item._secondarySize) 109 _maxSecondarySize = item._secondarySize; 110 _totalSize += item._measuredSize; 111 } 112 return _orientation == Orientation.Horizontal ? Point(_totalSize, _maxSecondarySize) : Point(_maxSecondarySize, _totalSize); 113 } 114 115 /// fill widget layout list with Visible or Invisible items, measure them 116 void setWidgets(ref WidgetList widgets) { 117 // remove old items, if any 118 clear(); 119 // reserve space 120 if (_list.length < widgets.count) 121 _list.length = widgets.count; 122 // copy 123 for (int i = 0; i < widgets.count; i++) { 124 Widget item = widgets.get(i); 125 if (item.visibility == Visibility.Gone) 126 continue; 127 _list[_count++].set(item, _orientation); 128 } 129 } 130 131 void layout(Rect rc) { 132 // measure again - available area could be changed 133 if (_measureParentSize.x != rc.width || _measureParentSize.y != rc.height) 134 measure(rc.width, rc.height); 135 int contentSecondarySize = 0; 136 int contentHeight = 0; 137 int totalSize = 0; 138 int delta = 0; 139 int resizableSize = 0; 140 int resizableWeight = 0; 141 int nonresizableSize = 0; 142 int nonresizableWeight = 0; 143 int maxItem = 0; // max item dimention 144 // calc total size 145 int visibleCount = cast(int)_list.length; 146 for (int i = 0; i < _count; i++) { 147 LayoutItem * item = &_list[i]; 148 int weight = item.weight; 149 int size = item.measuredSize; 150 totalSize += size; 151 if (maxItem < item.secondarySize) 152 maxItem = item.secondarySize; 153 if (item.fillParent) { 154 resizableWeight += weight; 155 resizableSize += size * weight; 156 } else { 157 nonresizableWeight += weight; 158 nonresizableSize += size * weight; 159 } 160 } 161 if (_orientation == Orientation.Vertical) { 162 if (_layoutWidth == WRAP_CONTENT && maxItem < rc.width) 163 contentSecondarySize = maxItem; 164 else 165 contentSecondarySize = rc.width; 166 if (_layoutHeight == FILL_PARENT || totalSize > rc.height) 167 delta = rc.height - totalSize; // total space to add to fit 168 } else { 169 if (_layoutHeight == WRAP_CONTENT && maxItem < rc.height) 170 contentSecondarySize = maxItem; 171 else 172 contentSecondarySize = rc.height; 173 if (_layoutWidth == FILL_PARENT || totalSize > rc.width) 174 delta = rc.width - totalSize; // total space to add to fit 175 } 176 // calculate resize options and scale 177 bool needForceResize = false; 178 bool needResize = false; 179 int scaleFactor = 10000; // per weight unit 180 if (delta != 0 && visibleCount > 0) { 181 // need resize of some children 182 needResize = true; 183 // resize all if need to shrink or only resizable are too small to correct delta 184 needForceResize = delta < 0 || resizableWeight == 0; // || resizableSize * 2 / 3 < delta; // do we need resize non-FILL_PARENT items? 185 // calculate scale factor: weight / delta * 10000 186 if (needForceResize && nonresizableSize + resizableSize > 0) 187 scaleFactor = 10000 * delta / (nonresizableSize + resizableSize); 188 else if (resizableSize > 0) 189 scaleFactor = 10000 * delta / resizableSize; 190 else 191 scaleFactor = 0; 192 } 193 //Log.d("VerticalLayout delta=", delta, ", nonres=", nonresizableWeight, ", res=", resizableWeight, ", scale=", scaleFactor); 194 // find last resized - to allow fill space 1 pixel accurate 195 int lastResized = -1; 196 for (int i = 0; i < _count; i++) { 197 LayoutItem * item = &_list[i]; 198 if (item.fillParent || needForceResize) { 199 lastResized = i; 200 } 201 } 202 // final resize and layout of children 203 int position = 0; 204 int deltaTotal = 0; 205 for (int i = 0; i < _count; i++) { 206 LayoutItem * item = &_list[i]; 207 int layoutSize = item.layoutSize; 208 int weight = item.weight; 209 int size = item.measuredSize; 210 if (needResize && (layoutSize == FILL_PARENT || needForceResize)) { 211 // do resize 212 int correction = scaleFactor * weight * size / 10000; 213 deltaTotal += correction; 214 // for last resized, apply additional correction to resolve calculation inaccuracy 215 if (i == lastResized) { 216 correction += delta - deltaTotal; 217 } 218 size += correction; 219 } 220 // apply size 221 Rect childRect = rc; 222 if (_orientation == Orientation.Vertical) { 223 // Vertical 224 childRect.top += position; 225 childRect.bottom = childRect.top + size; 226 childRect.right = childRect.left + contentSecondarySize; 227 item.layout(childRect); 228 } else { 229 // Horizontal 230 childRect.left += position; 231 childRect.right = childRect.left + size; 232 childRect.bottom = childRect.top + contentSecondarySize; 233 item.layout(childRect); 234 } 235 position += size; 236 } 237 } 238 239 void clear() { 240 for (int i = 0; i < _count; i++) 241 _list[i].clear(); 242 _count = 0; 243 } 244 ~this() { 245 clear(); 246 } 247 } 248 249 class LinearLayout : WidgetGroup { 250 protected Orientation _orientation = Orientation.Vertical; 251 /// returns linear layout orientation (Vertical, Horizontal) 252 @property Orientation orientation() { return _orientation; } 253 /// sets linear layout orientation 254 @property LinearLayout orientation(Orientation value) { _orientation = value; requestLayout(); return this; } 255 256 this(string ID = null) { 257 super(ID); 258 _layoutItems = new LayoutItems(); 259 } 260 261 LayoutItems _layoutItems; 262 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 263 override void measure(int parentWidth, int parentHeight) { 264 Rect m = margins; 265 Rect p = padding; 266 // calc size constraints for children 267 int pwidth = parentWidth; 268 int pheight = parentHeight; 269 if (parentWidth != SIZE_UNSPECIFIED) 270 pwidth -= m.left + m.right + p.left + p.right; 271 if (parentHeight != SIZE_UNSPECIFIED) 272 pheight -= m.top + m.bottom + p.top + p.bottom; 273 // measure children 274 _layoutItems.setLayoutParams(orientation, layoutWidth, layoutHeight); 275 _layoutItems.setWidgets(_children); 276 Point sz = _layoutItems.measure(pwidth, pheight); 277 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 278 } 279 280 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 281 override void layout(Rect rc) { 282 _needLayout = false; 283 if (visibility == Visibility.Gone) { 284 return; 285 } 286 _pos = rc; 287 applyMargins(rc); 288 applyPadding(rc); 289 _layoutItems.layout(rc); 290 } 291 /// Draw widget at its position to buffer 292 override void onDraw(DrawBuf buf) { 293 if (visibility != Visibility.Visible) 294 return; 295 super.onDraw(buf); 296 Rect rc = _pos; 297 applyMargins(rc); 298 applyPadding(rc); 299 auto saver = ClipRectSaver(buf, rc); 300 for (int i = 0; i < _children.count; i++) { 301 Widget item = _children.get(i); 302 if (item.visibility != Visibility.Visible) 303 continue; 304 item.onDraw(buf); 305 } 306 } 307 308 } 309 310 class VerticalLayout : LinearLayout { 311 this(string ID = null) { 312 super(ID); 313 orientation = Orientation.Vertical; 314 } 315 } 316 317 class HorizontalLayout : LinearLayout { 318 this(string ID = null) { 319 super(ID); 320 orientation = Orientation.Horizontal; 321 } 322 } 323 324 /// place all children into same place (usually, only one child should be visible at a time) 325 class FrameLayout : WidgetGroup { 326 this(string ID) { 327 super(ID); 328 } 329 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 330 override void measure(int parentWidth, int parentHeight) { 331 Rect m = margins; 332 Rect p = padding; 333 // calc size constraints for children 334 int pwidth = parentWidth; 335 int pheight = parentHeight; 336 if (parentWidth != SIZE_UNSPECIFIED) 337 pwidth -= m.left + m.right + p.left + p.right; 338 if (parentHeight != SIZE_UNSPECIFIED) 339 pheight -= m.top + m.bottom + p.top + p.bottom; 340 // measure children 341 Point sz; 342 for (int i = 0; i < _children.count; i++) { 343 Widget item = _children.get(i); 344 if (item.visibility != Visibility.Gone) { 345 item.measure(pwidth, pheight); 346 if (sz.x < item.measuredWidth) 347 sz.x = item.measuredWidth; 348 if (sz.y < item.measuredHeight) 349 sz.y = item.measuredHeight; 350 } 351 } 352 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 353 } 354 355 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 356 override void layout(Rect rc) { 357 _needLayout = false; 358 if (visibility == Visibility.Gone) { 359 return; 360 } 361 _pos = rc; 362 applyMargins(rc); 363 applyPadding(rc); 364 for (int i = 0; i < _children.count; i++) { 365 Widget item = _children.get(i); 366 if (item.visibility != Visibility.Gone) { 367 item.layout(rc); 368 } 369 } 370 } 371 372 /// Draw widget at its position to buffer 373 override void onDraw(DrawBuf buf) { 374 if (visibility != Visibility.Visible) 375 return; 376 super.onDraw(buf); 377 Rect rc = _pos; 378 applyMargins(rc); 379 applyPadding(rc); 380 auto saver = ClipRectSaver(buf, rc); 381 for (int i = 0; i < _children.count; i++) { 382 Widget item = _children.get(i); 383 if (item.visibility != Visibility.Visible) 384 continue; 385 item.onDraw(buf); 386 } 387 } 388 389 /// make one of children (with specified ID) visible, for the rest, set visibility to otherChildrenVisibility 390 bool showChild(string ID, Visibility otherChildrenVisibility = Visibility.Invisible, bool updateFocus = false) { 391 bool found = false; 392 Widget foundWidget = null; 393 for (int i = 0; i < _children.count; i++) { 394 Widget item = _children.get(i); 395 if (item.compareId(ID)) { 396 item.visibility = Visibility.Visible; 397 foundWidget = item; 398 found = true; 399 } else { 400 item.visibility = otherChildrenVisibility; 401 } 402 } 403 if (foundWidget !is null && updateFocus) 404 foundWidget.setFocus(); 405 return found; 406 } 407 }