5
// Lluis Sanchez <lluis@xamarin.com>
7
// Copyright (c) 2011 Xamarin Inc
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
using System.Collections.Generic;
30
using System.ComponentModel;
33
using System.Windows.Markup;
37
public class Box: Widget
39
ChildrenCollection<BoxPlacement> children;
40
Orientation direction;
43
protected new class WidgetBackendHost: Widget.WidgetBackendHost, ICollectionEventSink<BoxPlacement>, IContainerEventSink<BoxPlacement>
45
public void AddedItem (BoxPlacement item, int index)
47
((Box)Parent).OnAdd (item.Child, item);
50
public void RemovedItem (BoxPlacement item, int index)
52
((Box)Parent).OnRemove (item.Child);
55
public void ChildChanged (BoxPlacement child, string hint)
57
((Box)Parent).OnChildChanged (child, hint);
60
public void ChildReplaced (BoxPlacement child, Widget oldWidget, Widget newWidget)
62
((Box)Parent).OnReplaceChild (child, oldWidget, newWidget);
66
protected override BackendHost CreateBackendHost ()
68
return new WidgetBackendHost ();
72
get { return (IBoxBackend) BackendHost.Backend; }
75
internal Box (Orientation dir)
77
children = new ChildrenCollection<BoxPlacement> ((WidgetBackendHost)BackendHost);
81
public double Spacing {
82
get { return spacing; }
83
set { spacing = value; OnPreferredSizeChanged (); }
86
public ChildrenCollection<BoxPlacement> Placements {
87
get { return children; }
90
public IEnumerable<Widget> Children {
91
get { return children.Select (c => c.Child); }
94
public void PackStart (Widget widget)
96
PackStart (widget, BoxMode.None, 0);
99
public void PackStart (Widget widget, BoxMode mode)
101
PackStart (widget, mode, 0);
104
public void PackStart (Widget widget, BoxMode mode, int padding)
106
Pack (widget, mode, padding, PackOrigin.Start);
109
public void PackEnd (Widget widget)
111
PackEnd (widget, BoxMode.None, 0);
114
public void PackEnd (Widget widget, BoxMode mode)
116
PackEnd (widget, mode, 0);
119
public void PackEnd (Widget widget, BoxMode mode, int padding)
121
Pack (widget, mode, padding, PackOrigin.End);
124
void Pack (Widget widget, BoxMode mode, int padding, PackOrigin ptype)
126
var p = new BoxPlacement ((WidgetBackendHost)BackendHost, widget);
129
p.PackOrigin = ptype;
133
public bool Remove (Widget widget)
135
for (int n=0; n<children.Count; n++) {
136
if (children[n].Child == widget) {
137
children.RemoveAt (n);
145
/// Removes all children
152
void OnAdd (Widget child, BoxPlacement placement)
154
RegisterChild (child);
155
Backend.Add ((IWidgetBackend)GetBackend (child));
156
OnPreferredSizeChanged ();
159
void OnRemove (Widget child)
161
UnregisterChild (child);
162
Backend.Remove ((IWidgetBackend)GetBackend (child));
163
OnPreferredSizeChanged ();
166
void OnChildChanged (BoxPlacement placement, object hint)
168
OnPreferredSizeChanged ();
171
internal protected virtual void OnReplaceChild (BoxPlacement placement, Widget oldWidget, Widget newWidget)
173
if (oldWidget != null)
174
OnRemove (oldWidget);
175
OnAdd (newWidget, placement);
178
protected override void OnReallocate ()
180
var size = Backend.Size;
181
if (size.Width == 0 || size.Height == 0)
184
var visibleChildren = children.Where (c => c.Child.Visible).ToArray ();
185
IWidgetBackend[] widgets = new IWidgetBackend [visibleChildren.Length];
186
Rectangle[] rects = new Rectangle [visibleChildren.Length];
188
if (direction == Orientation.Horizontal) {
189
CalcDefaultSizes (Surface.SizeRequestMode, size.Width, size.Height);
191
double xe = size.Width + spacing;
192
for (int n=0; n<visibleChildren.Length; n++) {
193
var bp = visibleChildren [n];
194
if (bp.PackOrigin == PackOrigin.End)
195
xe -= bp.NextSize + spacing;
196
double x = bp.PackOrigin == PackOrigin.Start ? xs : xe;
197
widgets[n] = (IWidgetBackend)GetBackend (bp.Child);
198
rects[n] = new Rectangle (x, 0, bp.NextSize >= 0 ? bp.NextSize : 0, size.Height >= 0 ? size.Height : 0);
199
if (bp.PackOrigin == PackOrigin.Start)
200
xs += bp.NextSize + spacing;
203
CalcDefaultSizes (Surface.SizeRequestMode, size.Height, size.Width);
205
double ye = size.Height + spacing;
206
for (int n=0; n<visibleChildren.Length; n++) {
207
var bp = visibleChildren [n];
208
if (bp.PackOrigin == PackOrigin.End)
209
ye -= bp.NextSize + spacing;
210
double y = bp.PackOrigin == PackOrigin.Start ? ys : ye;
211
widgets[n] = (IWidgetBackend)GetBackend (bp.Child);
212
rects[n] = new Rectangle (0, y, size.Width >= 0 ? size.Width : 0, bp.NextSize >= 0 ? bp.NextSize : 0);
213
if (bp.PackOrigin == PackOrigin.Start)
214
ys += bp.NextSize + spacing;
217
Backend.SetAllocation (widgets, rects);
219
if (!Application.EngineBackend.HandlesSizeNegotiation) {
220
foreach (var bp in visibleChildren)
221
bp.Child.Surface.Reallocate ();
225
void CalcDefaultSizes (SizeRequestMode mode, double totalSize, double lengthConstraint)
227
bool calcHeights = direction == Orientation.Vertical;
228
bool useLengthConstraint = mode == SizeRequestMode.HeightForWidth && calcHeights || mode == SizeRequestMode.WidthForHeight && !calcHeights;
230
double naturalSize = 0;
232
var visibleChildren = children.Where (b => b.Child.Visible).ToArray ();
233
var sizes = new Dictionary<BoxPlacement,WidgetSize> ();
235
// Get the natural size of each child
236
foreach (var bp in visibleChildren) {
238
if (useLengthConstraint)
239
s = GetPreferredLengthForSize (mode, bp.Child, lengthConstraint);
241
s = GetPreferredSize (calcHeights, bp.Child);
243
naturalSize += s.NaturalSize;
244
bp.NextSize = s.NaturalSize;
245
if ((bp.BoxMode & BoxMode.Expand) != 0)
249
double remaining = totalSize - naturalSize - (spacing * (double)(visibleChildren.Length - 1));
251
// The box is not big enough to fit the widgets using its natural size.
252
// We have to shrink the widgets.
254
// List of widgets that we have to shrink
255
var toShrink = new List<BoxPlacement> (visibleChildren);
257
// The total amount we have to shrink
258
double shrinkSize = -remaining;
260
while (toShrink.Count > 0 && shrinkSize > 0) {
261
SizeSplitter sizePart = new SizeSplitter (shrinkSize, toShrink.Count);
263
for (int i=0; i < toShrink.Count; i++) {
264
var bp = toShrink[i];
265
bp.NextSize -= sizePart.NextSizePart ();
267
WidgetSize size = sizes [bp];
269
if (bp.NextSize < size.MinSize) {
270
// If the widget can't be shrinked anymore, we remove it from the shrink list
271
// and increment the remaining shrink size. We'll loop again and this size will be
272
// substracted from the cells which can still be reduced
273
shrinkSize += (size.MinSize - bp.NextSize);
274
bp.NextSize = size.MinSize;
275
toShrink.RemoveAt (i);
282
var expandRemaining = new SizeSplitter (remaining, nexpands);
283
foreach (var bp in visibleChildren) {
284
if ((bp.BoxMode & BoxMode.Expand) != 0)
285
bp.NextSize += expandRemaining.NextSizePart ();
290
protected override WidgetSize OnGetPreferredWidth ()
292
WidgetSize s = new WidgetSize ();
294
if (direction == Orientation.Horizontal) {
296
foreach (var cw in Children.Where (b => b.Visible)) {
297
s += cw.Surface.GetPreferredWidth ();
301
s += spacing * (double)(count - 1);
303
foreach (var cw in Children.Where (b => b.Visible))
304
s = s.UnionWith (cw.Surface.GetPreferredWidth ());
309
protected override WidgetSize OnGetPreferredHeight ()
311
WidgetSize s = new WidgetSize ();
313
if (direction == Orientation.Vertical) {
315
foreach (var cw in Children.Where (b => b.Visible)) {
316
s += cw.Surface.GetPreferredHeight ();
320
s += spacing * (double)(count - 1);
322
foreach (var cw in Children.Where (b => b.Visible))
323
s = s.UnionWith (cw.Surface.GetPreferredHeight ());
328
protected override WidgetSize OnGetPreferredHeightForWidth (double width)
330
return GetPreferredLengthForSize (SizeRequestMode.HeightForWidth, width);
333
protected override WidgetSize OnGetPreferredWidthForHeight (double height)
335
return GetPreferredLengthForSize (SizeRequestMode.WidthForHeight, height);
338
WidgetSize GetPreferredLengthForSize (SizeRequestMode mode, double width)
340
WidgetSize s = new WidgetSize ();
342
if ((direction == Orientation.Horizontal && mode == SizeRequestMode.HeightForWidth) || (direction == Orientation.Vertical && mode == SizeRequestMode.WidthForHeight)) {
343
CalcDefaultSizes (mode, width, -1);
344
foreach (var bp in children.Where (b => b.Child.Visible)) {
345
s = s.UnionWith (GetPreferredLengthForSize (mode, bp.Child, bp.NextSize));
350
foreach (var bp in children.Where (b => b.Child.Visible)) {
351
s += GetPreferredLengthForSize (mode, bp.Child, width);
355
s += spacing * (double)(count - 1);
360
WidgetSize GetPreferredSize (bool calcHeight, Widget w)
363
return w.Surface.GetPreferredHeight ();
365
return w.Surface.GetPreferredWidth ();
368
WidgetSize GetPreferredLengthForSize (SizeRequestMode mode, Widget w, double width)
370
if (mode == SizeRequestMode.WidthForHeight)
371
return w.Surface.GetPreferredWidthForHeight (width);
373
return w.Surface.GetPreferredHeightForWidth (width);
386
[ContentProperty("Child")]
387
public class BoxPlacement
389
IContainerEventSink<BoxPlacement> parent;
391
BoxMode boxMode = BoxMode.None;
393
PackOrigin packType = PackOrigin.Start;
396
internal BoxPlacement (IContainerEventSink<BoxPlacement> parent, Widget child)
398
this.parent = parent;
402
internal double NextSize;
404
public int Position {
406
return this.position;
410
parent.ChildChanged (this, "Position");
414
[DefaultValue (BoxMode.None)]
415
public BoxMode BoxMode {
421
parent.ChildChanged (this, "BoxMode");
432
parent.ChildChanged (this, "Padding");
436
[DefaultValue (PackOrigin.Start)]
437
public PackOrigin PackOrigin {
439
return this.packType;
443
parent.ChildChanged (this, "PackType");
447
public Widget Child {
448
get { return child; }
452
parent.ChildReplaced (this, old, value);
457
public enum PackOrigin
468
public SizeSplitter (double total, int numParts)
471
part = ((int)total) / numParts;
472
rem = ((int)total) % numParts;
476
public double NextSizePart ()