Clone in a terminal:
OverviewItem.vala
/*
	Copyright (C) 2012 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;

[CCode (cname = "draw_overview_glyph")]
public extern bool draw_overview_glyph (Context context, string font_file, double width, double height, unichar character);

namespace BirdFont {
	
public class OverviewItem : GLib.Object {
	public unichar character = '\0';
	public GlyphCollection? glyphs;
	public double x;
	public double y;
	public bool selected = false;
	public CharacterInfo info;
		
	public static double DEFAULT_WIDTH = 100;
	public static double DEFAULT_HEIGHT = 130;
	public static double DEFAULT_MARGIN = 20;
	
	public static double width = 100;
	public static double height = 130;
	public static double margin = 20;

	public static double glyph_scale = 1.0;
	
	public VersionList version_menu;
	Text label;
	
	private Surface? cache = null;
	
	public static Surface? label_background = null;
	public static Surface? selected_label_background = null;
	public static Surface? label_background_no_menu = null;
	public static Surface? selected_label_background_no_menu = null;
		
	public OverviewItem () {	
	}

	public void set_character (unichar character) {
		this.character = character;
	}
	
	public void set_glyphs (GlyphCollection? gc) {
		glyphs = gc;
		
		if (glyphs != null) {	
			version_menu = new VersionList ((!) glyphs);
			version_menu.add_glyph_item.connect ((glyph) => {
				((!) glyphs).insert_glyph (glyph, true);
			});
			
			version_menu.signal_delete_item.connect ((glyph_index) => {
				Overview v = MainWindow.get_overview ();
				version_menu = new VersionList ((!) glyphs);
				v.update_item_list ();
				GlyphCanvas.redraw ();
			});
		}	

		info = new CharacterInfo (character, glyphs);
		
		if (glyphs == null) {
			label = new Text ();
		} else {
			if (character != '\0') {
				label = new Text ((!) character.to_string (), 17);
			} else {
				label = new Text ((!) info.get_name (), 17);
			}
			
			truncate_label ();
		}
		
		draw_background ();		
	}

	public void clear_cache () {
		cache = null;
		
		if (glyphs != null) {
			Glyph g = ((!) glyphs).get_current ();
			g.overview_thumbnail = null;
		}
	}

	public void draw_glyph_from_font () {
		if (glyphs == null) {
			return;
		}
		
		Glyph g;
		double gx, gy;
		double x1, x2, y1, y2;
		double scale_box;
		double w, h;
		double glyph_width, glyph_height;
		Surface s;
		Context c;
		Color color = Color.black ();
		
		g = ((!) glyphs).get_current ();
		
		if (likely (g.overview_thumbnail != null)) {
			cache = g.overview_thumbnail;
			return;
		}
		
		w = width;
		h = height;

		scale_box = (height / DEFAULT_HEIGHT) * 0.65;

		s = Screen.create_background_surface ((int) width, (int) height - 20);
		c = new Context (s);
		
		c.save ();
		g.boundaries (out x1, out y1, out x2, out y2);
	
		glyph_width = x2 - x1;
		glyph_height = y2 - y1;
		
		c.save ();
		c.scale (scale_box * Screen.get_scale (), scale_box * Screen.get_scale ());

		g.add_help_lines ();
		
		gx = ((w / scale_box) - glyph_width) / 2 - g.get_left_side_bearing ();
		gy = h / scale_box + g.get_baseline () - 20 / scale_box - 20;

		c.translate (gx - Glyph.xc () - g.get_lsb (), gy - Glyph.yc ());

		g.draw_paths (c, color);
		c.restore ();
		
		cache = s;
		g.overview_thumbnail = s;
		
		GlyphCanvas.redraw ();
	}

	public void draw_background () {
		double scale_box;
		double w, h;
		Surface s;
		Context c;
		
		w = width;
		h = height;
		
		scale_box = width / DEFAULT_WIDTH;
		s = Screen.create_background_surface ((int) width, (int) height - 20);
		c = new Context (s);
		
		if (glyphs != null) { // FIXME: lock
			draw_glyph_from_font ();
		} else {
			c.scale (Screen.get_scale (), Screen.get_scale ());
			
			c.save ();
			
			bool glyph_found;
			string? font_file;
			
			Theme.color (c, "Overview Glyph");
			
			font_file = FontCache.fallback_font.get_default_font_file ();	
			glyph_found = draw_overview_glyph (c, (!) font_file, width, height, character);
			
			if (!glyph_found) {
				font_file = find_font (FallbackFont.font_config, (!) character.to_string ());
				
				if (font_file != null) {
					string path = (!) font_file;
					
					if (!path.has_suffix("LastResort.ttf")) {
						draw_overview_glyph (c, path, width, height, character);
					}
				}
			}
			
			c.restore ();
			
			cache = s;
			GlyphCanvas.redraw ();
		}
	}

	public static void reset_label () {
		label_background = null;
		selected_label_background = null;
	}
	
	void truncate_label () {
		double w = has_icons () ? width - 43 : width;
		label.truncate (w);
	}

	public string get_name () {
		StringBuilder s;
		
		if (glyphs != null) {
			return ((!) glyphs).get_name ();
		}
		
		s = new StringBuilder ();
		s.append_unichar (character);
		
		return s.str;
	}
	
	public void set_selected (bool s) {
		selected = s;
	}
	
	public static double full_width () {
		return width + margin;
	}

	public static double full_height () {
		return height + margin;
	}
	
	public bool click_menu (uint button, double px, double py) {
		bool a;
		GlyphCollection g;

		if (has_icons () && glyphs != null) {
			g = (!) glyphs;
			version_menu.set_position (x + width - 21, y + height - 18);
			a = version_menu.menu_item_action (px, py); // select one item on the menu
			
			if (a) {
				MainWindow.get_overview ().reset_cache ();
				MainWindow.get_overview ().update_item_list ();

				GlyphCanvas.redraw ();
				return true;
			}
			
			version_menu.menu_icon_action (px, py); // click in the open menu
		}
		
		return false;
	}
	
	public bool click_info (uint button, double px, double py) {
		info.set_position (x + width - 17, y + height - 22.5);
		
		if (has_icons () && info.is_over_icon (px, py)) {
			MainWindow.get_overview ().set_character_info (info);
			return true;
		}
		
		return false;
	}
	
	public bool click (uint button, double px, double py) {
		bool s = (x <= px <= x + width) && (y <= py <= y + height);		
		return s;
	}

	public bool double_click (uint button, double px, double py) {
		selected = (x <= px <= x + width) && (y <= py <= y + height);
		return selected;
	}

	public bool is_on_screen (WidgetAllocation allocation) {
		return y + height > 0 && y < allocation.height;
	}

	public void draw (WidgetAllocation allocation, Context cr) {
		if (!is_on_screen (allocation)) {
			return;
		}

		cr.save ();
		Theme.color (cr, "Background 1");
		cr.rectangle (x, y, width, height);
		cr.fill ();
		cr.restore ();

		cr.save ();
		Theme.color (cr, "Overview Item Border");
		cr.rectangle (x, y, width, height);
		cr.set_line_width (1);
		cr.stroke ();
		cr.restore ();
		
		draw_thumbnail (cr, x, y + height);
		
		draw_caption (cr);
		draw_menu (cr);
	}

	private void draw_thumbnail (Context cr, double x, double y) {		
		if (cache != null) {
			cr.save ();
			cr.set_antialias (Cairo.Antialias.NONE);
			cr.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ());	
			cr.set_source_surface ((!) cache, (int) (x * Screen.get_scale ()), (int) ((y - height)) * Screen.get_scale ());
			cr.paint ();
			cr.restore ();
		}
	}

	public bool has_icons () {
		return width > 50;
	}

	public void draw_caption (Context cr) {
		draw_label_background (cr);
		
		cr.save ();
		
		if (glyphs != null) {
			if (selected) {
				Theme.text_color (label, "Overview Selected Foreground");
			} else {
				Theme.text_color (label, "Overview Foreground");
			}
		
			label.draw_at_baseline (cr, x + 0.08 * width, y + height - 6);
		}
		
		cr.restore ();
	}
	
	public void create_label_background_cache (Context cr) {
		Context cc;
		Cairo.Pattern p;
		Surface cache;
			
		// unselected item
		cache = Screen.create_background_surface ((int) width + 1, 20);
		cc = new Context (cache);
		cc.scale(Screen.get_scale(), Screen.get_scale());

		cc.rectangle (0, 0, width, 20 - 1);
		p = new Cairo.Pattern.linear (0.0, 0, 0.0, 20);
		Theme.gradient (p, "Overview Item 1", "Overview Item 2");
		cc.set_source (p);
			
		cc.fill ();
		
		if (has_icons ()) {
			draw_menu_icon (cc, false);
			draw_character_info_icon (cc);
		}

		label_background = (!) cache;	

		// selected item
		cache = Screen.create_background_surface ((int) width + 1, 20);
		cc = new Context (cache);
		cc.scale(Screen.get_scale(), Screen.get_scale());

		cc.rectangle (0, 0, width, 20 - 1);

		Theme.color (cc, "Selected Overview Item");
			
		cc.fill ();
		
		if (has_icons ()) {
			draw_menu_icon (cc, true);
			draw_character_info_icon (cc);
		}

		selected_label_background = (!) cache;	
	
		// deselected item without menu icon
		cache = Screen.create_background_surface ((int) width, 20);
		cc = new Context (cache);
		cc.scale(Screen.get_scale(), Screen.get_scale());
		cc.rectangle (0, 0, width - 1, 20 - 1);
		p = new Cairo.Pattern.linear (0.0, 0, 0.0, 20);
		Theme.gradient (p, "Overview Item 1", "Overview Item 2");
		cc.set_source (p);
		cc.fill ();

		if (has_icons ()) {
			draw_character_info_icon (cc);
		}
		
		label_background_no_menu = (!) cache;

		// selected item
		cache = Screen.create_background_surface ((int) width + 1, 20);
		cc = new Context (cache);
		cc.scale(Screen.get_scale(), Screen.get_scale());
		cc.rectangle (0, 0, width, 20 - 1);
		Theme.color (cc, "Selected Overview Item");
		cc.fill ();

		if (has_icons ()) {
			draw_character_info_icon (cc);
		}
				
		selected_label_background_no_menu = (!) cache;		
	}
	
	bool has_menu () {
		return glyphs != null;
	}
	
	public void draw_label_background (Context cr) {
		Surface cache;
		bool icon;
		
		if (unlikely (label_background == null)) {
			create_label_background_cache (cr);
		}
		
		if (label_background != null 
			&& selected_label_background != null
			&& label_background_no_menu != null
			&& selected_label_background_no_menu != null) {
			
			icon = has_menu ();
			if (selected && icon) {
				cache = (!) selected_label_background;
			} else if (!selected && icon) {
				cache = (!) label_background;
			} else if (selected && !icon) {
				cache = (!) selected_label_background_no_menu;
			} else {
				cache = (!) label_background_no_menu;
			}
			
			Screen.paint_background_surface (cr, cache, (int) x, (int) (y + height - 19));
		}
	}
	
	private void draw_character_info_icon (Context cr) {
		info.draw_icon (cr, selected, width - 17, -1.5);
	}
	
	public void hide_menu () {
		if (!is_null (version_menu)) {
			version_menu.menu_visible = false;
		}
	}
	
	private void draw_menu_icon (Context cc, bool selected) {
		Text icon;

		icon = new Text ("dropdown_menu", 17);
		icon.load_font ("icons.birdfont");

		if (selected) {
			Theme.text_color (icon, "Overview Selected Foreground");
		} else {
			Theme.text_color (icon, "Overview Foreground");
		}
		
		icon.draw_at_top (cc, width - 32, 0);
	}
	
	private void draw_menu (Context cr) {
		if (likely (glyphs == null || !version_menu.menu_visible)) {
			return;
		}
		
		version_menu.draw_menu (cr);
	}
}

}

Contact

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