Clone in a terminal:
SettingsDisplay.vala
/*
	Copyright (C) 2014 2015 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 Cairo;
using Math;

namespace BirdFont {

internal abstract class SettingsDisplay : FontDisplay {
	
	double scroll = 0;
	double content_height = 1;
	WidgetAllocation allocation;
	public Gee.ArrayList<SettingsItem> tools;

	public static SpinButton precision;
	
	SettingsItem new_key_bindings = new SettingsItem.head_line ("");
	public static bool update_key_bindings = false;
	
	protected SettingsDisplay () {
		allocation = new WidgetAllocation ();
		tools = new Gee.ArrayList<SettingsItem> ();
		content_height = 200;
		precision = new SpinButton ("precision");
	}
	
	public abstract void create_setting_items ();

	public override void draw (WidgetAllocation allocation, Context cr) {		
		this.allocation = allocation;
		
		layout ();
		
		// background
		cr.save ();
		cr.rectangle (0, 0, allocation.width, allocation.height);
		cr.set_line_width (0);
		Theme.color (cr, "Default Background");
		cr.fill ();
		cr.stroke ();
		cr.restore ();
		
		foreach (SettingsItem s in tools) {
			if (-20 <= s.y <= allocation.height + 20) {
				s.draw (allocation, cr);
			}
		}
	}	

	public void layout () {
		double y = -scroll;
		bool first = true;
		foreach (SettingsItem s in tools) {
			
			if (!first && s.headline) {
				y += 30;
			}
			
			s.y = y;
			
			if (s.button != null) {
				((!) s.button).y = y;
				((!) s.button).x = 20;
			}
			
			if (s.headline) {
				y += 50;
			} else {
				y += 40;
			}
			
			first = false;
		}

		content_height = y + scroll;
	}

	void set_key_bindings (SettingsItem item) {	
		if (new_key_bindings.active) {
			new_key_bindings.active = false;
			update_key_bindings = false;
		} else {	
			new_key_bindings.active = false;
			new_key_bindings = item;
			update_key_bindings = true;
			new_key_bindings.active = true;
		}
	}

	public override void key_release (uint keyval) {
		SettingsItem old_key_binding;
		
		if (!is_modifier_key (keyval) || keyval == Key.BACK_SPACE || keyval == Key.DEL) {
			if (update_key_bindings) {
				if (keyval == Key.BACK_SPACE || keyval == Key.DEL) {
					update_key_bindings = false;
					new_key_bindings.active = false;
					new_key_bindings.menu_item.modifiers = NONE;
					new_key_bindings.menu_item.key = '\0';
				} else if (KeyBindings.get_mod_from_key (keyval) == NONE) {
					
					if (has_key_binding (KeyBindings.modifier, (unichar) keyval)) {
						old_key_binding = (!) get_key_binding (KeyBindings.modifier, (unichar) keyval);
						
						if (same_scope (old_key_binding, new_key_bindings)) {
							old_key_binding.menu_item.modifiers = NONE;
							old_key_binding.menu_item.key = '\0';
						}
					}
					
					new_key_bindings.menu_item.modifiers = KeyBindings.modifier;
					new_key_bindings.menu_item.key = (unichar) keyval;
					update_key_bindings = false;
					new_key_bindings.active = false;
				}
				
				MainWindow.get_menu ().write_key_bindings ();
				GlyphCanvas.redraw ();	
			}
		}
	}

	/** Check if key binding is used in same tab. */
	bool same_scope (SettingsItem key_binding1, SettingsItem key_binding2) {
		foreach (string scope in key_binding1.menu_item.displays) {
			if (key_binding2.menu_item.in_display (scope)) {
				return true;
			}
		}
		
		return false;
	}
	
	bool has_key_binding (uint modifier, unichar key) {
		return get_key_binding (modifier, key) != null;
	}
	
	SettingsItem? get_key_binding (uint modifier, unichar key) {
		foreach (SettingsItem i in tools) {
			if (i.menu_item.modifiers == modifier && i.menu_item.key == key) {
				return i;
			}
		}
		
		return null; 
	}

	public override void button_press (uint button, double x, double y) {
		foreach (SettingsItem s in tools) {
			if (s.handle_events && s.button != null) {
				if (((!) s.button).is_over (x, y)) {
					
					((!) s.button).set_selected (! ((!) s.button).selected);
					
					if (((!) s.button).selected) {
						((!) s.button).select_action ((!) s.button);
					}
					
					((!) s.button).panel_press_action ((!) s.button, button, x, y);
				}
			}
		}
		GlyphCanvas.redraw ();
	}
	
	public override void button_release (int button, double x, double y) {
		foreach (SettingsItem s in tools) {
			if (s.handle_events && s.button != null) {
				((!) s.button).panel_release_action (((!) s.button), button, x, y);
			}
			
			if (s.key_bindings && s.y <= y < s.y + 40 && button == 1) {
				set_key_bindings (s);
			}
		}
		GlyphCanvas.redraw ();
	}

	public override void motion_notify (double x, double y) {
		bool consumed = false;
		bool active;
		bool update = false;
		
		foreach (SettingsItem si in tools) {
			
			if (si.handle_events && si.button != null) {
				active = ((!) si.button).is_over (x, y);

				if (!active && ((!) si.button).is_active ()) {
					((!) si.button).move_out_action ((!) si.button);
				}
						
				if (((!) si.button).set_active (active)) {
					update = true;
				}	
			}	
		}
		
		foreach (SettingsItem s in tools) {
			if (s.handle_events && s.button != null) {
				if (((!) s.button).panel_move_action ((!) s.button, x, y)) {
					consumed = true;
				}
			}
		}
		
		if (consumed || update) {
			GlyphCanvas.redraw ();
		}
	}

	public override string get_label () {
		return t_("Settings");
	}

	public override string get_name () {
		return "Settings";
	}

	public override bool has_scrollbar () {
		return true;
	}
	
	public override void scroll_wheel (double x, double y, double pixeldelta, double dy) {
		if (dy < 0) {
			foreach (SettingsItem s in tools) {
				if (s.handle_events && s.button != null) {
					if (((!) s.button).is_over (x, y)) {
						((!) s.button).scroll_wheel_down_action ((!) s.button);
						return;
					}
				}
			}
		} else {
			foreach (SettingsItem s in tools) {
				if (s.handle_events && s.button != null) {
					if (((!) s.button).is_over (x, y)) {
						((!) s.button).scroll_wheel_up_action ((!) s.button);
						return;
					}
				}
			}		
		}
		
		scroll -= dy;

		if (scroll + allocation.height >=  content_height) {
			scroll = content_height - allocation.height;
		}

		if (scroll < 0) {
			scroll = 0;
		}
				
		update_scrollbar ();
		GlyphCanvas.redraw ();
	}

	public override void selected_canvas () {
		MainWindow.get_toolbox ().set_default_tool_size ();
		update_scrollbar ();
		GlyphCanvas.redraw ();
	}
	
	public override void update_scrollbar () {
		double h = content_height - allocation.height;
		MainWindow.set_scrollbar_size (allocation.height / content_height);
		MainWindow.set_scrollbar_position (scroll /  h);
	}

	public override void scroll_to (double percent) {
		double h = content_height - allocation.height;
		scroll = percent * h;
		GlyphCanvas.redraw ();
	}
}

}

Contact

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