Clone in a terminal:
BoxLayout.vala
/*
	Copyright (C) 2016 Johan Mattsson

	This library is free software; you can redistribute it and/or modify 
	it under the terms of the GNU Lesser General Public License as 
	published by the Free Software Foundation; either version 3 of the 
	License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful, but 
	WITHOUT ANY WARRANTY; without even the implied warranty of 
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
	Lesser General Public License for more details.
*/

using Gee;
using SvgBird;
using B;
using Cairo;

namespace Bird {

public enum BoxOrientation {
	HORIZONTAL,
	VERTICAL
}

class BoxLayout : Component {

	public BoxOrientation orientation { get; private set; }
	
	public BoxLayout (XmlElement layout_tag, BoxOrientation orientation, Defs defs) {
		base (layout_tag, defs);
		this.orientation = orientation;
	}

	public override void draw (Context cairo) {
		cairo.save ();
		cairo.translate (x, y);
		clip (cairo);
		
		foreach (Component component in components) {
			component.draw (cairo);
		}

		cairo.restore ();
	}
			
	bool is_width_remainder (Component component) {
		return component.style.property_equals ("width", "remainder");
	}

	bool is_height_remainder (Component component) {
		return component.style.property_equals ("height", "remainder");
	}

	/** The smallest size this layout can be. */
	public override void get_min_size (out double min_width, out double min_height) {
		min_width = 0;
		min_height = 0;
		
		if (orientation == BoxOrientation.HORIZONTAL) {
			foreach (Component component in components) {
				double w, h;
				
				component.get_min_size (out w, out h);
				min_width += w;
				
				if (h > min_height) {
					min_height = h;
				}
			}	
		} else {
			foreach (Component component in components) {
				double w, h;
				
				component.get_min_size (out w, out h);	
				min_height += h;
				
				if (w > min_width) {
					min_width = w;
				}
			}
		}
		
		min_width += get_padding_left ();
		min_width += get_padding_right ();
		min_height += get_padding_top ();
		min_height += get_padding_bottom ();
		
		limit_size (ref min_width, ref min_height);
	}

	public override void layout (double parent_width, double parent_height) {
		double w, h;
		
		get_min_size (out w, out h);
		int remainders = count_remainders ();
		
		if (!is_width_remainder (this)) {
			width = w;
		}
		
		if (!is_height_remainder (this)) {
			height = h;
		}
		
		if (remainders > 0) {
			double remainder_size;
			
			if (orientation == BoxOrientation.HORIZONTAL) {
				remainder_size = (parent_width - w) / remainders;
				layout_variable_size (remainder_size, parent_height);
			} else {
				remainder_size = (parent_height - h) / remainders;
				layout_variable_size (remainder_size, parent_width);
			}
		}
		
		if (orientation == BoxOrientation.HORIZONTAL) {
			foreach (Component component in components) {
				component.layout (parent_width, h);
			}
		} else {
			foreach (Component component in components) {
				component.layout (w, parent_height);
			}
		}
		
		layout_positions ();
	}
	
	int count_remainders () {
		int remainders = 0;
		
		if (orientation == BoxOrientation.HORIZONTAL) {
			foreach (Component component in components) {
				if (is_width_remainder (component)) {
					remainders++;
				}
			}
		} else {
			foreach (Component component in components) {
				if (is_height_remainder (component)) {
					remainders++;
				}
			}
		}
		
		return remainders;
	}
	
	void layout_variable_size (double remainder_size, double fixed_size) {
		double w, h;
		
		w = 0;
		h = 0;
		foreach (Component component in components) {
			bool width_remainder = is_width_remainder (component);
			bool height_remainder = is_height_remainder (component);
			
			if (width_remainder && orientation == BoxOrientation.HORIZONTAL) {
				w = remainder_size;
				w -= component.get_padding_left ();
				w -= component.get_padding_right ();
				h = fixed_size;
			}
			
			if (height_remainder && orientation == BoxOrientation.VERTICAL) {
				w = fixed_size;
				h = remainder_size;
				h -= component.get_padding_top ();
				h -= component.get_padding_bottom ();
			}
			
			if (width_remainder || height_remainder) {
				double min_width, min_height;
				component.get_min_size (out min_width, out min_height);
				
				if (w < min_width) {
					w = min_width;
				}
				
				if (h < min_height) {
					h = min_height;
				}
				
				component.limit_size (ref w, ref h);
				component.width = w;
				component.height = h;
			}
		}
	}

	void layout_positions () {
		double child_x = 0;
		double child_y = 0;

		foreach (Component component in components) {
			component.x = child_x + component.get_padding_left ();
			component.y = child_y + component.get_padding_top ();

			if (orientation == BoxOrientation.HORIZONTAL) {
				child_x = component.x + component.padded_width + get_padding_right ();
			} else {
				child_y = component.y + component.padded_height  + get_padding_bottom ();
			}
		}
	}

	public override string to_string () {
		return "BoxLayout";
	}
}

}

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112