.
1 /*
2 Copyright (C) 2012 2014 2015 Johan Mattsson
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 3 of the
7 License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 */
14
15 using Cairo;
16 using Math;
17
18 [CCode (cname = "draw_overview_glyph")]
19 public extern bool draw_overview_glyph (Context context, string font_file, double width, double height, unichar character);
20
21 namespace BirdFont {
22
23 public class OverViewItem : GLib.Object {
24 public unichar character = '\0';
25 public GlyphCollection? glyphs;
26 public double x;
27 public double y;
28 public bool selected = false;
29 public CharacterInfo info;
30
31 public static double DEFAULT_WIDTH = 100;
32 public static double DEFAULT_HEIGHT = 130;
33 public static double DEFAULT_MARGIN = 20;
34
35 public static double width = 100;
36 public static double height = 130;
37 public static double margin = 20;
38
39 public static double glyph_scale = 1.0;
40
41 public VersionList version_menu;
42 Text label;
43
44 private Surface? cache = null;
45
46 public static Surface? label_background = null;
47 public static Surface? selected_label_background = null;
48 public static Surface? label_background_no_menu = null;
49 public static Surface? selected_label_background_no_menu = null;
50
51 public OverViewItem () {
52 }
53
54 public void set_character (unichar character) {
55 this.character = character;
56 }
57
58 public void set_glyphs (GlyphCollection? gc) {
59 glyphs = gc;
60
61 if (glyphs != null) {
62 version_menu = new VersionList ((!) glyphs);
63 version_menu.add_glyph_item.connect ((glyph) => {
64 ((!) glyphs).insert_glyph (glyph, true);
65 });
66
67 version_menu.signal_delete_item.connect ((glyph_index) => {
68 OverView v = MainWindow.get_overview ();
69 version_menu = new VersionList ((!) glyphs);
70 v.update_item_list ();
71 GlyphCanvas.redraw ();
72 });
73 }
74
75 info = new CharacterInfo (character, glyphs);
76
77 if (glyphs == null) {
78 label = new Text ();
79 } else {
80 if (character != '\0') {
81 label = new Text ((!) character.to_string (), 17);
82 } else {
83 label = new Text ((!) info.get_name (), 17);
84 }
85
86 truncate_label ();
87 }
88
89 draw_background ();
90 }
91
92 public void clear_cache () {
93 cache = null;
94
95 if (glyphs != null) {
96 Glyph g = ((!) glyphs).get_current ();
97 g.overview_thumbnail = null;
98 }
99 }
100
101 public void draw_glyph_from_font () {
102 if (glyphs == null) {
103 return;
104 }
105
106 Glyph g;
107 double gx, gy;
108 double x1, x2, y1, y2;
109 double scale_box;
110 double w, h;
111 double glyph_width, glyph_height;
112 Surface s;
113 Context c;
114 Color color = Color.black ();
115
116 g = ((!) glyphs).get_current ();
117
118 if (likely (g.overview_thumbnail != null)) {
119 cache = g.overview_thumbnail;
120 return;
121 }
122
123 w = width;
124 h = height;
125
126 scale_box = (height / DEFAULT_HEIGHT) * 0.65;
127
128 s = Screen.create_background_surface ((int) width, (int) height - 20);
129 c = new Context (s);
130
131 c.save ();
132 g.boundaries (out x1, out y1, out x2, out y2);
133
134 glyph_width = x2 - x1;
135 glyph_height = y2 - y1;
136
137 c.save ();
138 c.scale (scale_box * Screen.get_scale (), scale_box * Screen.get_scale ());
139
140 g.add_help_lines ();
141
142 gx = ((w / scale_box) - glyph_width) / 2 - g.get_left_side_bearing ();
143 gy = h / scale_box + g.get_baseline () - 20 / scale_box - 20;
144
145 c.translate (gx - Glyph.xc () - g.get_lsb (), gy - Glyph.yc ());
146
147 g.draw_paths (c, color);
148 c.restore ();
149
150 cache = s;
151 g.overview_thumbnail = s;
152
153 GlyphCanvas.redraw ();
154 }
155
156 public void draw_background () {
157 double scale_box;
158 double w, h;
159 Surface s;
160 Context c;
161
162 w = width;
163 h = height;
164
165 scale_box = width / DEFAULT_WIDTH;
166 s = Screen.create_background_surface ((int) width, (int) height - 20);
167 c = new Context (s);
168
169 if (glyphs != null) { // FIXME: lock
170 draw_glyph_from_font ();
171 } else {
172 c.scale (Screen.get_scale (), Screen.get_scale ());
173
174 c.save ();
175
176 bool glyph_found;
177 string? font_file;
178
179 Theme.color (c, "Overview Glyph");
180
181 font_file = FontCache.fallback_font.get_default_font_file ();
182 glyph_found = draw_overview_glyph (c, (!) font_file, width, height, character);
183
184 if (!glyph_found) {
185 font_file = find_font (FallbackFont.font_config, (!) character.to_string ());
186
187 if (font_file != null) {
188 string path = (!) font_file;
189
190 if (!path.has_suffix("LastResort.ttf")) {
191 draw_overview_glyph (c, path, width, height, character);
192 }
193 }
194 }
195
196 c.restore ();
197
198 cache = s;
199 GlyphCanvas.redraw ();
200 }
201 }
202
203 public static void reset_label () {
204 label_background = null;
205 selected_label_background = null;
206 }
207
208 void truncate_label () {
209 double w = has_icons () ? width - 43 : width;
210 label.truncate (w);
211 }
212
213 public string get_name () {
214 StringBuilder s;
215
216 if (glyphs != null) {
217 return ((!) glyphs).get_name ();
218 }
219
220 s = new StringBuilder ();
221 s.append_unichar (character);
222
223 return s.str;
224 }
225
226 public void set_selected (bool s) {
227 selected = s;
228 }
229
230 public static double full_width () {
231 return width + margin;
232 }
233
234 public static double full_height () {
235 return height + margin;
236 }
237
238 public bool click (uint button, double px, double py) {
239 bool a;
240 GlyphCollection g;
241 bool s = (x <= px <= x + width) && (y <= py <= y + height);
242
243 if (has_icons () && glyphs != null) {
244 g = (!) glyphs;
245 version_menu.set_position (x + width - 21, y + height - 18);
246 a = version_menu.menu_item_action (px, py); // select one item on the menu
247
248 if (a) {
249 return s;
250 }
251
252 version_menu.menu_icon_action (px, py); // click in the open menu
253 }
254
255 info.set_position (x + width - 17, y + height - 22.5);
256 if (has_icons () && info.is_over_icon (px, py)) {
257 MainWindow.get_overview ().set_character_info (info);
258 }
259
260 return s;
261 }
262
263 public bool double_click (uint button, double px, double py) {
264 selected = (x <= px <= x + width) && (y <= py <= y + height);
265 return selected;
266 }
267
268 public bool is_on_screen (WidgetAllocation allocation) {
269 return y + height > 0 && y < allocation.height;
270 }
271
272 public void draw (WidgetAllocation allocation, Context cr) {
273 if (!is_on_screen (allocation)) {
274 return;
275 }
276
277 cr.save ();
278 Theme.color (cr, "Background 1");
279 cr.rectangle (x, y, width, height);
280 cr.fill ();
281 cr.restore ();
282
283 cr.save ();
284 Theme.color (cr, "Overview Item Border");
285 cr.rectangle (x, y, width, height);
286 cr.set_line_width (1);
287 cr.stroke ();
288 cr.restore ();
289
290 draw_thumbnail (cr, x, y + height);
291
292 draw_caption (cr);
293 draw_menu (cr);
294 }
295
296 private void draw_thumbnail (Context cr, double x, double y) {
297 if (cache != null) {
298 cr.save ();
299 cr.set_antialias (Cairo.Antialias.NONE);
300 cr.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ());
301 cr.set_source_surface ((!) cache, (int) (x * Screen.get_scale ()), (int) ((y - height)) * Screen.get_scale ());
302 cr.paint ();
303 cr.restore ();
304 }
305 }
306
307 public bool has_icons () {
308 return width > 50;
309 }
310
311 public void draw_caption (Context cr) {
312 draw_label_background (cr);
313
314 cr.save ();
315
316 if (glyphs != null) {
317 if (selected) {
318 Theme.text_color (label, "Overview Selected Foreground");
319 } else {
320 Theme.text_color (label, "Overview Foreground");
321 }
322
323 label.draw_at_baseline (cr, x + 0.08 * width, y + height - 6);
324 }
325
326 cr.restore ();
327 }
328
329 public void create_label_background_cache (Context cr) {
330 Context cc;
331 Cairo.Pattern p;
332 Surface cache;
333
334 // unselected item
335 cache = Screen.create_background_surface ((int) width + 1, 20);
336 cc = new Context (cache);
337 cc.scale(Screen.get_scale(), Screen.get_scale());
338
339 cc.rectangle (0, 0, width, 20 - 1);
340 p = new Cairo.Pattern.linear (0.0, 0, 0.0, 20);
341 Theme.gradient (p, "Overview Item 1", "Overview Item 2");
342 cc.set_source (p);
343
344 cc.fill ();
345
346 if (has_icons ()) {
347 draw_menu_icon (cc, false);
348 draw_character_info_icon (cc);
349 }
350
351 label_background = (!) cache;
352
353 // selected item
354 cache = Screen.create_background_surface ((int) width + 1, 20);
355 cc = new Context (cache);
356 cc.scale(Screen.get_scale(), Screen.get_scale());
357
358 cc.rectangle (0, 0, width, 20 - 1);
359
360 Theme.color (cc, "Selected Overview Item");
361
362 cc.fill ();
363
364 if (has_icons ()) {
365 draw_menu_icon (cc, true);
366 draw_character_info_icon (cc);
367 }
368
369 selected_label_background = (!) cache;
370
371 // deselected item without menu icon
372 cache = Screen.create_background_surface ((int) width, 20);
373 cc = new Context (cache);
374 cc.scale(Screen.get_scale(), Screen.get_scale());
375 cc.rectangle (0, 0, width - 1, 20 - 1);
376 p = new Cairo.Pattern.linear (0.0, 0, 0.0, 20);
377 Theme.gradient (p, "Overview Item 1", "Overview Item 2");
378 cc.set_source (p);
379 cc.fill ();
380
381 if (has_icons ()) {
382 draw_character_info_icon (cc);
383 }
384
385 label_background_no_menu = (!) cache;
386
387 // selected item
388 cache = Screen.create_background_surface ((int) width + 1, 20);
389 cc = new Context (cache);
390 cc.scale(Screen.get_scale(), Screen.get_scale());
391 cc.rectangle (0, 0, width, 20 - 1);
392 Theme.color (cc, "Selected Overview Item");
393 cc.fill ();
394
395 if (has_icons ()) {
396 draw_character_info_icon (cc);
397 }
398
399 selected_label_background_no_menu = (!) cache;
400 }
401
402 bool has_menu () {
403 return glyphs != null;
404 }
405
406 public void draw_label_background (Context cr) {
407 Surface cache;
408 bool icon;
409
410 if (unlikely (label_background == null)) {
411 create_label_background_cache (cr);
412 }
413
414 if (label_background != null
415 && selected_label_background != null
416 && label_background_no_menu != null
417 && selected_label_background_no_menu != null) {
418
419 icon = has_menu ();
420 if (selected && icon) {
421 cache = (!) selected_label_background;
422 } else if (!selected && icon) {
423 cache = (!) label_background;
424 } else if (selected && !icon) {
425 cache = (!) selected_label_background_no_menu;
426 } else {
427 cache = (!) label_background_no_menu;
428 }
429
430 Screen.paint_background_surface (cr, cache, (int) x, (int) (y + height - 19));
431 }
432 }
433
434 private void draw_character_info_icon (Context cr) {
435 info.draw_icon (cr, selected, width - 17, -1.5);
436 }
437
438 public void hide_menu () {
439 if (!is_null (version_menu)) {
440 version_menu.menu_visible = false;
441 }
442 }
443
444 private void draw_menu_icon (Context cc, bool selected) {
445 Text icon;
446
447 icon = new Text ("dropdown_menu", 17);
448 icon.load_font ("icons.bf");
449
450 if (selected) {
451 Theme.text_color (icon, "Overview Selected Foreground");
452 } else {
453 Theme.text_color (icon, "Overview Foreground");
454 }
455
456 icon.draw_at_top (cc, width - 32, 0);
457 }
458
459 private void draw_menu (Context cr) {
460 if (likely (glyphs == null || !version_menu.menu_visible)) {
461 return;
462 }
463
464 version_menu.draw_menu (cr);
465 }
466 }
467
468 }
469