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