The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

TabBar.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/TabBar.vala.
New color theme
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 17 namespace BirdFont { 18 19 public class TabBar : GLib.Object { 20 21 public int width = 0; 22 public int height = 0; 23 24 public Gee.ArrayList<Tab> tabs; 25 26 static const int NO_TAB = -1; 27 static const int NEXT_TAB = -2; 28 static const int PREVIOUS_TAB = -3; 29 static const int PROGRESS_WHEEL = -3; 30 static const int SHOW_MENU = -4; 31 32 int first_tab = 0; 33 int selected = 0; 34 int over = NO_TAB; 35 int over_close_tab = NO_TAB; 36 37 public signal void signal_tab_selected (Tab selected_tab); 38 public signal void redraw_tab_bar (int x, int y, int w, int h); 39 40 Tab? previous_tab = null; 41 Tab? current_tab = null; 42 43 double scale = 1; // scale images in 320 dpi 44 45 bool processing = false; 46 double wheel_rotation = 0; 47 48 double background_r = 51 /255.0; 49 double background_g = 54 /255.0; 50 double background_b = 59 /255.0; 51 52 Text menu_icon; 53 Text progress_icon; 54 Text left_arrow; 55 Text right_arrow; 56 57 public TabBar () { 58 tabs = new Gee.ArrayList<Tab> (); 59 60 menu_icon = new Text ("menu_icon"); 61 menu_icon.load_font (Theme.get_icon_file ()); 62 63 progress_icon = new Text ("progress"); 64 progress_icon.load_font (Theme.get_icon_file ()); 65 66 left_arrow = new Text ("left_arrow"); 67 left_arrow.load_font (Theme.get_icon_file ()); 68 69 right_arrow = new Text ("right_arrow"); 70 right_arrow.load_font (Theme.get_icon_file ()); 71 72 start_wheel (); 73 } 74 75 public void redraw (int x, int y, int w, int h) { 76 redraw_tab_bar (x, y, w, h); 77 } 78 79 public void set_background_color (double r, double g, double b) { 80 background_r = r; 81 background_g = g; 82 background_b = r; 83 } 84 85 public void motion (double x, double y) { 86 if (MenuTab.suppress_event) { 87 warn_if_test ("Event suppressed"); 88 return; 89 } 90 91 over_close (x, y, out over, out over_close_tab); 92 } 93 94 private void over_close (double x, double y, out int over, out int over_close_tab) { 95 int i = 0; 96 double offset = 0; 97 bool close_y, close_x; 98 99 if (x < 24 && has_scroll ()) { 100 over_close_tab = NO_TAB; 101 over = PREVIOUS_TAB; 102 return; 103 } 104 105 if (!has_progress_wheel ()) { 106 if (x > width - 25) { 107 over_close_tab = NO_TAB; 108 over = SHOW_MENU; 109 return; 110 } 111 } else if (has_scroll () && has_progress_wheel ()) { 112 if (x > width - 19) { 113 over_close_tab = NO_TAB; 114 over = PROGRESS_WHEEL; 115 } else if (x > width - 2 * 19) { 116 over_close_tab = NO_TAB; 117 over = NEXT_TAB; 118 } 119 } else if (!has_scroll () && has_progress_wheel ()) { 120 if (x > width - 19) { 121 over_close_tab = NO_TAB; 122 over = PROGRESS_WHEEL; 123 } 124 } else if (has_scroll () && !has_progress_wheel ()) { 125 if (x > width - 19) { 126 over_close_tab = NO_TAB; 127 over = NEXT_TAB; 128 } 129 } 130 131 if (has_scroll ()) { 132 offset += 25; 133 } 134 135 foreach (Tab t in tabs) { 136 if (i < first_tab) { 137 i++; 138 continue; 139 } 140 141 if (offset < x < offset + t.get_width ()) { 142 over = i; 143 144 close_y = height / 2.0 - 4 < y < height / 2.0 + 4; 145 close_x = x > offset + t.get_width () - 16; 146 147 if (close_y && close_x) { 148 over_close_tab = i; 149 } else { 150 over_close_tab = NO_TAB; 151 } 152 153 return; 154 } 155 156 offset += t.get_width (); 157 i++; 158 } 159 160 over_close_tab = NO_TAB; 161 over = NO_TAB; 162 } 163 164 165 /** Select tab for a glyph by charcode or name. 166 * @return true if the tab was found 167 */ 168 public bool select_char (string s) { 169 int i = 0; 170 171 if (MenuTab.suppress_event) { 172 warn_if_test ("Event suppressed"); 173 return false; 174 } 175 176 foreach (Tab t in tabs) { 177 if (t.get_display ().get_name () == s) { 178 select_tab (i); 179 return true; 180 } 181 i++; 182 } 183 184 return false; 185 } 186 187 public bool select_tab_name (string s) { 188 if (MenuTab.suppress_event) { 189 warn_if_test ("Event suppressed"); 190 return false; 191 } 192 193 return select_char (s); 194 } 195 196 public void select_overview () { 197 if (MenuTab.suppress_event) { 198 warn_if_test ("Event suppressed"); 199 return; 200 } 201 202 select_tab_name ("Overview"); 203 } 204 205 private void select_previous_tab () { 206 Tab t; 207 bool open; 208 209 if (MenuTab.suppress_event) { 210 warn_if_test ("Event suppressed"); 211 return; 212 } 213 214 if (previous_tab == null) { 215 return; 216 } 217 218 t = (!) previous_tab; 219 open = selected_open_tab (t); 220 221 if (!open) { 222 select_tab ((int) tabs.size - 1); 223 } 224 } 225 226 public void close_display (FontDisplay f) { 227 int i = -1; 228 229 if (MenuTab.suppress_event) { 230 warn_if_test ("Event suppressed"); 231 return; 232 } 233 234 if (tabs.size >= 1) { 235 foreach (Tab t in tabs) { 236 ++i; 237 238 if (t.get_display () == f) { 239 close_tab (i) ; 240 return; 241 } 242 } 243 } 244 245 return_if_fail (i != -1); 246 } 247 248 public void close_all_tabs () { 249 if (MenuTab.suppress_event) { 250 warn_if_test ("Event suppressed"); 251 return; 252 } 253 254 for (int i = 0; i < get_length (); i++) { 255 if (close_tab (i, false, true)) { 256 close_all_tabs (); 257 } 258 } 259 } 260 261 public bool close_tab (int index, bool background_tab = false, bool select_new_tab = true) { 262 Tab t; 263 EmptyTab empty_tab_canvas; 264 Tab empty_tab; 265 GlyphCollection gc; 266 267 if (MenuTab.suppress_event) { 268 warn_if_test ("Event suppressed"); 269 return false; 270 } 271 272 if (!(0 <= index < tabs.size)) { 273 return false; 274 } 275 276 if (tabs.size == 1) { 277 empty_tab_canvas = new EmptyTab ("", ""); 278 gc = new GlyphCollection.with_glyph('\0', ""); 279 GlyphCanvas.set_display (empty_tab_canvas); 280 MainWindow.get_glyph_canvas ().set_current_glyph_collection (gc); 281 empty_tab = new Tab (empty_tab_canvas, 0, false); 282 signal_tab_selected (empty_tab); 283 } 284 285 t = tabs.get (index); 286 287 if (first_tab > 0) { 288 first_tab--; 289 } 290 291 if (t.has_close_button ()) { 292 t.get_display ().close (); 293 294 tabs.remove_at (index); 295 296 if (!background_tab && select_new_tab) { 297 select_previous_tab (); 298 } 299 300 return true; 301 } 302 303 if (select_new_tab) { 304 select_tab (index); 305 } 306 307 return false; 308 } 309 310 public bool close_by_name (string name, bool background_tab = false) { 311 int i = 0; 312 313 foreach (var t in tabs) { 314 if (t.get_display ().get_name () == name) { 315 return close_tab (i, background_tab); 316 } 317 318 i++; 319 } 320 321 return false; 322 } 323 324 public void close_background_tab_by_name (string name) { 325 close_by_name (name, true); 326 } 327 328 /** Select a tab and return true if it is open. */ 329 public bool selected_open_tab (Tab t) { 330 int i = 0; 331 332 if (MenuTab.suppress_event) { 333 warn_if_test ("Event suppressed"); 334 return false; 335 } 336 337 foreach (var n in tabs) { 338 if (n == t) { 339 select_tab (i); 340 return true; 341 } 342 343 i++; 344 } 345 346 return false; 347 } 348 349 public Tab? get_nth (int i) { 350 if (!(0 <= i < get_length ())) { 351 return null; 352 } 353 354 return tabs.get (i); 355 } 356 357 public Tab? get_tab (string name) { 358 foreach (var n in tabs) { 359 if (n.get_display ().get_name () == name) { 360 return n; 361 } 362 } 363 364 return null; 365 } 366 367 public bool selected_open_tab_by_name (string t) { 368 int i = 0; 369 370 if (MenuTab.suppress_event) { 371 warn_if_test ("Event suppressed"); 372 return false; 373 } 374 375 foreach (var n in tabs) { 376 if (n.get_display ().get_name () == t) { 377 select_tab (i); 378 return true; 379 } 380 381 i++; 382 } 383 384 return false; 385 } 386 387 public Tab get_selected_tab () { 388 int s = get_selected (); 389 if (0 <= s < tabs.size) { 390 return tabs.get (get_selected ()); 391 } 392 393 warning ("No tab selected."); 394 return new Tab (new EmptyTab ("Error", "Error"), 30, false); 395 } 396 397 public uint get_length () { 398 return tabs.size; 399 } 400 401 public int get_selected () { 402 return selected; 403 } 404 405 public void select_tab (int index, bool signal_selected = true) { 406 Tab t; 407 408 if (MenuTab.suppress_event) { 409 warn_if_test ("Event suppressed"); 410 return; 411 } 412 413 // always close any pending text input if the user switches tab 414 TabContent.hide_text_input (); 415 416 if (index == SHOW_MENU) { 417 MainWindow.get_menu ().show_menu = !MainWindow.get_menu ().show_menu; 418 GlyphCanvas.redraw (); 419 return; 420 } 421 422 if (index == NEXT_TAB) { 423 selected++; 424 425 if (selected >= tabs.size) { 426 selected = (int) tabs.size - 1; 427 } 428 429 scroll_to_tab (selected); 430 return; 431 } 432 433 if (index == PREVIOUS_TAB) { 434 435 if (selected > 0) { 436 selected--; 437 } 438 439 scroll_to_tab (selected); 440 return; 441 } 442 443 if (!(0 <= index < tabs.size)) { 444 return; 445 } 446 447 selected = index; 448 449 t = tabs.get (index); 450 451 previous_tab = current_tab; 452 current_tab = t; 453 454 scroll_to_tab (selected, signal_selected); 455 } 456 457 private bool has_scroll () { 458 int i = 0; 459 double offset = 19; 460 double end = (has_progress_wheel ()) ? width - 28 : width - 19; 461 462 if (first_tab > 0) { 463 return true; 464 } 465 466 foreach (Tab t in tabs) { 467 if (i < first_tab) { 468 i++; 469 continue; 470 } 471 472 if (offset + t.get_width () + 3 > end) { 473 return true; 474 } 475 476 offset += t.get_width (); 477 i++; 478 } 479 480 return false; 481 } 482 483 private void signal_selected (int index) { 484 Tab t; 485 486 t = tabs.get (index); 487 488 GlyphCanvas.set_display (t.get_display ()); 489 490 MainWindow.get_glyph_canvas () 491 .set_current_glyph_collection (t.get_glyph_collection ()); 492 493 signal_tab_selected (t); 494 } 495 496 private void scroll_to_tab (int index, bool send_signal_selected = true) { 497 double offset = 19; 498 int i = 0; 499 double end = (has_progress_wheel ()) ? width - 68 : width - 40; 500 501 if (index < first_tab) { 502 first_tab = index; 503 504 if (send_signal_selected) { 505 signal_selected (index); 506 } 507 return; 508 } 509 510 foreach (Tab t in tabs) { 511 512 if (i < first_tab) { 513 i++; 514 continue; 515 } 516 517 // out of view 518 if (offset + t.get_width () + 3 > end) { 519 first_tab++; 520 scroll_to_tab (index); 521 return; 522 } 523 524 // in view 525 if (i == index) { 526 527 if (send_signal_selected) { 528 signal_selected (index); 529 } 530 531 return; 532 } 533 534 offset += t.get_width (); 535 i++; 536 } 537 538 warning (""); 539 } 540 541 public void select_tab_click (double x, double y, int width, int height) { 542 int over, close; 543 544 if (MainWindow.get_menu ().show_menu) { 545 MainWindow.get_menu ().show_menu = false; 546 GlyphCanvas.redraw (); 547 } 548 549 this.width = width; 550 this.height = height; 551 this.scale = height / 117.0; 552 553 over_close (x, y, out over, out close); 554 555 if (over_close_tab >= 0 && over == selected) { 556 close_tab (over_close_tab); 557 } else { 558 select_tab (over); 559 } 560 } 561 562 public void add_tab (FontDisplay display_item, bool signal_selected = true, GlyphCollection? gc = null) { 563 double tab_width = -1; 564 bool always_open = false; 565 int s = (tabs.size == 0) ? 0 : selected + 1; 566 Tab t; 567 568 if (MenuTab.suppress_event) { 569 warn_if_test ("Event suppressed"); 570 return; 571 } 572 573 if (tab_width < 0) { 574 tab_width = 9 * display_item.get_label ().char_count (); 575 tab_width += 36; 576 } 577 578 t = new Tab (display_item, tab_width, always_open); 579 tabs.insert (s,t); 580 581 if (gc != null) { 582 t.set_glyph_collection ((!) gc); 583 } 584 585 GlyphCanvas.set_display (t.get_display ()); 586 587 MainWindow.get_glyph_canvas () 588 .set_current_glyph_collection (t.get_glyph_collection ()); 589 590 select_tab (s, signal_selected); 591 } 592 593 /** Returns true if the new item was added to the bar. */ 594 public bool add_unique_tab (FontDisplay display_item, bool signal_selected = true) { 595 bool i; 596 597 if (MenuTab.suppress_event) { 598 warn_if_test ("Event suppressed"); 599 return false; 600 } 601 602 i = select_tab_name (display_item.get_name ()); 603 604 if (!i) { 605 add_tab (display_item, signal_selected); 606 return true; 607 } 608 609 return false; 610 } 611 612 public void draw (Context cr, int width, int height) { 613 double next_tab_x; 614 double w, h; 615 616 this.width = width; 617 this.height = height; 618 this.scale = height / 117.0; 619 620 cr.save (); 621 cr.set_line_width (0); 622 Theme.color (cr, "Background 4"); 623 cr.rectangle (0, 0, width, height); 624 cr.fill (); 625 cr.restore (); 626 627 cr.save (); 628 cr.scale (scale, scale); 629 630 w = width / scale; 631 h = height / scale; 632 633 if (has_scroll () && !has_progress_wheel ()) { 634 // left arrow 635 Theme.text_color (left_arrow, "Foreground 2"); 636 left_arrow.set_font_size (40 / scale); 637 left_arrow.widget_x = 2 / scale; 638 left_arrow.widget_y = h / 2.0 - (40 / scale ) / 2; 639 left_arrow.draw (cr); 640 641 // right arrow 642 Theme.text_color (right_arrow, "Foreground 2"); 643 next_tab_x = (has_progress_wheel ()) ? w - (2 * 19 + 3) / scale : w - 19 / scale; 644 next_tab_x-= 32 / scale; 645 646 right_arrow.set_font_size (40 / scale); 647 right_arrow.widget_x = next_tab_x; 648 right_arrow.widget_y = h / 2.0 - (40 / scale ) / 2; 649 right_arrow.draw (cr); 650 } 651 652 // progress wheel 653 if (has_progress_wheel ()) { 654 double progress_size = 40 / scale; 655 656 Theme.text_color (progress_icon, "Foreground 2"); 657 658 progress_icon.set_font_size (progress_size); 659 progress_icon.widget_x = w - 14 / scale; 660 progress_icon.widget_y = h / 2.0; 661 progress_icon.use_cache (false); 662 663 cr.save (); 664 cr.translate (progress_icon.widget_x, progress_icon.widget_y); 665 cr.rotate (wheel_rotation); 666 cr.translate (-progress_icon.widget_x, -progress_icon.widget_y); 667 668 progress_icon.draw (cr); 669 cr.restore (); 670 } else { 671 // menu icon 672 if (MainWindow.get_menu ().show_menu) { 673 Theme.color (cr, "Background 3"); 674 cr.rectangle (w - 33 / scale, 0, 33 / scale, h); 675 cr.fill (); 676 } 677 678 if (MainWindow.get_menu ().show_menu) { 679 Theme.text_color (menu_icon, "Foreground Inverted"); 680 } else { 681 Theme.text_color (menu_icon, "Highlighted 1"); 682 } 683 684 menu_icon.set_font_size (40 / scale); 685 menu_icon.widget_x = (int) (w - 25 / scale); 686 menu_icon.widget_y = (int) (h / 2.0 - 20 / scale); 687 menu_icon.draw (cr); 688 } 689 690 draw_tabs (cr); 691 cr.restore (); 692 } 693 694 private void draw_tabs (Context cr) { 695 double text_height, text_width, center_x, center_y; 696 double close_opacity; 697 double offset; 698 double tab_width; 699 double tabs_end = width / scale; 700 double h = height / scale; 701 double tab_height; 702 Tab t; 703 Text label; 704 705 if (has_progress_wheel ()) { 706 tabs_end -= 19 / scale; 707 } 708 709 if (has_scroll ()) { 710 tabs_end -= 60 / scale; 711 offset = 24 / scale; 712 } else { 713 offset = 0; 714 } 715 716 tab_height = this.height / scale; 717 718 for (int tab_index = first_tab; tab_index < tabs.size; tab_index++) { 719 t = tabs.get (tab_index); 720 721 cr.save (); 722 cr.translate (offset, 0); 723 724 tab_width = t.get_width () / scale; 725 726 if (offset + tab_width > tabs_end) { 727 cr.restore (); 728 break; 729 } 730 731 // background 732 if (tab_index == selected) { 733 cr.save (); 734 Theme.color (cr, "Highlighted 1"); 735 cr.rectangle (0, 0, tab_width, h); 736 cr.fill (); 737 cr.restore (); 738 } else if (tab_index == over) { 739 cr.save (); 740 Theme.color (cr, "Background 7"); 741 cr.rectangle (0, 0, tab_width, h); 742 cr.fill (); 743 cr.restore (); 744 } else { 745 cr.save (); 746 Theme.color (cr, "Background 4"); 747 cr.rectangle (0, 0, tab_width, h); 748 cr.fill (); 749 cr.restore (); 750 } 751 752 // close (x) 753 if (t.has_close_button ()) { 754 cr.save (); 755 cr.new_path (); 756 cr.set_line_width (1 / scale); 757 758 close_opacity = (over_close_tab == tab_index) ? 1 : 0.2; 759 760 if (tab_index == selected) { 761 Theme.color_opacity (cr, "Foreground 4", close_opacity); 762 } else { 763 Theme.color_opacity (cr, "Foreground 2", close_opacity); 764 } 765 766 cr.move_to (tab_width - 7 / scale, h / 2.0 - 2.5 / scale); 767 cr.line_to (tab_width - 12 / scale, h / 2.0 + 2.5 / scale); 768 769 cr.move_to (tab_width - 12 / scale, h / 2.0 - 2.5 / scale); 770 cr.line_to (tab_width - 7 / scale, h / 2.0 + 2.5 / scale); 771 772 cr.stroke (); 773 cr.restore (); 774 } 775 776 // tab label 777 label = new Text (); 778 label.use_cache (false); 779 label.set_text (t.get_label ()); 780 text_height = (int) (16 / scale); 781 label.set_font_size (text_height); 782 text_width = label.get_extent (); 783 center_x = tab_width / 2.0 - text_width / 2.0; 784 center_y = (int) (tab_height / 2.0 + 4 / scale); 785 786 if (tab_index == selected) { 787 Theme.text_color (label, "Foreground 4"); 788 } else { 789 Theme.text_color (label, "Foreground 2"); 790 } 791 792 label.set_font_size (text_height); 793 label.draw_at_baseline (cr, center_x, center_y); 794 795 // edges 796 if (tab_index != selected) { // don't draw edges for the selected tab 797 if (tab_index + 1 != selected) { 798 cr.save (); 799 Theme.color (cr, "Foreground 5"); 800 cr.rectangle (tab_width - 1 / scale, 0, 1 / scale, h); 801 cr.fill (); 802 cr.restore (); 803 } 804 805 if (tab_index == first_tab) { 806 cr.save (); 807 Theme.color (cr, "Foreground 5"); 808 cr.rectangle (0, 0, 1 / scale, h); 809 cr.fill (); 810 cr.restore (); 811 } 812 } 813 814 cr.restore (); 815 816 offset += tab_width; 817 } 818 } 819 820 public void add_empty_tab (string name, string label) { 821 add_tab (new EmptyTab (name, label)); 822 } 823 824 bool has_progress_wheel () { 825 return processing; 826 } 827 828 public void set_progress (bool running) { 829 TimeoutSource timer; 830 831 if (unlikely (processing == running)) { 832 warning (@"Progress is already set to $running"); 833 return; 834 } 835 836 processing = running; 837 838 if (processing) { 839 timer = new TimeoutSource (50); 840 timer.set_callback (() => { 841 wheel_rotation += 0.008 * 2 * Math.PI; 842 843 if (wheel_rotation > 2 * Math.PI) { 844 wheel_rotation -= 2 * Math.PI; 845 } 846 847 redraw_tab_bar (width - 40, 0, 40, height); 848 return processing; 849 }); 850 timer.attach (null); 851 } 852 } 853 854 public static void start_wheel () { 855 TabBar t; 856 if (!is_null (MainWindow.get_tab_bar ())) { 857 t = MainWindow.get_tab_bar (); 858 t.set_progress (true); 859 } 860 } 861 862 public static void stop_wheel () { 863 if (!is_null (MainWindow.get_tab_bar ())) { 864 MainWindow.get_tab_bar ().set_progress (false); 865 } 866 } 867 } 868 869 } 870