.
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
29 public bool selected {
30 get; set;
31 }
32
33 public CharacterInfo info;
34
35 public static double DEFAULT_WIDTH = 100;
36 public static double DEFAULT_HEIGHT = 130;
37 public static double DEFAULT_MARGIN = 20;
38
39 public static double width = 100;
40 public static double height = 130;
41 public static double margin = 20;
42
43 public static double glyph_scale = 1.0;
44
45 public VersionList? version_menu = null;
46 Text label;
47
48 private Surface? cache = null;
49
50 public static Surface? label_background = null;
51 public static Surface? selected_label_background = null;
52 public static Surface? label_background_no_menu = null;
53 public static Surface? selected_label_background_no_menu = null;
54
55 public OverViewItem () {
56 }
57
58 public void set_character (unichar character) {
59 this.character = character;
60 }
61
62 public void set_glyphs (GlyphCollection? gc) {
63 glyphs = gc;
64 }
65
66 public void generate_graphics (bool generate_versions = true) {
67 if (glyphs != null && generate_versions) {
68 VersionList versions = new VersionList ((!) glyphs);
69
70 versions.add_glyph_item.connect ((glyph) => {
71 ((!) glyphs).insert_glyph (glyph, true);
72 });
73
74 versions.signal_delete_item.connect ((glyph_index) => {
75 OverView v = MainWindow.get_overview ();
76 version_menu = new VersionList ((!) glyphs);
77 v.update_item_list ();
78 GlyphCanvas.redraw ();
79 });
80
81 version_menu = versions;
82 }
83
84 info = new CharacterInfo (character, glyphs);
85
86 if (glyphs == null) {
87 label = new Text ();
88 } else {
89 label = new Text ((!) character.to_string (), 17);
90 truncate_label ();
91 }
92
93 draw_background ();
94 }
95
96 public void clear_cache () {
97 cache = null;
98
99 if (glyphs != null) {
100 Glyph g = ((!) glyphs).get_current ();
101 g.overview_thumbnail = null;
102 }
103 }
104
105 public void draw_glyph_from_font () {
106 if (glyphs == null) {
107 return;
108 }
109
110 Glyph g;
111 double gx, gy;
112 double x1, x2, y1, y2;
113 double scale_box;
114 double w, h;
115 double glyph_width, glyph_height;
116 Surface s;
117 Context c;
118
119 g = ((!) glyphs).get_current ();
120
121 if (likely (g.overview_thumbnail != null)) {
122 cache = g.overview_thumbnail;
123 return;
124 }
125
126 w = width;
127 h = height;
128
129 scale_box = width / DEFAULT_WIDTH;
130
131 s = Screen.create_background_surface ((int) width, (int) height - 20);
132 c = new Context (s);
133
134 c.save ();
135 g.boundaries (out x1, out y1, out x2, out y2);
136
137 glyph_width = x2 - x1;
138 glyph_height = y2 - y1;
139
140 c.save ();
141 c.scale (glyph_scale * Screen.get_scale (), glyph_scale * Screen.get_scale ());
142
143 g.add_help_lines ();
144
145 gx = ((w / glyph_scale) - glyph_width) / 2 - g.get_left_side_bearing ();
146 gy = (h / glyph_scale) - 25 / glyph_scale;
147 c.translate (gx - Glyph.xc () - g.get_lsb (), g.get_baseline () + gy - Glyph.yc ());
148 g.draw_layers (c);
149
150 c.restore ();
151
152 cache = s;
153 g.overview_thumbnail = s;
154
155 GlyphCanvas.redraw ();
156 }
157
158 public void draw_background () {
159 double scale_box;
160 Surface s;
161 Context c;
162
163 scale_box = width / DEFAULT_WIDTH;
164 adjust_scale ();
165
166 s = Screen.create_background_surface ((int) width, (int) height - 20);
167 c = new Context (s);
168
169 if (glyphs != null) {
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 static double full_width () {
227 return width + margin;
228 }
229
230 public static double full_height () {
231 return height + margin;
232 }
233
234 public bool click (uint button, double px, double py) {
235 bool a;
236 GlyphCollection g;
237 bool s = (x <= px <= x + width) && (y <= py <= y + height);
238
239 if (has_icons () && glyphs != null && version_menu != null) {
240 g = (!) glyphs;
241 VersionList versions = (!) version_menu;
242 versions.set_position (x + width - 21, y + height - 18);
243 a = versions.menu_item_action (px, py); // select one item on the menu
244
245 if (a) {
246 return s;
247 }
248
249 versions.menu_icon_action (px, py); // click in the open menu
250 }
251
252 info.set_position (x + width - 17, y + height - 22.5);
253 if (has_icons () && info.is_over_icon (px, py)) {
254 MainWindow.get_overview ().set_character_info (info);
255 }
256
257 return s;
258 }
259
260 public bool double_click (uint button, double px, double py) {
261 selected = (x <= px <= x + width) && (y <= py <= y + height);
262 return selected;
263 }
264
265 public bool is_on_screen (WidgetAllocation allocation) {
266 return y + height > 0 && y < allocation.height;
267 }
268
269 public void draw (WidgetAllocation allocation, Context cr) {
270 if (!is_on_screen (allocation)) {
271 return;
272 }
273
274 cr.save ();
275 Theme.color (cr, "Background 1");
276 cr.rectangle (x, y, width, height);
277 cr.fill ();
278 cr.restore ();
279
280 cr.save ();
281 Theme.color (cr, "Overview Item Border");
282 cr.rectangle (x, y, width, height);
283 cr.set_line_width (1);
284 cr.stroke ();
285 cr.restore ();
286
287 draw_thumbnail (cr, x, y + height);
288 draw_caption (cr);
289 draw_menu (cr);
290 }
291
292 public void adjust_scale () {
293 double x1, x2, y1, y2, glyph_width, glyph_height, scale, gx;
294 Glyph g;
295 Font font;
296
297 if (glyphs != null) {
298 font = BirdFont.get_current_font ();
299 g = ((!) glyphs).get_current ();
300
301 if (g.boundaries (out x1, out y1, out x2, out y2)) {
302 glyph_width = x2 - x1;
303 glyph_height = y2 - y1;
304
305 if (glyph_scale == 1) {
306 // caption height is 20
307 glyph_scale = (height - 20) / (font.top_limit - font.bottom_limit);
308 }
309
310 scale = glyph_scale;
311 gx = ((width / scale) - glyph_width) / 2;
312
313 if (gx < 0) {
314 glyph_scale = 1 + 2 * gx / width;
315 }
316 }
317 }
318 }
319
320 private void draw_thumbnail (Context cr, double x, double y) {
321 if (cache != null) {
322 cr.save ();
323 cr.set_antialias (Cairo.Antialias.NONE);
324 cr.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ());
325 cr.set_source_surface ((!) cache, (int) (x * Screen.get_scale ()), (int) ((y - height)) * Screen.get_scale ());
326 cr.paint ();
327 cr.restore ();
328 }
329 }
330
331 public bool has_icons () {
332 return width > 50;
333 }
334
335 public void draw_caption (Context cr) {
336 draw_label_background (cr);
337
338 cr.save ();
339
340 if (glyphs != null) {
341 if (selected) {
342 Theme.text_color (label, "Overview Selected Foreground");
343 } else {
344 Theme.text_color (label, "Overview Foreground");
345 }
346
347 label.draw_at_baseline (cr, x + 0.08 * width, y + height - 6);
348 }
349
350 cr.restore ();
351 }
352
353 public void create_label_background_cache (Context cr) {
354 Context cc;
355 Cairo.Pattern p;
356 Surface cache;
357
358 // unselected item
359 cache = Screen.create_background_surface ((int) width + 1, 20);
360 cc = new Context (cache);
361 cc.scale(Screen.get_scale(), Screen.get_scale());
362
363 cc.rectangle (0, 0, width, 20 - 1);
364 p = new Cairo.Pattern.linear (0.0, 0, 0.0, 20);
365 Theme.gradient (p, "Overview Item 1", "Overview Item 2");
366 cc.set_source (p);
367
368 cc.fill ();
369
370 if (has_icons ()) {
371 if (version_menu != null) {
372 draw_menu_icon (cc, false);
373 }
374
375 draw_character_info_icon (cc);
376 }
377
378 label_background = (!) cache;
379
380 // selected item
381 cache = Screen.create_background_surface ((int) width + 1, 20);
382 cc = new Context (cache);
383 cc.scale(Screen.get_scale(), Screen.get_scale());
384
385 cc.rectangle (0, 0, width, 20 - 1);
386
387 Theme.color (cc, "Selected Overview Item");
388
389 cc.fill ();
390
391 if (has_icons ()) {
392 draw_menu_icon (cc, true);
393 draw_character_info_icon (cc);
394 }
395
396 selected_label_background = (!) cache;
397
398 // deselected item without menu icon
399 cache = Screen.create_background_surface ((int) width, 20);
400 cc = new Context (cache);
401 cc.scale(Screen.get_scale(), Screen.get_scale());
402 cc.rectangle (0, 0, width - 1, 20 - 1);
403 p = new Cairo.Pattern.linear (0.0, 0, 0.0, 20);
404 Theme.gradient (p, "Overview Item 1", "Overview Item 2");
405 cc.set_source (p);
406 cc.fill ();
407
408 if (has_icons ()) {
409 draw_character_info_icon (cc);
410 }
411
412 label_background_no_menu = (!) cache;
413
414 // selected item
415 cache = Screen.create_background_surface ((int) width + 1, 20);
416 cc = new Context (cache);
417 cc.scale(Screen.get_scale(), Screen.get_scale());
418 cc.rectangle (0, 0, width, 20 - 1);
419 Theme.color (cc, "Selected Overview Item");
420 cc.fill ();
421
422 if (has_icons ()) {
423 draw_character_info_icon (cc);
424 }
425
426 selected_label_background_no_menu = (!) cache;
427 }
428
429 bool has_menu () {
430 return glyphs != null;
431 }
432
433 public void draw_label_background (Context cr) {
434 Surface cache;
435 bool icon;
436
437 if (unlikely (label_background == null)) {
438 create_label_background_cache (cr);
439 }
440
441 if (label_background != null
442 && selected_label_background != null
443 && label_background_no_menu != null
444 && selected_label_background_no_menu != null) {
445
446 icon = has_menu ();
447 if (selected && icon) {
448 cache = (!) selected_label_background;
449 } else if (!selected && icon) {
450 cache = (!) label_background;
451 } else if (selected && !icon) {
452 cache = (!) selected_label_background_no_menu;
453 } else {
454 cache = (!) label_background_no_menu;
455 }
456
457 Screen.paint_background_surface (cr, cache, (int) x, (int) (y + height - 19));
458 }
459 }
460
461 private void draw_character_info_icon (Context cr) {
462 info.draw_icon (cr, selected, width - 17, -1.5);
463 }
464
465 public void hide_menu () {
466 if (!is_null (version_menu)) {
467 VersionList v = (!) version_menu;
468 v.menu_visible = false;
469 }
470 }
471
472 private void draw_menu_icon (Context cc, bool selected) {
473 Text icon;
474
475 icon = new Text ("dropdown_menu", 17);
476 icon.load_font ("icons.bf");
477
478 if (selected) {
479 Theme.text_color (icon, "Overview Selected Foreground");
480 } else {
481 Theme.text_color (icon, "Overview Foreground");
482 }
483
484 icon.draw_at_top (cc, width - 32, 0);
485 }
486
487 private void draw_menu (Context cr) {
488 if (glyphs == null) {
489 return;
490 }
491
492 if (version_menu == null) {
493 return;
494 }
495
496 VersionList v = (!) version_menu;
497
498 if (!v.menu_visible) {
499 return;
500 }
501
502 v.draw_menu (cr);
503 }
504 }
505
506 }
507