Clone in a terminal:
Expander.vala
/*
    Copyright (C) 2012 Johan Mattsson

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

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using Cairo;
using Math;

namespace Supplement {

public class Expander : GLib.Object {
	
	public double x = 7;
	public double y = 5;

	public double w = 6;
	public double h = 5;
	
	public double margin = 0;
	
	protected double rotation = 0;
	protected double opacity = 0.5;
	
	protected bool active = false;
	protected bool open = false;

	public List<Tool> tool;

	bool persist = false;
	bool unique = false;
	
	public Expander () {
	}

	/** Returns true if tools can be used with the current canvas after
	 * they have been selectes and false if they are a commands to be executed.
	 */
	public bool is_persistent () {
		return persist;
	}

	/** Returns true if all other tools in thid expander should be deselected 
	 * when a tool is selected.
	 */
	public bool is_unique () {
		return unique;
	}

	public void set_persistent (bool p) {
		persist = p;
	}

	public void set_unique (bool u) {
		unique = u;
	}
	
	private void update_tool_position () {
		int i = 0;
		double xt = x + 10;
		double yt = y - 4;
		foreach (var t in tool) {
			t.set_properties (xt, yt, w, h);
			
			if (i == 3) {
				yt += 31;
				i = 0;
				xt = x + 10;
			} else {
				i++;
				xt += w + 3 + 25;
			}
		}
	}
	
	public void set_offset (double ty) {
		y = ty;
		
		if (open) {
			update_tool_position ();
		}
	}
	
	public void add_tool (Tool t) {
		tool.append (t);
		update_tool_position ();
		
		t.select_action.connect ((selected) => {
				if (!selected.new_selection) {
					if (is_persistent ()) {
						selected.set_selected (true);
					} else {
						selected.set_selected (false);
					}
				}
			
				MainWindow.get_toolbox ().redraw ((int) x, (int) y, (int) w  + 300, (int) (h + margin));
			
				if (is_unique ()) {
					foreach (var deselected in tool) {
						if (selected.get_id () != deselected.get_id ()) {
							deselected.set_selected (false);
						}
					}
				}

				if (!is_persistent ()) {
						var time = new TimeoutSource(200);
						time.set_callback(() => {
							selected.set_selected (false);
							MainWindow.get_toolbox ().redraw ((int) x, (int) y, (int) w  + 300, (int) (h + margin));
							return false;
						});
						time.attach(null);
				}

				selected.new_selection = false;
			});
	}
	
	public bool is_over (double xp, double yp) {	
		return (x - 7/2.0 <= xp <= x + w  + 7/2.0 && y - 7/2.0<= yp <= y + w + 7/2.0);  
	}
	
	public bool set_active (bool a) {
		bool r = (active != a);
		opacity = (a) ? 1 : 0;
		active = a;
		return r;
	}
	
	public bool is_open () {
		return open;
	}
	
	public virtual bool set_open (bool o) {
		bool r = (open != o);
		rotation = (o) ? Math.PI_2 : 0;
		
		if (o) {
			margin = 35 * (int)((tool.length () / 4.0) + 1) ;
			rotation = Math.PI_2;			
			if (tool.length () % 4 == 0) {
				margin -= 35;
			}
		} else {
			margin = 0;
			rotation = 0; 
		}
		
		open = o;
		return r;
	}
	
	public void draw (int wd, int hd, Context cr) {
		double lx, ly;
		double ih2 = 5.4 / 2;
		double iw2 = 5.4 / 2;
		
		// box
		cr.save ();
		cr.set_line_join (LineJoin.ROUND);
		cr.set_line_width(7);
		cr.set_source_rgba (176/255.0, 211/255.0, 230/255.0, opacity);
		cr.rectangle (x, y, w, h);
		cr.stroke ();
		cr.restore ();
		
		// arrow
		cr.save ();
		
		cr.translate (x + w/2, y + h/2);
		cr.rotate (rotation);

		cr.set_line_width (1);
		cr.set_source_rgba (0, 0, 0, opacity);

		cr.new_path ();
		cr.move_to (-iw2, -ih2);
		cr.line_to (iw2, 0);	
		cr.line_to (-iw2, +ih2);

		cr.close_path();
		cr.stroke ();
		cr.restore ();
		
		// separator
		cr.save ();
		lx = x + w + 7;
		ly = y + ih2;
		if (lx < wd) {
			cr.set_line_width(1);
			cr.set_source_rgba (0, 0, 0, 0.2);
			cr.move_to (lx, ly);
			cr.line_to (wd - w - x + 4, ly);	
			cr.stroke ();
		}
		cr.restore ();

	}
	
	public void draw_content (int w, int h, Context cr) {
		if (open) {
			cr.save ();
			foreach (var t in tool) {
				t.draw (cr);
			}
			cr.restore ();
		}
	}
	
}

}

Contact

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