Clone in a terminal:
Toolbox.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 Toolbox : GLib.Object  {
	GlyphCanvas glyph_canvas;
	
	bool expanded = true;
	
	ToolboxExpander toolbox_expander;
	
	public List<Expander> expanders;
	
	Tool current_tool = new Tool ("no_icon");
	
	Expander draw_tools;
	Expander grid_expander;
		
	BackgroundTool move_background;
	CutBackgroundTool cut_background;
	
	public Tool press_tool = new Tool (null); // activate the pressed button on release
	
	public SpinButton background_scale = new SpinButton ();
	public SpinButton precision;
	
	public signal void redraw (int x, int y, int w, int h);
	
	public int allocation_width = 0;
	public int allocation_height = 0;
	
	ImageSurface? toolbox_background = null;
	
	public Toolbox (GlyphCanvas main_glyph_canvas) {
		glyph_canvas = main_glyph_canvas;

		toolbox_background = Icons.get_icon ("toolbox_background.png");

		toolbox_expander = new ToolboxExpander ();
		
		add_expander (toolbox_expander);
		
		draw_tools = new Expander ();
			
		Expander path_tool_modifiers = new Expander ();
		Expander draw_tool_modifiers = new Expander ();
		Expander characterset_tools = new Expander ();
		Expander test_tools = new Expander ();
		Expander guideline_tools = new Expander ();
		Expander view_tools = new Expander ();
		Expander grid = new Expander ();
		Expander background_tools = new Expander ();
		Expander trace = new Expander ();
		
		grid_expander = grid;

		// Draw tools
		PenTool pen_tool = new PenTool ("pen_tool");
		draw_tools.add_tool (pen_tool);
	
		ZoomTool zoom_tool = new ZoomTool ("zoom_tool");
		draw_tools.add_tool (zoom_tool);

		CutTool cut_tool = new CutTool ("cut");
		draw_tools.add_tool (cut_tool);	

		Tool move_tool = new MoveTool ("move");
		draw_tools.add_tool (move_tool);

		Tool shrink_tool = new ShrinkTool ("shrink");
		// Fixa: draw_tools.add_tool (shrink_tool);	
		
		// Draw tool modifiers
		// DELETE tools:
		
		Tool new_point = new Tool ("new_point", _("Right click to add new point, left click to move points") + " " + _("and double click to add new point on path"), 'a');
		new_point.select_action.connect ((self) => {
				select_draw_tool ();
			});
		
		// draw_tool_modifiers.add_tool (new_point);

		Tool insert_point_on_path = new Tool ("insert_point_on_path", _("Add new point on path"), 'n');
		insert_point_on_path.select_action.connect ((self) => {
			select_draw_tool ();
			pen_tool.begin_from_new_point_on_path ();
		});
		// draw_tool_modifiers.add_tool (insert_point_on_path);
		
		if (Supplement.experimental) {
			Tool new_point_on_path = new Tool ("new_point_on_path", _("Begin new line from point on path"), 'n');
			new_point_on_path.select_action.connect ((self) => {
					select_draw_tool ();
					pen_tool.begin_from_new_point_on_path ();
				});
			draw_tool_modifiers.add_tool (new_point_on_path);	
		}

		Tool tie_editpoint_tool = new Tool ("tie_point", _("Tie curve handles for selected edit point"), 'w');
		tie_editpoint_tool.select_action.connect ((self) => {
			bool tie;
			
			select_draw_tool ();
			
			foreach (EditPoint ep in pen_tool.selected_points) {
				tie = !ep.tie_handles;

				if (tie) {
					ep.process_tied_handle ();
				}

				ep.set_tie_handle (tie);
				
				MainWindow.get_current_glyph ().update_view ();				
			}
		});
		draw_tool_modifiers.add_tool (tie_editpoint_tool);	

		Tool tie_x_or_y = new Tool ("tie_x_or_y", _("Tie coordinates to previous edit point"), 'q');
		tie_x_or_y.select_action.connect((self) => {
			select_draw_tool ();
			pen_tool.tie_x_or_y_coordinates = !pen_tool.tie_x_or_y_coordinates;
		});
		// Fixa: draw_tool_modifiers.add_tool (tie_x_or_y);	

		Tool erase_tool = new Tool ("erase_tool", _("Delete edit points"));
		erase_tool.select_action.connect((self) => {
			select_draw_tool ();
		});
		// draw_tool_modifiers.add_tool (erase_tool);	

		// adjust precision
		string precision_value = Preferences.get ("precision");
		
		precision = new SpinButton ("precision", _("Set precision"));
		
		if (precision_value != "") {
			precision.set_value (precision_value);
		} else {
			precision.set_value_round (1);
		}
		
		precision.new_value_action.connect ((self) => {
			select_tool (precision);
			
			Preferences.set ("precision", self.get_display_value ());
			redraw ((int) precision.x, (int) precision.y, 70, 70);
		});

		precision.select_action.connect((self) => {
			pen_tool.set_precision (((SpinButton)self).get_value ());
		});
		
		precision.set_min (0.001);
		precision.set_max (1);
		
		draw_tool_modifiers.add_tool (precision);
				
		// path tools
		Tool union_paths_tool = new MergeTool ("union_paths");
		path_tool_modifiers.add_tool (union_paths_tool);
		
		Tool reverse_path_tool = new Tool ("reverse_path", _("Create counter from outline"), 'r');
		reverse_path_tool.select_action.connect ((self) => {
			Glyph g = MainWindow.get_current_glyph ();
			
			foreach (Path p in g.active_paths) {
				p.reverse ();
			}
		
			g.redraw_area (0, 0, g.allocation.width, g.allocation.height);
		});
		path_tool_modifiers.add_tool (reverse_path_tool);

		Tool move_layer = new Tool ("move_layer", _("Move to path to the bottom layer"), 'd');
		move_layer.select_action.connect ((self) => {
			Glyph g = MainWindow.get_current_glyph ();

			foreach (Path p in g.active_paths) {
				g.path_list.remove (p);
				g.path_list.prepend (p);
			}
		});
		path_tool_modifiers.add_tool (move_layer);
		
		// Character set tools
		Tool full_unicode = new Tool ("utf_8", _("Show full unicode characters set"), 'f', CTRL);
		full_unicode.select_action.connect ((self) => {
				MainWindow.get_tab_bar ().add_unique_tab (new OverView (), 100, false);	
				OverView o = MainWindow.get_overview ();
				GlyphRange gr = new GlyphRange ();
				gr.use_full_unicode_range ();
				o.set_glyph_range (gr);
				MainWindow.get_tab_bar ().select_tab_name ("Overview");
			});
		characterset_tools.add_tool (full_unicode);

		Tool custom_character_set = new Tool ("custom_character_set", _("Show default characters set"), 'r', CTRL);
		custom_character_set.select_action.connect ((self) => {
			MainWindow.get_tab_bar ().add_unique_tab (new OverView (), 100, false);
			OverView o = MainWindow.get_overview ();
			GlyphRange gr = new GlyphRange ();
			gr.use_default_range ();
			o.set_glyph_range (gr);
			MainWindow.get_tab_bar ().select_tab_name ("Overview");
		});
		characterset_tools.add_tool (custom_character_set);

		Tool avalilable_characters = new Tool ("available_characters", _("Show all characters in font"), 'd', CTRL);
		avalilable_characters.select_action.connect ((self) => {
			MainWindow.get_tab_bar ().add_unique_tab (new OverView (), 100, false);
			OverView o = MainWindow.get_overview ();
			o.display_all_available_glyphs ();
			MainWindow.get_tab_bar ().select_tab_name ("Overview");
		});
		characterset_tools.add_tool (avalilable_characters);

		Tool delete_glyph = new Tool ("delete_selected_glyph", _("Delete selected glyph"));
		delete_glyph.select_action.connect ((self) => {
			OverView o = MainWindow.get_overview ();
			
			if (MainWindow.get_current_display () is OverView) {
				o.delete_selected_glyph ();
			}
			
			MainWindow.get_tab_bar ().select_tab_name ("Overview");
		});
		characterset_tools.add_tool (delete_glyph);
						
		if (Supplement.has_argument ("--test")) {
			Tool test_case = new Tool ("test_case");
			test_case.select_action.connect((self) => {
					if (self.is_selected ()) {
						if (TestSupplement.is_running ()) {
							TestSupplement.pause ();
						} else {
							TestSupplement.continue ();
						}
					}
				});
			test_tools.add_tool (test_case);

			Tool slow_test = new Tool ("slow_test");
			slow_test.select_action.connect((self) => {
					bool s = TestSupplement.is_slow_test ();
					TestSupplement.set_slow_test (!s);
					s = TestSupplement.is_slow_test ();
					self.set_selected (s);
				});
		
			test_tools.add_tool (slow_test);
					
			// Run from commad line
			string? st = Supplement.get_argument ("--test");
			if (st != null && ((!)st).char_count () > 0) {
				IdleSource idle = new IdleSource ();

				idle.set_callback (() => {			
					select_tool (test_case);
					return false;
				});
				
				idle.attach (null);
			}
			
			if (Supplement.has_argument ("--slow")) {
				select_tool (slow_test);
			}
			
		}
		
		// help lines, grid and other guidlines
		Tool help_lines = new Tool ("help_lines", "Show help lines", 'l');
		help_lines.select_action.connect ((self) => {
				bool h;
				h = glyph_canvas.get_current_glyph ().get_show_help_lines ();
				glyph_canvas.get_current_glyph ().set_show_help_lines (!h);
				self.set_selected (!h);
				glyph_canvas.get_current_glyph ().redraw_help_lines ();
			});	

		guideline_tools.add_tool (help_lines);

		Tool xheight_help_lines = new Tool ("show_xheight_helplines", _("Show help lines for x-height and baseline"), 'x');
		xheight_help_lines.select_action.connect ((self) => {
			Glyph g = MainWindow.get_current_glyph ();
			bool v = !g.get_xheight_lines_visible ();
			g.set_xheight_lines_visible (v);
			self.set_selected (v);
			MainWindow.get_glyph_canvas ().redraw ();
			
			if (v && !help_lines.is_selected ()) {
				select_tool (help_lines);
			}
			
		});
		guideline_tools.add_tool (xheight_help_lines);

		Tool background_help_lines = new Tool ("background_help_lines", _("Show help lines at top and bottom margin"), 't');
		background_help_lines.select_action.connect ((self) => {
			Glyph g = MainWindow.get_current_glyph ();
			bool v = !g.get_margin_lines_visible ();
			g.set_margin_lines_visible (v);
			self.set_selected (v);
			MainWindow.get_glyph_canvas ().redraw ();
			
			if (v && !help_lines.is_selected ()) {
				select_tool (help_lines);
			}
		});
		guideline_tools.add_tool (background_help_lines);

		Tool new_grid = new GridTool ("new_grid");
		guideline_tools.add_tool (new_grid);

		// Zoom tools 
		Tool zoom_in = new Tool ("zoom_in", _("Zoom in"), '+', CTRL);
		zoom_in.select_action.connect ((self) => {
			zoom_tool.store_current_view ();
			glyph_canvas.get_current_display ().zoom_in ();
		});
		view_tools.add_tool (zoom_in);

		Tool zoom_out = new Tool ("zoom_out", _("Zoom out"), '-', CTRL);
		zoom_out.select_action.connect ((self) => {
			zoom_tool.store_current_view ();
			glyph_canvas.get_current_display ().zoom_out ();
		});
		view_tools.add_tool (zoom_out);

		Tool reset_zoom = new Tool ("zoom_1_1", _("Zoom to scale 1:1"), '0', CTRL);
		reset_zoom.select_action.connect ((self) => {
				zoom_tool.store_current_view ();
				glyph_canvas.get_current_display ().reset_zoom ();
				glyph_canvas.redraw_area(0, 0, glyph_canvas.allocation.width, glyph_canvas.allocation.height);
			});
		view_tools.add_tool (reset_zoom);

		Tool full_glyph = new Tool ("full_glyph", _("Show full glyph"));
		full_glyph.select_action.connect((self) => {
			zoom_tool.store_current_view ();
			zoom_tool.zoom_full_glyph ();
		});
		view_tools.add_tool (full_glyph);

		Tool zoom_boundries = new Tool ("zoom_boundries", _("Zoom in at region boundries"), 'v');
		zoom_boundries.select_action.connect((self) => {
			zoom_tool.store_current_view ();
			glyph_canvas.get_current_display ().zoom_max ();
		});
		view_tools.add_tool (zoom_boundries);

		Tool zoom_bg = new Tool ("zoom_background_image", _("Zoom in background image"), 'b');
		zoom_bg.select_action.connect((self) => {
			if (MainWindow.get_current_glyph ().get_background_image () != null) {
				zoom_tool.store_current_view ();					
				glyph_canvas.get_current_display ().reset_zoom ();
				
				zoom_tool.zoom_full_background_image ();
				
				glyph_canvas.redraw_area(0, 0, glyph_canvas.allocation.width, glyph_canvas.allocation.height);
			}
		});
		view_tools.add_tool (zoom_bg);

		Tool zoom_prev = new Tool ("prev", _("Previous view"), 'j', CTRL);
		zoom_prev.select_action.connect((self) => {
			zoom_tool.previous_view ();
		});
		view_tools.add_tool (zoom_prev);

		Tool zoom_next = new Tool ("next", _("Next view"), 'l', CTRL);
		zoom_next.select_action.connect((self) => {
			zoom_tool.next_view ();
		});
		view_tools.add_tool (zoom_next);
				
		// background tools
		background_scale = new SpinButton ("scale_background", _("Set size for background image"));
		background_scale.set_value_round (1);
		
		background_scale.new_value_action.connect((self) => {
			background_scale.select_action (self);
		});
		
		background_scale.select_action.connect((self) => {
			SpinButton sb = (SpinButton) self;
			Glyph g = MainWindow.get_current_glyph ();
			GlyphBackgroundImage? img = g.get_background_image ();
			double s = sb.get_value ();
			
			if (img != null) {
				((!)img).set_img_scale (s, s);
			}
			
			MainWindow.get_glyph_canvas ().redraw ();
		});
		
		background_tools.add_tool (background_scale);		

		BackgroundTool move_background = new BackgroundTool ("move_background");			
		background_tools.add_tool (move_background);
		this.move_background = move_background;
		
		CutBackgroundTool cut_background = new CutBackgroundTool ("cut_background");
		background_tools.add_tool (cut_background);
		this.cut_background = cut_background;
		
		Tool show_bg = new Tool ("show_background", _("Show/hide background image"));
		show_bg.select_action.connect ((self) => {
			Glyph g = MainWindow.get_current_glyph ();
			g.set_background_visible (!g.get_background_visible ());
			MainWindow.get_glyph_canvas ().redraw ();
		});
		background_tools.add_tool (show_bg);

		Tool bg_selection = new Tool ("insert_background", _("Insert a new background image"));
		bg_selection.select_action.connect((self) => {
			Glyph? g = null;
			FontDisplay fd = MainWindow.get_current_display ();
			TooltipArea tp = MainWindow.get_tool_tip ();
			tp.show_text (_("Creating thumbnails"));
			
			bg_selection.yield ();
		
			if (fd is Glyph) {
				g = (Glyph) fd;
			} else {
				return;
			}
			
			BackgroundSelection bgs = new BackgroundSelection ();
			MainWindow.get_tab_bar ().add_unique_tab (bgs , 120, false);
			
			tp.show_text ("");
		});
		
		bg_selection.set_show_background (true);
		background_tools.add_tool (bg_selection);
		
		SpinButton background_contrast = new SpinButton ("background_contrast", _("Set contrast for background image"));
		background_contrast.set_value_round (1);

		background_contrast.new_value_action.connect ((self) => {
			background_contrast.select_action (self);
		});
		
		background_contrast.select_action.connect ((self) => {		
			Glyph g = MainWindow.get_current_glyph ();
			GlyphBackgroundImage? bg = g.get_background_image ();
			GlyphBackgroundImage b;
			
			if (bg != null) {
				b = (!) bg;
				b.set_contrast (background_contrast.get_value ());
			}
		});
		
		// Fixa: background_tools.add_tool (background_contrast);

		SpinButton background_threshold = new SpinButton ("background_threshold", _("Set threshold"));
		background_threshold.set_value_round (1);

		background_threshold.new_value_action.connect ((self) => {
			background_threshold.select_action (self);
		});
		
		background_threshold.select_action.connect ((self) => {		
			Glyph g = MainWindow.get_current_glyph ();
			GlyphBackgroundImage? bg = g.get_background_image ();
			GlyphBackgroundImage b;
			
			if (bg != null) {
				b = (!) bg;
				b.set_threshold (background_threshold.get_value ());
			}
			
		});
		trace.add_tool (background_threshold);
		
		draw_tools.set_open (true);
		draw_tool_modifiers.set_open (true);
		path_tool_modifiers.set_open (true);
		view_tools.set_open (true);
		grid.set_open (true);
		characterset_tools.set_open (true);
		test_tools.set_open (true);
		guideline_tools.set_open (true);
		background_tools.set_open (true);
		trace.set_open (true);
		
		add_expander (draw_tools);
		add_expander (draw_tool_modifiers);
		add_expander (path_tool_modifiers);	
		
		add_expander (characterset_tools);
		add_expander (guideline_tools);
		add_expander (grid);
		add_expander (view_tools);
		add_expander (background_tools);
		
		// Fixa: add_expander (trace);
		
		add_expander (test_tools);
		
		draw_tools.set_persistent (true);
		draw_tools.set_unique (true);
		
		draw_tool_modifiers.set_persistent (true);
		draw_tool_modifiers.set_unique (true);

		path_tool_modifiers.set_persistent (false);
		path_tool_modifiers.set_unique (false);

		characterset_tools.set_persistent (true);
		characterset_tools.set_unique (true);
		
		test_tools.set_persistent (true);
	
		guideline_tools.set_persistent (true);
		guideline_tools.set_unique (false);
		
		grid.set_persistent (true);
		grid.set_unique (true);
		grid.set_open (false);
		
		background_tools.set_persistent (true);
		background_tools.set_unique (true);
		
		trace.set_persistent (false);
		trace.set_unique (false);

		update_expanders ();
		reset_active_tool ();

		// Default selection
		var idle = new IdleSource();
		idle.set_callback (() => {
			new_point.set_selected (true);
			pen_tool.set_selected (true);
			select_draw_tool ();			
			
			if (glyph_canvas.get_current_glyph ().get_show_help_lines ()) {
				help_lines.set_selected (true);
				help_lines.set_active (false);
			}

			add_new_grid ();
			add_new_grid ();
			
			move (0, 0);
			
			return false;
		});
		
		idle.attach (null);
	}
	
	public void key_press (uint keyval) {
		print ("key_press\n");
		foreach (var exp in MainWindow.get_toolbox ().expanders) {
			foreach (Tool t in exp.tool) {
				t.set_active (false);
				
				if (t.key == keyval && t.modifier_flag == NONE) {
					MainWindow.get_toolbox ().select_tool (t);
				}
			}
		}
	}
	
	public void press (uint button, double x, double y) {
		foreach (var exp in expanders) {
			foreach (Tool t in exp.tool) {
				if (t.is_over (x, y)) {
					t.panel_press_action (t, button, x, y);
					press_tool = t;
				}
			}
		}
	}
	
	public void release (uint button, double x, double y) {
		foreach (var exp in expanders) {
			if (exp.is_over (x, y)) {
				exp.set_open (! exp.is_open ());					
				update_expanders ();			
				redraw ((int) exp.x - 10, (int) exp.y - 10, allocation_width, (int) (allocation_height - exp.y + 10));
			}
			
			if (exp.is_open ()) {
				foreach (Tool t in exp.tool) {
					bool active = t.is_over (x, y);
					
					if (active) {
						if (press_tool == t) {
							select_tool (t);
						}
					}
					
					t.panel_release_action (t, button, x, y);
				}
			}
		}
	}
	
	private void select_draw_tool () {
		select_tool_by_name ("pen_tool");
	}
	
	public void move (double x, double y) {
		bool update;
		bool a;
		foreach (var exp in expanders) {
			a = exp.is_over (x, y);
			update = exp.set_active (a);
			
			if (update) {
				MainWindow.get_toolbox ().redraw ((int) exp.x - 10, (int) exp.y - 10, (int) (exp.x + exp.w + 10), (int) (exp.y + exp.h + 10));
			}
			
			if (exp.is_open ()) {
				foreach (Tool t in exp.tool) {
					bool active = t.is_over (x, y);
					TooltipArea? tpa = null;
					
					update = t.set_active (active);
					tpa = MainWindow.get_tool_tip ();
					
					if (active && tpa != null) {
						((!)tpa).update_text ();
					}
					
					if (update) {
						MainWindow.get_toolbox ().redraw (0, 0, allocation_width, allocation_height);
					}
					
					t.panel_move_action (t, x, y);
				}
			}
		}
	}
	
	public void remove_all_grid_buttons () {
		while (grid_expander.tool.length () > 0) {
			grid_expander.tool.remove_link (grid_expander.tool.last ());
		}
		
		while (GridTool.sizes.length () > 0) {
			GridTool.sizes.remove_link (GridTool.sizes.first ());
		}
		
		grid_expander.set_open (true);
		update_expanders ();
		redraw (0, 0, allocation_width, allocation_height);		
	}
	
	public void parse_grid (string spin_button_value) {
		SpinButton sb = add_new_grid ();
		sb.set_value (spin_button_value);
		select_tool (sb);
	}

	public SpinButton add_new_grid () {
		SpinButton grid_width = new SpinButton ("grid_width", _("Set size for grid"));
		
		grid_width.new_value_action.connect((self) => {
			grid_width.select_action (grid_width);
		});

		grid_width.select_action.connect((self) => {
			SpinButton sb = (SpinButton) self;
			GridTool.set_grid_width (sb.get_value ());
			MainWindow.get_glyph_canvas ().redraw ();
		});
				
		grid_expander.add_tool (grid_width);

		GridTool.sizes.append (grid_width);

		grid_expander.set_open (true);
		update_expanders ();
		
		redraw (0, 0, allocation_width, allocation_height);
		
		select_tool (grid_width);
		grid_width.set_active (false);
		
		return grid_width;
	}

	public void remove_current_grid () {
		Tool grid_width;
		
		foreach (Tool t in grid_expander.tool) {
			if (t.is_selected () && t is SpinButton) {
				GridTool.sizes.remove ((SpinButton)t);
				grid_expander.tool.remove (t);
				break;
			}
		}
		
		if (grid_expander.tool.length () > 0) {
			grid_width = grid_expander.tool.last ().data;
			select_tool (grid_width);
			grid_width.set_active (false);
		}
		
		update_expanders ();
		redraw (0, 0, allocation_width, allocation_height);
	}

	public void reset_active_tool () {
		foreach (var exp in expanders) {
			foreach (Tool t in exp.tool) {
				t.set_active (false);
			}
		}
	}

	public Tool? get_active_tool () {
		foreach (var exp in expanders) {
			foreach (Tool t in exp.tool) {
				if (t.is_active ()) {
					return t;
				}
			}
		}
		
		return null;
	}

	public Tool get_current_tool () {
		return current_tool;
	}
	
	public void select_tool (Tool tool) {
		foreach (var exp in expanders) {
			
			foreach (Tool t in exp.tool) {
				if (tool.get_id () == t.get_id ()) {
					exp.set_open (true);
					
					bool update;
					
					update = tool.set_selected (true);
					update = tool.set_active (true);
					
					tool.select_action (tool); // execute command
					
					if (update) {							
						redraw ((int) exp.x - 10, (int) exp.y - 10, allocation_width, (int) (allocation_height - exp.y + 10));
					}
					
					if (exp == draw_tools || t == move_background || t == cut_background) {
						current_tool = tool;
					}
				}
			}
			
		}
	}
	
	public Tool get_tool (string name) {
		foreach (var e in expanders) {
			foreach (var t in e.tool) {
				if (t.get_name () == name) {
					return t;
				}
			}
		}

		if (name == cut_background.get_name ()) {
			return cut_background;
		}
				
		warning ("No tool found for name \"%s\".\n", name);
		
		return new Tool ("no_icon");
	}
	
	public static void select_tool_by_name (string name) {
		Toolbox b = MainWindow.get_toolbox ();
		b.select_tool (b.get_tool (name));
	}
		
	private void update_expanders () {
		Expander? p = null; 
		Expander pp;
		foreach (var e in expanders) {
			if (p != null) {
				pp = (!) p;
				e.set_offset (pp.y + pp.margin + 12);
			}
			
			p = e;
		}
	}
	
	private void add_expander (Expander e) {
		expanders.append (e);
	}
	
	private void draw_expanders (int w, int h, Context cr) {
		foreach (Expander e in expanders) {
			e.draw (w, h, cr);
			if (e.is_open ()) {
				e.draw_content (w, h, cr);
			}
		}
	}
	
	public void draw (int w, int h, Context cr) { 
		cr.save ();
		
		cr.rectangle(0, 0, w, h);
		cr.set_line_width(0);
		cr.set_source_rgba(255/255.0, 255/255.0, 255/255.0, 1);
		cr.fill();

		for (int i = 0; i < w; i++) {
			cr.set_source_surface ((!) toolbox_background, i, 0);
			cr.paint ();
		}

		cr.rectangle(0, 0, 1, h);
		cr.set_line_width(0);
		cr.set_source_rgba(0/255.0, 0/255.0, 0/255.0, 1);
		cr.fill();
		
		draw_expanders (w, h, cr);
		
		cr.restore ();
	}
}

class ToolboxExpander : Expander {
	
	public ToolboxExpander () {
	}
	
	public override bool set_open (bool o) {
		bool r = (open != o);
		rotation = (o) ? 0 : Math.PI;
		open = o;
		return r;
	}
}

}

Contact

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