… | |
… | |
333 | } |
333 | } |
334 | |
334 | |
335 | sub size_request { |
335 | sub size_request { |
336 | require Carp; |
336 | require Carp; |
337 | Carp::confess "size_request is abstract"; |
337 | Carp::confess "size_request is abstract"; |
|
|
338 | } |
|
|
339 | |
|
|
340 | sub baseline_shift { |
|
|
341 | 0 |
338 | } |
342 | } |
339 | |
343 | |
340 | sub configure { |
344 | sub configure { |
341 | my ($self, $x, $y, $w, $h) = @_; |
345 | my ($self, $x, $y, $w, $h) = @_; |
342 | |
346 | |
… | |
… | |
1091 | if exists $self->{title}; |
1095 | if exists $self->{title}; |
1092 | |
1096 | |
1093 | if ($self->{has_close_button}) { |
1097 | if ($self->{has_close_button}) { |
1094 | $self->{close_button} = |
1098 | $self->{close_button} = |
1095 | new CFClient::UI::ImageButton |
1099 | new CFClient::UI::ImageButton |
1096 | image => 'x1_close.png', |
1100 | path => 'x1_close.png', |
1097 | on_activate => sub { $self->hide }; |
1101 | on_activate => sub { $self->hide }; |
1098 | |
1102 | |
1099 | $self->CFClient::UI::Container::add ($self->{close_button}); |
1103 | $self->CFClient::UI::Container::add ($self->{close_button}); |
1100 | } |
1104 | } |
1101 | |
1105 | |
… | |
… | |
1438 | |
1442 | |
1439 | sub invoke_size_allocate { |
1443 | sub invoke_size_allocate { |
1440 | my ($self, $w, $h) = @_; |
1444 | my ($self, $w, $h) = @_; |
1441 | |
1445 | |
1442 | my $space = $self->{vertical} ? $h : $w; |
1446 | my $space = $self->{vertical} ? $h : $w; |
1443 | my $children = $self->{children}; |
1447 | my @children = $self->visible_children; |
1444 | |
1448 | |
1445 | my @req; |
1449 | my @req; |
1446 | |
1450 | |
1447 | if ($self->{homogeneous}) { |
1451 | if ($self->{homogeneous}) { |
1448 | @req = ($space / (@$children || 1)) x @$children; |
1452 | @req = ($space / (@children || 1)) x @children; |
1449 | } else { |
1453 | } else { |
1450 | @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @$children; |
1454 | @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @children; |
1451 | my $req = List::Util::sum @req; |
1455 | my $req = List::Util::sum @req; |
1452 | |
1456 | |
1453 | if ($req > $space) { |
1457 | if ($req > $space) { |
1454 | # ah well, not enough space |
1458 | # ah well, not enough space |
1455 | $_ *= $space / $req for @req; |
1459 | $_ *= $space / $req for @req; |
1456 | } else { |
1460 | } else { |
1457 | my $expand = (List::Util::sum map $_->{expand}, @$children) || 1; |
1461 | my $expand = (List::Util::sum map $_->{expand}, @children) || 1; |
1458 | |
1462 | |
1459 | $space = ($space - $req) / $expand; # remaining space to give away |
1463 | $space = ($space - $req) / $expand; # remaining space to give away |
1460 | |
1464 | |
1461 | $req[$_] += $space * $children->[$_]{expand} |
1465 | $req[$_] += $space * $children[$_]{expand} |
1462 | for 0 .. $#$children; |
1466 | for 0 .. $#children; |
1463 | } |
1467 | } |
1464 | } |
1468 | } |
1465 | |
1469 | |
1466 | CFClient::UI::harmonize \@req; |
1470 | CFClient::UI::harmonize \@req; |
1467 | |
1471 | |
1468 | my $pos = 0; |
1472 | my $pos = 0; |
1469 | for (0 .. $#$children) { |
1473 | for (0 .. $#children) { |
1470 | my $alloc = $req[$_]; |
1474 | my $alloc = $req[$_]; |
1471 | $children->[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h)); |
1475 | $children[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h)); |
1472 | |
1476 | |
1473 | $pos += $alloc; |
1477 | $pos += $alloc; |
1474 | } |
1478 | } |
1475 | |
1479 | |
1476 | 1 |
1480 | 1 |
… | |
… | |
1631 | }; |
1635 | }; |
1632 | |
1636 | |
1633 | @{ $self->{size_req} } |
1637 | @{ $self->{size_req} } |
1634 | } |
1638 | } |
1635 | |
1639 | |
|
|
1640 | sub baseline_shift { |
|
|
1641 | $_[0]{layout}->descent |
|
|
1642 | } |
|
|
1643 | |
1636 | sub invoke_size_allocate { |
1644 | sub invoke_size_allocate { |
1637 | my ($self, $w, $h) = @_; |
1645 | my ($self, $w, $h) = @_; |
1638 | |
1646 | |
1639 | delete $self->{ox}; |
1647 | delete $self->{ox}; |
1640 | |
1648 | |
… | |
… | |
2092 | package CFClient::UI::Image; |
2100 | package CFClient::UI::Image; |
2093 | |
2101 | |
2094 | our @ISA = CFClient::UI::Base::; |
2102 | our @ISA = CFClient::UI::Base::; |
2095 | |
2103 | |
2096 | use CFClient::OpenGL; |
2104 | use CFClient::OpenGL; |
2097 | use Carp qw/confess/; |
|
|
2098 | |
2105 | |
2099 | our %loaded_images; |
2106 | our %texture_cache; |
2100 | |
2107 | |
2101 | sub new { |
2108 | sub new { |
2102 | my $class = shift; |
2109 | my $class = shift; |
2103 | |
2110 | |
2104 | my $self = $class->SUPER::new (can_events => 0, @_); |
2111 | my $self = $class->SUPER::new ( |
|
|
2112 | can_events => 0, |
|
|
2113 | @_, |
|
|
2114 | ); |
2105 | |
2115 | |
2106 | $self->{image} or confess "Image has 'image' not set. This is a fatal error!"; |
2116 | $self->{path} |
|
|
2117 | or Carp::croak "required attribute 'path' not set"; |
2107 | |
2118 | |
2108 | $loaded_images{$self->{image}} ||= |
2119 | $self->{tex} = $texture_cache{$self->{path}} ||= |
2109 | new_from_file CFClient::Texture CFClient::find_rcfile $self->{image}, mipmap => 1; |
2120 | new_from_file CFClient::Texture CFClient::find_rcfile $self->{path}, mipmap => 1; |
2110 | |
2121 | |
2111 | my $tex = $self->{tex} = $loaded_images{$self->{image}}; |
2122 | Scalar::Util::weaken $texture_cache{$self->{path}}; |
2112 | |
2123 | |
2113 | Scalar::Util::weaken $loaded_images{$self->{image}}; |
2124 | $self->{aspect} ||= $self->{tex}{w} / $self->{tex}{h}; |
2114 | |
|
|
2115 | $self->{aspect} = $tex->{w} / $tex->{h}; |
|
|
2116 | |
2125 | |
2117 | $self |
2126 | $self |
2118 | } |
2127 | } |
2119 | |
2128 | |
2120 | sub size_request { |
2129 | sub size_request { |
2121 | my ($self) = @_; |
2130 | my ($self) = @_; |
2122 | |
2131 | |
2123 | ($self->{tex}->{w}, $self->{tex}->{h}) |
2132 | ($self->{tex}{w}, $self->{tex}{h}) |
2124 | } |
2133 | } |
2125 | |
2134 | |
2126 | sub _draw { |
2135 | sub _draw { |
2127 | my ($self) = @_; |
2136 | my ($self) = @_; |
2128 | |
2137 | |
… | |
… | |
2566 | |
2575 | |
2567 | $self->{fontsize} = $fontsize; |
2576 | $self->{fontsize} = $fontsize; |
2568 | $self->reflow; |
2577 | $self->reflow; |
2569 | } |
2578 | } |
2570 | |
2579 | |
|
|
2580 | sub visible_children { |
|
|
2581 | my ($self) = @_; |
|
|
2582 | |
|
|
2583 | @{$self->{children}}[0,1] |
|
|
2584 | } |
|
|
2585 | |
2571 | sub invoke_size_allocate { |
2586 | sub invoke_size_allocate { |
2572 | my ($self, $w, $h) = @_; |
2587 | my ($self, $w, $h) = @_; |
2573 | |
2588 | |
|
|
2589 | my ($empty, $slider, @other) = @{ $self->{children} }; |
|
|
2590 | $_->configure (@$_{qw(x y req_w req_h)}) for @other; |
|
|
2591 | |
2574 | $self->{layout}->set_font ($self->{font}) if $self->{font}; |
2592 | $self->{layout}->set_font ($self->{font}) if $self->{font}; |
2575 | $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); |
2593 | $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); |
2576 | $self->{layout}->set_width ($self->{children}[0]{w}); |
2594 | $self->{layout}->set_width ($empty->{w}); |
2577 | $self->{layout}->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent}); |
2595 | $self->{layout}->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent}); |
2578 | |
2596 | |
2579 | $self->reflow; |
2597 | $self->reflow; |
2580 | |
2598 | |
2581 | $self->SUPER::invoke_size_allocate ($w, $h) |
2599 | $self->SUPER::invoke_size_allocate ($w, $h) |
2582 | } |
2600 | } |
2583 | |
2601 | |
2584 | sub text_size { |
2602 | sub get_layout { |
2585 | my ($self, $text, $indent) = @_; |
2603 | my ($self, $para) = @_; |
2586 | |
2604 | |
2587 | my $layout = $self->{layout}; |
2605 | my $layout = $self->{layout}; |
2588 | |
2606 | |
|
|
2607 | $layout->set_font ($self->{font}) if $self->{font}; |
|
|
2608 | $layout->set_foreground (@{$para->{fg}}); |
2589 | $layout->set_height ($self->{fontsize} * $::FONTSIZE); |
2609 | $layout->set_height ($self->{fontsize} * $::FONTSIZE); |
2590 | $layout->set_width ($self->{children}[0]{w} - $indent); |
2610 | $layout->set_width ($self->{children}[0]{w} - $para->{indent}); |
2591 | $layout->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent}); |
2611 | $layout->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent}); |
2592 | $layout->set_markup ($text); |
2612 | $layout->set_markup ($para->{markup}); |
|
|
2613 | |
|
|
2614 | $layout->set_shapes ( |
|
|
2615 | map |
|
|
2616 | +(0, $_->baseline_shift +$_->{padding_y} - $_->{h}, $_->{w}, $_->{h}), |
|
|
2617 | @{$para->{widget}} |
2593 | |
2618 | ); |
|
|
2619 | |
2594 | $layout->size |
2620 | $layout |
2595 | } |
2621 | } |
2596 | |
2622 | |
2597 | sub reflow { |
2623 | sub reflow { |
2598 | my ($self) = @_; |
2624 | my ($self) = @_; |
2599 | |
2625 | |
… | |
… | |
2608 | $self->{children}[1]->set_value ($offset); |
2634 | $self->{children}[1]->set_value ($offset); |
2609 | } |
2635 | } |
2610 | |
2636 | |
2611 | sub clear { |
2637 | sub clear { |
2612 | my ($self) = @_; |
2638 | my ($self) = @_; |
|
|
2639 | |
|
|
2640 | my (undef, undef, @other) = @{ $self->{children} }; |
|
|
2641 | $self->remove ($_) for @other; |
2613 | |
2642 | |
2614 | $self->{par} = []; |
2643 | $self->{par} = []; |
2615 | $self->{height} = 0; |
2644 | $self->{height} = 0; |
2616 | $self->{children}[1]->set_range ([0, 0, 0, 1, 1]); |
2645 | $self->{children}[1]->set_range ([0, 0, 0, 1, 1]); |
2617 | } |
2646 | } |
2618 | |
2647 | |
2619 | sub add_paragraph { |
2648 | sub add_paragraph { |
2620 | my ($self, $color, $text, $indent) = @_; |
2649 | my ($self, $color, $para, $indent) = @_; |
2621 | |
2650 | |
2622 | for my $line (split /\n/, $text) { |
2651 | my ($text, @w) = ref $para ? @$para : $para; |
2623 | my ($w, $h) = $self->text_size ($line); |
2652 | |
2624 | $self->{height} += $h; |
2653 | $para = { |
2625 | push @{$self->{par}}, [$w + $indent, $h, $color, $indent, $line]; |
2654 | w => 1e10, |
|
|
2655 | wrapped => 1, |
|
|
2656 | fg => $color, |
|
|
2657 | indent => $indent, |
|
|
2658 | markup => $text, |
|
|
2659 | widget => \@w, |
2626 | } |
2660 | }; |
2627 | |
2661 | |
2628 | $self->{children}[1]->set_range ([$self->{height}, 0, $self->{height}, $self->{h}, 1]); |
2662 | $self->add (@w) if @w; |
|
|
2663 | push @{$self->{par}}, $para; |
|
|
2664 | |
|
|
2665 | $self->{need_reflow}++; |
|
|
2666 | $self->update; |
|
|
2667 | } |
|
|
2668 | |
|
|
2669 | sub scroll_to_bottom { |
|
|
2670 | my ($self) = @_; |
|
|
2671 | |
|
|
2672 | $self->{scroll_to_bottom} = 1; |
|
|
2673 | $self->update; |
2629 | } |
2674 | } |
2630 | |
2675 | |
2631 | sub update { |
2676 | sub update { |
2632 | my ($self) = @_; |
2677 | my ($self) = @_; |
2633 | |
2678 | |
… | |
… | |
2641 | my ($W, $H) = @{$self->{children}[0]}{qw(w h)}; |
2686 | my ($W, $H) = @{$self->{children}[0]}{qw(w h)}; |
2642 | |
2687 | |
2643 | if (delete $self->{need_reflow}) { |
2688 | if (delete $self->{need_reflow}) { |
2644 | my $height = 0; |
2689 | my $height = 0; |
2645 | |
2690 | |
2646 | my $layout = $self->{layout}; |
|
|
2647 | |
|
|
2648 | $layout->set_height ($self->{fontsize} * $::FONTSIZE); |
|
|
2649 | |
|
|
2650 | for (@{$self->{par}}) { |
2691 | for my $para (@{$self->{par}}) { |
2651 | if (1 || $_->[0] >= $W) { # TODO: works,but needs reconfigure etc. support |
2692 | if ($para->{w} != $W && ($para->{wrapped} || $para->{w} > $W)) { |
2652 | $layout->set_width ($W - $_->[3]); |
2693 | my $layout = $self->get_layout ($para); |
2653 | $layout->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent}); |
|
|
2654 | $layout->set_markup ($_->[4]); |
|
|
2655 | my ($w, $h) = $layout->size; |
2694 | my ($w, $h) = $layout->size; |
2656 | $_->[0] = $w + $_->[3]; |
2695 | |
2657 | $_->[1] = $h; |
2696 | $para->{w} = $w + $para->{indent}; |
|
|
2697 | $para->{h} = $h; |
|
|
2698 | $para->{wrapped} = $layout->has_wrapped; |
2658 | } |
2699 | } |
2659 | |
2700 | |
2660 | $height += $_->[1]; |
2701 | $height += $para->{h}; |
2661 | } |
2702 | } |
2662 | |
2703 | |
2663 | $self->{height} = $height; |
2704 | $self->{height} = $height; |
2664 | |
2705 | |
2665 | $self->{children}[1]->set_range ([$height, 0, $height, $H, 1]); |
2706 | $self->{children}[1]->set_range ([$self->{children}[1]{range}[0], 0, $height, $H, 1]); |
2666 | |
2707 | |
2667 | delete $self->{texture}; |
2708 | delete $self->{texture}; |
|
|
2709 | } |
|
|
2710 | |
|
|
2711 | if (delete $self->{scroll_to_bottom}) { |
|
|
2712 | $self->{children}[1]->set_value (1e10); |
2668 | } |
2713 | } |
2669 | |
2714 | |
2670 | $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub { |
2715 | $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub { |
2671 | glClearColor 0, 0, 0, 0; |
2716 | glClearColor 0, 0, 0, 0; |
2672 | glClear GL_COLOR_BUFFER_BIT; |
2717 | glClear GL_COLOR_BUFFER_BIT; |
… | |
… | |
2676 | my $y0 = $top; |
2721 | my $y0 = $top; |
2677 | my $y1 = $top + $H; |
2722 | my $y1 = $top + $H; |
2678 | |
2723 | |
2679 | my $y = 0; |
2724 | my $y = 0; |
2680 | |
2725 | |
2681 | my $layout = $self->{layout}; |
|
|
2682 | |
|
|
2683 | $layout->set_font ($self->{font}) if $self->{font}; |
|
|
2684 | |
|
|
2685 | glEnable GL_BLEND; |
2726 | glEnable GL_BLEND; |
2686 | #TODO# not correct in windows where rgba is forced off |
2727 | #TODO# not correct in windows where rgba is forced off |
2687 | glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA; |
2728 | glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA; |
2688 | |
2729 | |
2689 | for my $par (@{$self->{par}}) { |
2730 | for my $para (@{$self->{par}}) { |
2690 | my $h = $par->[1]; |
2731 | my $h = $para->{h}; |
2691 | |
2732 | |
2692 | if ($y0 < $y + $h && $y < $y1) { |
2733 | if ($y0 < $y + $h && $y < $y1) { |
2693 | $layout->set_foreground (@{ $par->[2] }); |
2734 | |
2694 | $layout->set_width ($W - $par->[3]); |
2735 | my $layout = $self->get_layout ($para); |
2695 | $layout->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent}); |
|
|
2696 | $layout->set_markup ($par->[4]); |
|
|
2697 | |
2736 | |
2698 | my ($w, $h, $data, $format, $internalformat) = $layout->render; |
2737 | my ($w, $h, $data, $format, $internalformat) = $layout->render; |
2699 | |
2738 | |
2700 | glRasterPos $par->[3], $y - $y0; |
2739 | glRasterPos $para->{indent}, $y - $y0; |
2701 | glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data; |
2740 | glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data; |
|
|
2741 | |
|
|
2742 | if (my @w = @{ $para->{widget} }) { |
|
|
2743 | my @s = $layout->get_shapes; |
|
|
2744 | |
|
|
2745 | for (@w) { |
|
|
2746 | my ($dx, $dy) = splice @s, 0, 2, (); |
|
|
2747 | |
|
|
2748 | $_->{x} = $dx + $para->{indent}; |
|
|
2749 | $_->{y} = $dy + $y - $y0; |
|
|
2750 | |
|
|
2751 | $_->draw; |
|
|
2752 | } |
|
|
2753 | } |
2702 | } |
2754 | } |
2703 | |
2755 | |
2704 | $y += $h; |
2756 | $y += $h; |
2705 | } |
2757 | } |
2706 | |
2758 | |
2707 | glDisable GL_BLEND; |
2759 | glDisable GL_BLEND; |
2708 | }; |
2760 | }; |
2709 | }); |
2761 | }); |
|
|
2762 | } |
|
|
2763 | |
|
|
2764 | sub reconfigure { |
|
|
2765 | my ($self) = @_; |
|
|
2766 | |
|
|
2767 | $self->SUPER::reconfigure; |
|
|
2768 | |
|
|
2769 | $_->{w} = 1e10 for @{ $self->{par} }; |
|
|
2770 | $self->reflow; |
2710 | } |
2771 | } |
2711 | |
2772 | |
2712 | sub _draw { |
2773 | sub _draw { |
2713 | my ($self) = @_; |
2774 | my ($self) = @_; |
2714 | |
2775 | |
… | |
… | |
2717 | glColor 0, 0, 0, 1; |
2778 | glColor 0, 0, 0, 1; |
2718 | $self->{texture}->draw_quad_alpha_premultiplied (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h}); |
2779 | $self->{texture}->draw_quad_alpha_premultiplied (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h}); |
2719 | glDisable GL_TEXTURE_2D; |
2780 | glDisable GL_TEXTURE_2D; |
2720 | |
2781 | |
2721 | $self->{children}[1]->draw; |
2782 | $self->{children}[1]->draw; |
2722 | |
|
|
2723 | } |
2783 | } |
2724 | |
2784 | |
2725 | ############################################################################# |
2785 | ############################################################################# |
2726 | |
2786 | |
2727 | package CFClient::UI::Animator; |
2787 | package CFClient::UI::Animator; |