ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.278
Committed: Sun Jun 4 23:05:06 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.277: +24 -21 lines
Log Message:
better gl extension management, play around with premultiplied alpha, without reaching an absolutely correct solution

File Contents

# User Rev Content
1 root 1.73 package CFClient::UI;
2 root 1.8
3 root 1.194 use utf8;
4 elmex 1.1 use strict;
5 root 1.18
6 root 1.74 use Scalar::Util ();
7     use List::Util ();
8 root 1.18
9 root 1.60 use CFClient;
10 root 1.240 use CFClient::Texture;
11 root 1.41
12 root 1.51 our ($FOCUS, $HOVER, $GRAB); # various widgets
13    
14 elmex 1.242 our $LAYOUT;
15 root 1.113 our $ROOT;
16 root 1.151 our $TOOLTIP;
17 root 1.51 our $BUTTON_STATE;
18    
19 root 1.202 our %WIDGET; # all widgets, weak-referenced
20    
21 elmex 1.242 sub get_layout {
22 root 1.256 my $layout;
23    
24 elmex 1.242 for (grep { $_->{name} } values %WIDGET) {
25 root 1.256 my $win = $layout->{$_->{name}} = { };
26    
27     $win->{x} = ($_->{x} + $_->{w} * 0.5) / $::WIDTH if $_->{x} =~ /^[0-9.]+$/;
28     $win->{y} = ($_->{y} + $_->{h} * 0.5) / $::HEIGHT if $_->{y} =~ /^[0-9.]+$/;
29     $win->{w} = $_->{w} / $::WIDTH if defined $_->{w};
30     $win->{h} = $_->{h} / $::HEIGHT if defined $_->{h};
31 root 1.258
32     $win->{show} = $_->{visible} && $_->{is_toplevel};
33 elmex 1.242 }
34    
35 root 1.256 $layout
36 elmex 1.242 }
37    
38     sub set_layout {
39     my ($layout) = @_;
40 root 1.258
41 elmex 1.242 $LAYOUT = $layout;
42     }
43    
44 root 1.151 sub check_tooltip {
45 root 1.266 return if $ENV{CFPLUS_DEBUG} & 8;
46    
47 root 1.151 if (!$GRAB) {
48     for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) {
49 root 1.160 if (length $widget->{tooltip}) {
50 root 1.151 if ($TOOLTIP->{owner} != $widget) {
51 root 1.253 $TOOLTIP->hide;
52    
53 root 1.151 $TOOLTIP->{owner} = $widget;
54 root 1.155
55     my $tip = $widget->{tooltip};
56    
57     $tip = $tip->($widget) if CODE:: eq ref $tip;
58    
59 root 1.196 $TOOLTIP->set_tooltip_from ($widget);
60 root 1.252 $TOOLTIP->show;
61 root 1.151 }
62    
63     return;
64     }
65     }
66     }
67    
68     $TOOLTIP->hide;
69     delete $TOOLTIP->{owner};
70     }
71    
72 elmex 1.1 # class methods for events
73 root 1.51 sub feed_sdl_key_down_event {
74 root 1.231 $FOCUS->emit (key_down => $_[0])
75 root 1.163 if $FOCUS;
76 root 1.51 }
77    
78     sub feed_sdl_key_up_event {
79 root 1.231 $FOCUS->emit (key_up => $_[0])
80 root 1.163 if $FOCUS;
81 root 1.51 }
82    
83     sub feed_sdl_button_down_event {
84     my ($ev) = @_;
85 root 1.137 my ($x, $y) = ($ev->{x}, $ev->{y});
86 root 1.51
87     if (!$BUTTON_STATE) {
88 root 1.113 my $widget = $ROOT->find_widget ($x, $y);
89 root 1.51
90     $GRAB = $widget;
91     $GRAB->update if $GRAB;
92 root 1.151
93     check_tooltip;
94 root 1.51 }
95    
96 root 1.137 $BUTTON_STATE |= 1 << ($ev->{button} - 1);
97 root 1.51
98 root 1.231 $GRAB->emit (button_down => $ev, $GRAB->coord2local ($x, $y))
99     if $GRAB;
100 root 1.51 }
101    
102     sub feed_sdl_button_up_event {
103     my ($ev) = @_;
104 root 1.137 my ($x, $y) = ($ev->{x}, $ev->{y});
105 root 1.51
106 root 1.113 my $widget = $GRAB || $ROOT->find_widget ($x, $y);
107 root 1.51
108 root 1.137 $BUTTON_STATE &= ~(1 << ($ev->{button} - 1));
109 root 1.51
110 root 1.231 $GRAB->emit (button_up => $ev, $GRAB->coord2local ($x, $y))
111     if $GRAB;
112 root 1.58
113 root 1.51 if (!$BUTTON_STATE) {
114     my $grab = $GRAB; undef $GRAB;
115     $grab->update if $grab;
116     $GRAB->update if $GRAB;
117 root 1.151
118     check_tooltip;
119 root 1.51 }
120     }
121    
122     sub feed_sdl_motion_event {
123     my ($ev) = @_;
124 root 1.137 my ($x, $y) = ($ev->{x}, $ev->{y});
125 root 1.51
126 root 1.113 my $widget = $GRAB || $ROOT->find_widget ($x, $y);
127 root 1.51
128     if ($widget != $HOVER) {
129     my $hover = $HOVER; $HOVER = $widget;
130    
131 root 1.105 $hover->update if $hover && $hover->{can_hover};
132     $HOVER->update if $HOVER && $HOVER->{can_hover};
133 root 1.151
134     check_tooltip;
135 root 1.51 }
136    
137 root 1.231 $HOVER->emit (mouse_motion => $ev, $HOVER->coord2local ($x, $y))
138     if $HOVER;
139 root 1.51 }
140 elmex 1.1
141 root 1.112 # convert position array to integers
142     sub harmonize {
143     my ($vals) = @_;
144    
145     my $rem = 0;
146    
147 root 1.116 for (@$vals) {
148 root 1.112 my $i = int $_ + $rem;
149     $rem += $_ - $i;
150     $_ = $i;
151     }
152     }
153    
154 root 1.220 sub full_refresh {
155     # make a copy, otherwise for complains about freed values.
156     my @widgets = values %WIDGET;
157    
158     $_->update
159     for @widgets;
160     }
161    
162 root 1.230 sub reconfigure_widgets {
163     # make a copy, otherwise C<for> complains about freed values.
164     my @widgets = values %WIDGET;
165    
166     $_->reconfigure
167     for @widgets;
168     }
169    
170 root 1.202 # call when resolution changes etc.
171     sub rescale_widgets {
172     my ($sx, $sy) = @_;
173    
174 root 1.230 for my $widget (values %WIDGET) {
175     if ($widget->{is_toplevel}) {
176 root 1.268 $widget->{x} += int $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/;
177     $widget->{y} += int $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/;
178 root 1.256
179     $widget->{x} = int 0.5 + $widget->{x} * $sx if $widget->{x} =~ /^[0-9.]+$/;
180     $widget->{w} = int 0.5 + $widget->{w} * $sx if exists $widget->{w};
181     $widget->{force_w} = int 0.5 + $widget->{force_w} * $sx if exists $widget->{force_w};
182     $widget->{y} = int 0.5 + $widget->{y} * $sy if $widget->{y} =~ /^[0-9.]+$/;
183     $widget->{h} = int 0.5 + $widget->{h} * $sy if exists $widget->{h};
184     $widget->{force_h} = int 0.5 + $widget->{force_h} * $sy if exists $widget->{force_h};
185    
186 root 1.268 $widget->{x} -= int $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/;
187     $widget->{y} -= int $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/;
188 root 1.256
189 root 1.202 }
190 root 1.230 }
191 root 1.202
192 root 1.230 reconfigure_widgets;
193 root 1.202 }
194    
195 root 1.73 #############################################################################
196    
197     package CFClient::UI::Base;
198    
199     use strict;
200    
201 root 1.138 use CFClient::OpenGL;
202 root 1.73
203 elmex 1.1 sub new {
204     my $class = shift;
205 root 1.10
206 root 1.79 my $self = bless {
207 root 1.256 x => "center",
208     y => "center",
209 root 1.164 z => 0,
210 root 1.256 w => undef,
211     h => undef,
212 elmex 1.150 can_events => 1,
213 root 1.65 @_
214 root 1.79 }, $class;
215    
216 root 1.258 Scalar::Util::weaken ($CFClient::UI::WIDGET{$self+0} = $self);
217    
218     for (keys %$self) {
219     if (/^on_(.*)$/) {
220     $self->connect ($1 => delete $self->{$_});
221     }
222     }
223    
224 root 1.256 if (my $layout = $CFClient::UI::LAYOUT->{$self->{name}}) {
225 root 1.259 $self->{x} = $layout->{x} * $CFClient::UI::ROOT->{alloc_w} if exists $layout->{x};
226     $self->{y} = $layout->{y} * $CFClient::UI::ROOT->{alloc_h} if exists $layout->{y};
227     $self->{force_w} = $layout->{w} * $CFClient::UI::ROOT->{alloc_w} if exists $layout->{w};
228     $self->{force_h} = $layout->{h} * $CFClient::UI::ROOT->{alloc_h} if exists $layout->{h};
229 root 1.256
230     $self->{x} -= $self->{force_w} * 0.5 if exists $layout->{x};
231     $self->{y} -= $self->{force_h} * 0.5 if exists $layout->{y};
232    
233 root 1.258 $self->show if $layout->{show};
234 root 1.79 }
235    
236     $self
237 elmex 1.1 }
238    
239 root 1.159 sub destroy {
240     my ($self) = @_;
241    
242     $self->hide;
243     %$self = ();
244     }
245    
246 root 1.134 sub show {
247     my ($self) = @_;
248 root 1.248
249 root 1.134 return if $self->{parent};
250    
251     $CFClient::UI::ROOT->add ($self);
252     }
253    
254 root 1.246 sub set_visible {
255     my ($self) = @_;
256    
257     return if $self->{visible};
258    
259     $self->{root} = $self->{parent}{root};
260     $self->{visible} = $self->{parent}{visible} + 1;
261    
262 root 1.248 $self->emit (visibility_change => 1);
263 root 1.251
264 root 1.252 $self->realloc if !exists $self->{req_w};
265 root 1.251
266     $_->set_visible for $self->children;
267 root 1.246 }
268    
269 root 1.231 sub set_invisible {
270 root 1.134 my ($self) = @_;
271    
272 root 1.244 return unless $self->{visible};
273    
274 root 1.251 $_->set_invisible for $self->children;
275 root 1.231
276 root 1.241 delete $self->{root};
277 root 1.231 delete $self->{visible};
278    
279 root 1.163 undef $GRAB if $GRAB == $self;
280     undef $HOVER if $HOVER == $self;
281 root 1.134
282 root 1.232 CFClient::UI::check_tooltip
283 root 1.251 if $TOOLTIP->{owner} == $self;
284 root 1.232
285 root 1.231 $self->focus_out;
286 root 1.244
287     $self->emit (visibility_change => 0);
288 root 1.231 }
289    
290 root 1.250 sub set_visibility {
291     my ($self, $visible) = @_;
292    
293     return if $self->{visible} == $visible;
294    
295     $visible ? $self->hide
296     : $self->show;
297     }
298    
299     sub toggle_visibility {
300     my ($self) = @_;
301    
302     $self->{visible}
303     ? $self->hide
304     : $self->show;
305     }
306    
307 root 1.231 sub hide {
308     my ($self) = @_;
309    
310     $self->set_invisible;
311    
312 root 1.163 $self->{parent}->remove ($self)
313     if $self->{parent};
314 root 1.134 }
315    
316 root 1.256 sub move_abs {
317 root 1.18 my ($self, $x, $y, $z) = @_;
318 root 1.128
319 root 1.256 $self->{x} = List::Util::max 0, int $x;
320     $self->{y} = List::Util::max 0, int $y;
321 root 1.18 $self->{z} = $z if defined $z;
322 root 1.128
323     $self->update;
324 root 1.18 }
325    
326 root 1.158 sub set_size {
327     my ($self, $w, $h) = @_;
328    
329 root 1.256 $self->{force_w} = $w;
330     $self->{force_h} = $h;
331 root 1.158
332 root 1.251 $self->realloc;
333 elmex 1.20 }
334    
335 root 1.14 sub size_request {
336 elmex 1.36 require Carp;
337 root 1.147 Carp::confess "size_request is abstract";
338 elmex 1.36 }
339    
340 root 1.128 sub configure {
341 root 1.75 my ($self, $x, $y, $w, $h) = @_;
342    
343 root 1.141 if ($self->{aspect}) {
344 root 1.256 my ($ow, $oh) = ($w, $h);
345    
346     $w = List::Util::min $w, int $h * $self->{aspect};
347     $h = List::Util::min $h, int $w / $self->{aspect};
348 root 1.141
349     # use alignment to adjust x, y
350 root 1.75
351 root 1.256 $x += int 0.5 * ($ow - $w);
352     $y += int 0.5 * ($oh - $h);
353 root 1.140 }
354    
355 root 1.256 if ($self->{x} ne $x || $self->{y} ne $y) {
356 root 1.141 $self->{x} = $x;
357     $self->{y} = $y;
358     $self->update;
359     }
360 root 1.40
361 root 1.259 if ($self->{alloc_w} != $w || $self->{alloc_h} != $h) {
362 root 1.251 return unless $self->{visible};
363    
364 root 1.259 $self->{alloc_w} = $w;
365     $self->{alloc_h} = $h;
366    
367     $self->{root}{size_alloc}{$self+0} = $self;
368 root 1.141 }
369 root 1.75 }
370    
371     sub size_allocate {
372 root 1.128 # nothing to be done
373 root 1.14 }
374    
375 root 1.202 sub children {
376 root 1.272 # nop
377     }
378    
379     sub visible_children {
380     $_[0]->children
381 root 1.202 }
382    
383 root 1.157 sub set_max_size {
384     my ($self, $w, $h) = @_;
385    
386     delete $self->{max_w}; $self->{max_w} = $w if $w;
387     delete $self->{max_h}; $self->{max_h} = $h if $h;
388     }
389    
390 root 1.209 sub set_tooltip {
391     my ($self, $tooltip) = @_;
392    
393 root 1.234 $tooltip =~ s/^\s+//;
394     $tooltip =~ s/\s+$//;
395    
396     return if $self->{tooltip} eq $tooltip;
397    
398 root 1.209 $self->{tooltip} = $tooltip;
399    
400     if ($CFClient::UI::TOOLTIP->{owner} == $self) {
401     delete $CFClient::UI::TOOLTIP->{owner};
402     CFClient::UI::check_tooltip;
403     }
404     }
405    
406 root 1.82 # translate global coordinates to local coordinate system
407 root 1.113 sub coord2local {
408 root 1.58 my ($self, $x, $y) = @_;
409    
410 root 1.191 $self->{parent}->coord2local ($x - $self->{x}, $y - $self->{y})
411 root 1.113 }
412    
413     # translate local coordinates to global coordinate system
414     sub coord2global {
415     my ($self, $x, $y) = @_;
416    
417 root 1.191 $self->{parent}->coord2global ($x + $self->{x}, $y + $self->{y})
418 root 1.58 }
419    
420 elmex 1.1 sub focus_in {
421 root 1.51 my ($self) = @_;
422    
423 root 1.68 return if $FOCUS == $self;
424 root 1.97 return unless $self->{can_focus};
425 root 1.68
426 root 1.51 my $focus = $FOCUS; $FOCUS = $self;
427 elmex 1.120
428 root 1.231 $self->_emit (focus_in => $focus);
429 elmex 1.120
430 root 1.51 $focus->update if $focus;
431     $FOCUS->update;
432 elmex 1.1 }
433 root 1.4
434 elmex 1.1 sub focus_out {
435 root 1.51 my ($self) = @_;
436 root 1.4
437 root 1.51 return unless $FOCUS == $self;
438 root 1.4
439 root 1.51 my $focus = $FOCUS; undef $FOCUS;
440 elmex 1.120
441 root 1.231 $self->_emit (focus_out => $focus);
442 elmex 1.120
443 root 1.51 $focus->update if $focus; #?
444 root 1.231
445     $::MAPWIDGET->focus_in #d# focus mapwidget if no other widget has focus
446     unless $FOCUS;
447 elmex 1.1 }
448 root 1.4
449 root 1.271 sub mouse_motion { 0 }
450     sub button_up { 0 }
451     sub key_down { 0 }
452     sub key_up { 0 }
453 root 1.51
454 root 1.68 sub button_down {
455     my ($self, $ev, $x, $y) = @_;
456    
457     $self->focus_in;
458 root 1.271
459     0
460 root 1.68 }
461    
462 root 1.251 sub find_widget {
463     my ($self, $x, $y) = @_;
464    
465     return () unless $self->{can_events};
466    
467     return $self
468     if $x >= $self->{x} && $x < $self->{x} + $self->{w}
469     && $y >= $self->{y} && $y < $self->{y} + $self->{h};
470    
471     ()
472     }
473    
474     sub set_parent {
475     my ($self, $parent) = @_;
476    
477     Scalar::Util::weaken ($self->{parent} = $parent);
478     $self->set_visible if $parent->{visible};
479     }
480    
481     sub connect {
482     my ($self, $signal, $cb) = @_;
483    
484     push @{ $self->{signal_cb}{$signal} }, $cb;
485     }
486    
487     sub _emit {
488     my ($self, $signal, @args) = @_;
489    
490     List::Util::sum map $_->($self, @args), @{$self->{signal_cb}{$signal} || []}
491     }
492    
493     sub emit {
494     my ($self, $signal, @args) = @_;
495    
496     $self->_emit ($signal, @args)
497     || $self->$signal (@args);
498     }
499    
500     sub visibility_change {
501     #my ($self, $visible) = @_;
502     }
503    
504     sub realloc {
505     my ($self) = @_;
506    
507 root 1.252 if ($self->{visible}) {
508 root 1.259 return if $self->{root}{realloc}{$self+0};
509 root 1.251
510 root 1.259 $self->{root}{realloc}{$self+0} = $self;
511 root 1.252 $self->{root}->update;
512     } else {
513     delete $self->{req_w};
514 root 1.259 delete $self->{req_h};
515 root 1.252 }
516 root 1.251 }
517    
518     sub update {
519     my ($self) = @_;
520    
521     $self->{parent}->update
522     if $self->{parent};
523     }
524    
525 root 1.256 sub reconfigure {
526     my ($self) = @_;
527    
528     $self->realloc;
529     $self->update;
530     }
531    
532 root 1.267 # using global variables seems a bit hacky, but passing through all drawing
533     # functions seems pointless.
534     our ($draw_x, $draw_y, $draw_w, $draw_h); # screen rectangle being drawn
535    
536 elmex 1.1 sub draw {
537 elmex 1.11 my ($self) = @_;
538    
539 root 1.68 return unless $self->{h} && $self->{w};
540    
541 root 1.268 # update screen rectangle
542 root 1.267 local $draw_x = $draw_x + $self->{x};
543     local $draw_y = $draw_y + $self->{y};
544 root 1.268 local $draw_w = $draw_x + $self->{w};
545     local $draw_h = $draw_y + $self->{h};
546 root 1.267
547 root 1.268 # skip widgets that are entirely outside the drawing area
548     return if ($draw_x + $self->{w} < 0) || ($draw_x >= $draw_w)
549     || ($draw_y + $self->{h} < 0) || ($draw_y >= $draw_h);
550 root 1.267
551 elmex 1.11 glPushMatrix;
552 root 1.12 glTranslate $self->{x}, $self->{y}, 0;
553 root 1.72
554 root 1.97 if ($self == $HOVER && $self->{can_hover}) {
555 root 1.278 glColor 1*0.2, 0.8*0.2, 0.5*0.2, 0.2;
556 root 1.50 glEnable GL_BLEND;
557 root 1.278 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
558 root 1.48 glBegin GL_QUADS;
559 root 1.278 glVertex 0 , 0;
560     glVertex $self->{w}, 0;
561     glVertex $self->{w}, $self->{h};
562     glVertex 0 , $self->{h};
563 root 1.47 glEnd;
564 root 1.50 glDisable GL_BLEND;
565 root 1.47 }
566 root 1.162
567 root 1.259 if ($ENV{CFPLUS_DEBUG} & 1) {
568 root 1.162 glPushMatrix;
569     glColor 1, 1, 0, 1;
570 root 1.278 glTranslate 0.375, 0.375;
571 root 1.162 glBegin GL_LINE_LOOP;
572 root 1.234 glVertex 0 , 0;
573     glVertex $self->{w} - 1, 0;
574     glVertex $self->{w} - 1, $self->{h} - 1;
575     glVertex 0 , $self->{h} - 1;
576 root 1.162 glEnd;
577     glPopMatrix;
578 root 1.205 #CFClient::UI::Label->new (w => $self->{w}, h => $self->{h}, text => $self, fontsize => 0)->_draw;
579 root 1.162 }
580 root 1.278
581     $self->_draw;
582     glPopMatrix;
583 elmex 1.11 }
584    
585     sub _draw {
586 root 1.38 my ($self) = @_;
587    
588     warn "no draw defined for $self\n";
589 elmex 1.1 }
590 root 1.4
591 root 1.18 sub DESTROY {
592     my ($self) = @_;
593    
594 root 1.202 delete $WIDGET{$self+0};
595 elmex 1.32 #$self->deactivate;
596 root 1.18 }
597    
598 root 1.39 #############################################################################
599    
600 root 1.73 package CFClient::UI::DrawBG;
601 root 1.68
602 root 1.73 our @ISA = CFClient::UI::Base::;
603 root 1.68
604     use strict;
605 root 1.138 use CFClient::OpenGL;
606 root 1.68
607     sub new {
608     my $class = shift;
609    
610     # range [value, low, high, page]
611    
612     $class->SUPER::new (
613 root 1.209 #bg => [0, 0, 0, 0.2],
614     #active_bg => [1, 1, 1, 0.5],
615 root 1.68 @_
616     )
617     }
618    
619     sub _draw {
620     my ($self) = @_;
621    
622 root 1.209 my $color = $FOCUS == $self && $self->{active_bg}
623     ? $self->{active_bg}
624     : $self->{bg};
625    
626     if ($color && (@$color < 4 || $color->[3])) {
627     my ($w, $h) = @$self{qw(w h)};
628 root 1.68
629 root 1.209 glEnable GL_BLEND;
630 root 1.278 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
631     glColor_premultiply @$color;
632 root 1.76
633 root 1.209 glBegin GL_QUADS;
634     glVertex 0 , 0;
635     glVertex 0 , $h;
636     glVertex $w, $h;
637     glVertex $w, 0;
638     glEnd;
639 root 1.76
640 root 1.209 glDisable GL_BLEND;
641     }
642 root 1.68 }
643    
644     #############################################################################
645    
646 root 1.73 package CFClient::UI::Empty;
647 root 1.66
648 root 1.73 our @ISA = CFClient::UI::Base::;
649 root 1.66
650 elmex 1.150 sub new {
651     my ($class, %arg) = @_;
652     $class->SUPER::new (can_events => 0, %arg);
653     }
654    
655 root 1.66 sub size_request {
656 root 1.256 my ($self) = @_;
657    
658     ($self->{w} + 0, $self->{h} + 0)
659 root 1.66 }
660    
661 root 1.67 sub draw { }
662 root 1.66
663     #############################################################################
664    
665 root 1.73 package CFClient::UI::Container;
666 elmex 1.15
667 root 1.73 our @ISA = CFClient::UI::Base::;
668 elmex 1.15
669 root 1.38 sub new {
670 root 1.64 my ($class, %arg) = @_;
671    
672 root 1.272 my $children = delete $arg{children};
673 root 1.38
674 root 1.166 my $self = $class->SUPER::new (
675     children => [],
676     can_events => 0,
677     %arg,
678     );
679 root 1.272
680     $self->add (@$children)
681     if $children;
682 root 1.38
683     $self
684     }
685    
686     sub add {
687 root 1.188 my ($self, @widgets) = @_;
688 root 1.113
689 root 1.188 $_->set_parent ($self)
690     for @widgets;
691 root 1.38
692 root 1.113 use sort 'stable';
693 root 1.38
694 root 1.66 $self->{children} = [
695 root 1.38 sort { $a->{z} <=> $b->{z} }
696 root 1.188 @{$self->{children}}, @widgets
697 root 1.66 ];
698 root 1.38
699 root 1.251 $self->realloc;
700 root 1.38 }
701 root 1.35
702 root 1.172 sub children {
703     @{ $_[0]{children} }
704     }
705    
706 elmex 1.32 sub remove {
707 root 1.134 my ($self, $child) = @_;
708    
709     delete $child->{parent};
710 root 1.163 $child->hide;
711 root 1.38
712 root 1.134 $self->{children} = [ grep $_ != $child, @{ $self->{children} } ];
713 root 1.38
714 root 1.251 $self->realloc;
715 root 1.38 }
716    
717 root 1.162 sub clear {
718     my ($self) = @_;
719    
720 root 1.163 my $children = delete $self->{children};
721     $self->{children} = [];
722 root 1.162
723 root 1.163 for (@$children) {
724     delete $_->{parent};
725     $_->hide;
726     }
727 root 1.182
728 root 1.251 $self->realloc;
729 root 1.162 }
730    
731 root 1.38 sub find_widget {
732     my ($self, $x, $y) = @_;
733    
734 root 1.45 $x -= $self->{x};
735     $y -= $self->{y};
736    
737 root 1.38 my $res;
738    
739 root 1.272 for (reverse $self->visible_children) {
740 root 1.45 $res = $_->find_widget ($x, $y)
741 root 1.38 and return $res;
742     }
743    
744 root 1.46 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
745 elmex 1.32 }
746 elmex 1.15
747 root 1.35 sub _draw {
748     my ($self) = @_;
749    
750 root 1.38 $_->draw for @{$self->{children}};
751 root 1.35 }
752 elmex 1.15
753 root 1.39 #############################################################################
754    
755 root 1.73 package CFClient::UI::Bin;
756 elmex 1.32
757 root 1.73 our @ISA = CFClient::UI::Container::;
758 elmex 1.32
759 root 1.66 sub new {
760     my ($class, %arg) = @_;
761    
762 root 1.73 my $child = (delete $arg{child}) || new CFClient::UI::Empty::;
763 root 1.66
764     $class->SUPER::new (children => [$child], %arg)
765     }
766    
767     sub add {
768 root 1.134 my ($self, $child) = @_;
769 root 1.66
770     $self->{children} = [];
771    
772 root 1.134 $self->SUPER::add ($child);
773 root 1.66 }
774    
775     sub remove {
776     my ($self, $widget) = @_;
777    
778     $self->SUPER::remove ($widget);
779    
780 root 1.73 $self->{children} = [new CFClient::UI::Empty]
781 root 1.66 unless @{$self->{children}};
782     }
783    
784 root 1.39 sub child { $_[0]->{children}[0] }
785 elmex 1.32
786 root 1.38 sub size_request {
787 root 1.68 $_[0]{children}[0]->size_request
788 root 1.38 }
789 elmex 1.32
790 root 1.38 sub size_allocate {
791 root 1.259 my ($self, $w, $h) = @_;
792 root 1.42
793 root 1.128 $self->{children}[0]->configure (0, 0, $w, $h);
794 root 1.38 }
795 elmex 1.32
796 root 1.39 #############################################################################
797    
798 root 1.274 # back-buffered drawing area
799    
800 root 1.73 package CFClient::UI::Window;
801 elmex 1.20
802 root 1.73 our @ISA = CFClient::UI::Bin::;
803 elmex 1.20
804 root 1.138 use CFClient::OpenGL;
805 elmex 1.20
806 root 1.42 sub new {
807 root 1.64 my ($class, %arg) = @_;
808 elmex 1.32
809 root 1.64 my $self = $class->SUPER::new (%arg);
810 elmex 1.32 }
811    
812     sub update {
813     my ($self) = @_;
814 root 1.42
815 root 1.198 $ROOT->on_post_alloc ($self => sub { $self->render_child });
816 root 1.42 $self->SUPER::update;
817 elmex 1.20 }
818    
819 root 1.174 sub size_allocate {
820 root 1.259 my ($self, $w, $h) = @_;
821 root 1.174
822 root 1.259 $self->SUPER::size_allocate ($w, $h);
823     $self->update;
824 root 1.174 }
825    
826 root 1.182 sub _render {
827 root 1.267 my ($self) = @_;
828    
829     $self->{children}[0]->draw;
830 root 1.182 }
831    
832 root 1.174 sub render_child {
833 elmex 1.20 my ($self) = @_;
834    
835 root 1.105 $self->{texture} = new_from_opengl CFClient::Texture $self->{w}, $self->{h}, sub {
836 root 1.216 glClearColor 0, 0, 0, 0;
837 root 1.105 glClear GL_COLOR_BUFFER_BIT;
838 root 1.182
839 root 1.267 {
840     package CFClient::UI::Base;
841    
842     ($draw_x, $draw_y, $draw_w, $draw_h) =
843     (0, 0, $self->{w}, $self->{h});
844     }
845    
846 root 1.182 $self->_render;
847 root 1.105 };
848 elmex 1.36 }
849    
850 elmex 1.20 sub _draw {
851     my ($self) = @_;
852    
853 root 1.267 my ($w, $h) = @$self{qw(w h)};
854 root 1.29
855 elmex 1.20 my $tex = $self->{texture}
856     or return;
857    
858     glEnable GL_TEXTURE_2D;
859 root 1.35 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
860 root 1.278 glEnable GL_BLEND;
861     glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
862     glColor 0, 0, 0, 1;
863 elmex 1.20
864 root 1.278 $tex->draw_quad (0, 0, $w, $h);
865 elmex 1.20
866 root 1.278 glDisable GL_BLEND;
867 elmex 1.20 glDisable GL_TEXTURE_2D;
868     }
869    
870 root 1.39 #############################################################################
871    
872 root 1.125 package CFClient::UI::ViewPort;
873    
874     our @ISA = CFClient::UI::Window::;
875    
876 root 1.234 sub new {
877     my $class = shift;
878    
879     $class->SUPER::new (
880     scroll_x => 0,
881     scroll_y => 1,
882     @_,
883     )
884     }
885    
886 root 1.125 sub size_request {
887     my ($self) = @_;
888    
889 root 1.259 my ($w, $h) = @{$self->child}{qw(req_w req_h)};
890 root 1.247
891     $w = 10 if $self->{scroll_x};
892     $h = 10 if $self->{scroll_y};
893 root 1.125
894 root 1.247 ($w, $h)
895 root 1.125 }
896    
897 root 1.182 sub size_allocate {
898 root 1.259 my ($self, $w, $h) = @_;
899    
900     my $child = $self->child;
901 root 1.182
902 root 1.259 $w = $child->{req_w} if $self->{scroll_x} && $child->{req_w};
903     $h = $child->{req_h} if $self->{scroll_y} && $child->{req_h};
904 root 1.234
905     $self->child->configure (0, 0, $w, $h);
906 root 1.182 $self->update;
907     }
908    
909     sub set_offset {
910     my ($self, $x, $y) = @_;
911    
912     $self->{view_x} = int $x;
913     $self->{view_y} = int $y;
914    
915     $self->update;
916     }
917    
918 root 1.191 # hmm, this does not work for topleft of $self... but we should not ask for that
919     sub coord2local {
920     my ($self, $x, $y) = @_;
921    
922     $self->SUPER::coord2local ($x + $self->{view_x}, $y + $self->{view_y})
923     }
924    
925     sub coord2global {
926 root 1.184 my ($self, $x, $y) = @_;
927    
928 root 1.191 $x = List::Util::min $self->{w}, $x - $self->{view_x};
929     $y = List::Util::min $self->{h}, $y - $self->{view_y};
930    
931     $self->SUPER::coord2global ($x, $y)
932 root 1.184 }
933    
934     sub find_widget {
935     my ($self, $x, $y) = @_;
936    
937     if ( $x >= $self->{x} && $x < $self->{x} + $self->{w}
938     && $y >= $self->{y} && $y < $self->{y} + $self->{h}
939     ) {
940     $self->child->find_widget ($x + $self->{view_x}, $y + $self->{view_y})
941     } else {
942     $self->CFClient::UI::Base::find_widget ($x, $y)
943     }
944     }
945    
946 root 1.182 sub _render {
947 root 1.125 my ($self) = @_;
948    
949 root 1.267 local $CFClient::UI::Base::draw_x = $CFClient::UI::Base::draw_x - $self->{view_x};
950     local $CFClient::UI::Base::draw_y = $CFClient::UI::Base::draw_y - $self->{view_y};
951    
952 root 1.182 CFClient::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y};
953    
954     $self->SUPER::_render;
955     }
956    
957     #############################################################################
958    
959     package CFClient::UI::ScrolledWindow;
960    
961     our @ISA = CFClient::UI::HBox::;
962    
963     sub new {
964 root 1.273 my ($class, %arg) = @_;
965    
966     my $child = delete $arg{child};
967 root 1.182
968     my $self;
969    
970     my $slider = new CFClient::UI::Slider
971 root 1.243 vertical => 1,
972     range => [0, 0, 1, 0.01], # HACK fix
973     on_changed => sub {
974 root 1.229 $self->{vp}->set_offset (0, $_[1]);
975 root 1.182 },
976     ;
977    
978     $self = $class->SUPER::new (
979 root 1.217 vp => (new CFClient::UI::ViewPort expand => 1),
980 root 1.182 slider => $slider,
981 root 1.273 %arg,
982 root 1.182 );
983    
984 root 1.273 $self->SUPER::add ($self->{vp}, $self->{slider});
985     $self->add ($child) if $child;
986 root 1.182
987     $self
988 root 1.125 }
989    
990 root 1.273 sub add {
991     my ($self, $widget) = @_;
992    
993     $self->{vp}->add ($self->{child} = $widget);
994     }
995    
996 root 1.239 sub update {
997     my ($self) = @_;
998    
999     $self->SUPER::update;
1000    
1001     # todo: overwrite size_allocate of child
1002     my $child = $self->{vp}->child;
1003     $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $child->{h}, $self->{vp}{h}, 1]);
1004     }
1005    
1006 root 1.229 sub size_allocate {
1007 root 1.259 my ($self, $w, $h) = @_;
1008 root 1.229
1009 root 1.259 $self->SUPER::size_allocate ($w, $h);
1010 root 1.229
1011     my $child = $self->{vp}->child;
1012     $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $child->{h}, $self->{vp}{h}, 1]);
1013     }
1014    
1015 root 1.199 #TODO# update range on size_allocate depending on child
1016 root 1.182 # update viewport offset on scroll
1017 root 1.125
1018     #############################################################################
1019    
1020 root 1.73 package CFClient::UI::Frame;
1021 elmex 1.15
1022 root 1.73 our @ISA = CFClient::UI::Bin::;
1023 elmex 1.15
1024 root 1.138 use CFClient::OpenGL;
1025 elmex 1.15
1026 root 1.199 sub new {
1027     my $class = shift;
1028    
1029     $class->SUPER::new (
1030     bg => undef,
1031     @_,
1032     )
1033     }
1034    
1035     sub _draw {
1036     my ($self) = @_;
1037    
1038     if ($self->{bg}) {
1039     my ($w, $h) = @$self{qw(w h)};
1040    
1041     glEnable GL_BLEND;
1042 root 1.278 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
1043     glColor_premultiply @{ $self->{bg} };
1044 root 1.199
1045     glBegin GL_QUADS;
1046     glVertex 0 , 0;
1047     glVertex 0 , $h;
1048     glVertex $w, $h;
1049     glVertex $w, 0;
1050     glEnd;
1051    
1052     glDisable GL_BLEND;
1053     }
1054    
1055     $self->SUPER::_draw;
1056     }
1057    
1058 root 1.39 #############################################################################
1059    
1060 root 1.73 package CFClient::UI::FancyFrame;
1061 elmex 1.31
1062 root 1.73 our @ISA = CFClient::UI::Bin::;
1063 elmex 1.31
1064 root 1.138 use CFClient::OpenGL;
1065 elmex 1.31
1066 root 1.255 my $bg =
1067     new_from_file CFClient::Texture CFClient::find_rcfile "d1_bg.png",
1068     mipmap => 1, wrap => 1;
1069    
1070     my @border =
1071 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1072 root 1.255 qw(d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png);
1073 elmex 1.34
1074 root 1.97 sub new {
1075 root 1.269 my ($class, %arg) = @_;
1076    
1077     my $title = delete $arg{title};
1078 root 1.97
1079 root 1.141 my $self = $class->SUPER::new (
1080 root 1.230 bg => [1, 1, 1, 1],
1081     border_bg => [1, 1, 1, 1],
1082     border => 0.6,
1083     can_events => 1,
1084 root 1.258 min_w => 16,
1085     min_h => 16,
1086 root 1.269 %arg,
1087 root 1.141 );
1088    
1089 root 1.269 $self->{title} = new CFClient::UI::Label
1090 root 1.141 align => 0,
1091     valign => 1,
1092 root 1.269 text => $title,
1093     fontsize => $self->{border}
1094     if defined $title;
1095 root 1.141
1096     $self
1097 root 1.97 }
1098    
1099 root 1.269 sub add {
1100     my ($self, @widgets) = @_;
1101    
1102     $self->SUPER::add (@widgets);
1103     $self->CFClient::UI::Container::add ($self->{title}) if $self->{title};
1104     }
1105    
1106 root 1.134 sub border {
1107     int $_[0]{border} * $::FONTSIZE
1108     }
1109    
1110 elmex 1.34 sub size_request {
1111     my ($self) = @_;
1112 root 1.39
1113 root 1.269 $self->{title}->size_request
1114     if $self->{title};
1115    
1116 root 1.78 my ($w, $h) = $self->SUPER::size_request;
1117 elmex 1.34
1118 root 1.97 (
1119 root 1.134 $w + $self->border * 2,
1120     $h + $self->border * 2,
1121 root 1.97 )
1122 elmex 1.36 }
1123    
1124     sub size_allocate {
1125 root 1.259 my ($self, $w, $h) = @_;
1126 root 1.40
1127 root 1.269 if ($self->{title}) {
1128     $self->{title}{w} = $w;
1129     $self->{title}{h} = $h;
1130     $self->{title}->size_allocate ($w, $h);
1131     }
1132 elmex 1.36
1133 root 1.269 my $border = $self->border;
1134 root 1.141
1135 root 1.269 $h -= List::Util::max 0, $border * 2;
1136     $w -= List::Util::max 0, $border * 2;
1137    
1138     $self->child->configure ($border, $border, $w, $h);
1139 elmex 1.34 }
1140    
1141 root 1.77 sub button_down {
1142     my ($self, $ev, $x, $y) = @_;
1143    
1144 root 1.176 my ($w, $h) = @$self{qw(w h)};
1145 root 1.134 my $border = $self->border;
1146    
1147 root 1.176 my $lr = ($x >= 0 && $x < $border) || ($x > $w - $border && $x < $w);
1148     my $td = ($y >= 0 && $y < $border) || ($y > $h - $border && $y < $h);
1149 root 1.77
1150 root 1.176 if ($lr & $td) {
1151     my ($wx, $wy) = ($self->{x}, $self->{y});
1152 root 1.139 my ($ox, $oy) = ($ev->{x}, $ev->{y});
1153 root 1.77 my ($bw, $bh) = ($self->{w}, $self->{h});
1154    
1155 root 1.176 my $mx = $x < $border;
1156     my $my = $y < $border;
1157    
1158 root 1.77 $self->{motion} = sub {
1159     my ($ev, $x, $y) = @_;
1160    
1161 root 1.176 my $dx = $ev->{x} - $ox;
1162     my $dy = $ev->{y} - $oy;
1163 root 1.77
1164 root 1.256 $self->{force_w} = $bw + $dx * ($mx ? -1 : 1);
1165     $self->{force_h} = $bh + $dy * ($my ? -1 : 1);
1166    
1167 root 1.277 $self->move_abs ($wx + $dx * $mx, $wy + $dy * $my);
1168 root 1.251 $self->realloc;
1169 root 1.77 };
1170    
1171 root 1.176 } elsif ($lr ^ $td) {
1172 root 1.139 my ($ox, $oy) = ($ev->{x}, $ev->{y});
1173 root 1.77 my ($bx, $by) = ($self->{x}, $self->{y});
1174    
1175     $self->{motion} = sub {
1176     my ($ev, $x, $y) = @_;
1177    
1178 root 1.139 ($x, $y) = ($ev->{x}, $ev->{y});
1179 root 1.77
1180 root 1.256 $self->move_abs ($bx + $x - $ox, $by + $y - $oy);
1181 root 1.277 # HACK: the next line is required to enforce placement
1182     $self->{parent}->size_allocate ($self->{parent}{w}, $self->{parent}{h});
1183 root 1.77 };
1184 root 1.271 } else {
1185     return 0;
1186 root 1.77 }
1187 root 1.271
1188     1
1189 root 1.77 }
1190    
1191     sub button_up {
1192     my ($self, $ev, $x, $y) = @_;
1193    
1194 root 1.271 !!delete $self->{motion}
1195 root 1.77 }
1196    
1197     sub mouse_motion {
1198     my ($self, $ev, $x, $y) = @_;
1199    
1200     $self->{motion}->($ev, $x, $y) if $self->{motion};
1201 root 1.271
1202     !!$self->{motion}
1203 root 1.77 }
1204    
1205 elmex 1.34 sub _draw {
1206     my ($self) = @_;
1207    
1208 root 1.269 my $child = $self->{children}[0];
1209    
1210 root 1.97 my ($w, $h ) = ($self->{w}, $self->{h});
1211 root 1.269 my ($cw, $ch) = ($child->{w}, $child->{h});
1212 elmex 1.34
1213     glEnable GL_TEXTURE_2D;
1214 root 1.97 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
1215 elmex 1.34
1216 root 1.134 my $border = $self->border;
1217    
1218 root 1.97 glColor @{ $self->{border_bg} };
1219 root 1.255 $border[0]->draw_quad_alpha (0, 0, $w, $border);
1220     $border[1]->draw_quad_alpha (0, $border, $border, $ch);
1221     $border[2]->draw_quad_alpha ($w - $border, $border, $border, $ch);
1222     $border[3]->draw_quad_alpha (0, $h - $border, $w, $border);
1223 elmex 1.34
1224 root 1.177 if (@{$self->{bg}} < 4 || $self->{bg}[3]) {
1225 root 1.255 glColor @{ $self->{bg} };
1226 root 1.76
1227 root 1.177 # TODO: repeat texture not scale
1228 root 1.255 # solve this better(?)
1229     $bg->{s} = $cw / $bg->{w};
1230     $bg->{t} = $ch / $bg->{h};
1231 root 1.197 $bg->draw_quad_alpha ($border, $border, $cw, $ch);
1232     }
1233 elmex 1.34
1234 root 1.197 glDisable GL_TEXTURE_2D;
1235 elmex 1.36
1236 root 1.269 $child->draw;
1237 root 1.177
1238 root 1.269 if ($self->{title}) {
1239     glTranslate 0, $border - $self->{h};
1240     $self->{title}->_draw;
1241     }
1242 elmex 1.34 }
1243 elmex 1.31
1244 root 1.39 #############################################################################
1245    
1246 root 1.73 package CFClient::UI::Table;
1247 elmex 1.15
1248 root 1.73 our @ISA = CFClient::UI::Base::;
1249 elmex 1.15
1250 root 1.75 use List::Util qw(max sum);
1251    
1252 root 1.138 use CFClient::OpenGL;
1253 elmex 1.15
1254 root 1.78 sub new {
1255     my $class = shift;
1256    
1257     $class->SUPER::new (
1258     col_expand => [],
1259 root 1.234 @_,
1260 root 1.78 )
1261     }
1262    
1263 root 1.236 sub children {
1264     grep $_, map @$_, grep $_, @{ $_[0]{children} }
1265     }
1266    
1267 elmex 1.15 sub add {
1268 root 1.113 my ($self, $x, $y, $child) = @_;
1269 elmex 1.32
1270 root 1.113 $child->set_parent ($self);
1271     $self->{children}[$y][$x] = $child;
1272 root 1.75
1273 root 1.251 $self->realloc;
1274 root 1.172 }
1275    
1276 root 1.236 # TODO: move to container class maybe? send children a signal on removal?
1277 root 1.115 sub clear {
1278     my ($self) = @_;
1279    
1280 root 1.172 my @children = $self->children;
1281     delete $self->{children};
1282 root 1.163
1283 root 1.172 for (@children) {
1284 root 1.163 delete $_->{parent};
1285     $_->hide;
1286     }
1287    
1288 root 1.251 $self->realloc;
1289 root 1.115 }
1290    
1291 root 1.75 sub get_wh {
1292     my ($self) = @_;
1293    
1294     my (@w, @h);
1295 elmex 1.15
1296 root 1.75 for my $y (0 .. $#{$self->{children}}) {
1297     my $row = $self->{children}[$y]
1298     or next;
1299 elmex 1.15
1300 root 1.75 for my $x (0 .. $#$row) {
1301     my $widget = $row->[$x]
1302     or next;
1303 root 1.149 my ($w, $h) = @$widget{qw(req_w req_h)};
1304 elmex 1.15
1305 root 1.75 $w[$x] = max $w[$x], $w;
1306     $h[$y] = max $h[$y], $h;
1307 elmex 1.17 }
1308 elmex 1.15 }
1309 root 1.75
1310     (\@w, \@h)
1311 elmex 1.15 }
1312    
1313     sub size_request {
1314     my ($self) = @_;
1315    
1316 root 1.75 my ($ws, $hs) = $self->get_wh;
1317 elmex 1.15
1318 root 1.75 (
1319 root 1.78 (sum @$ws),
1320     (sum @$hs),
1321 root 1.75 )
1322     }
1323    
1324     sub size_allocate {
1325 root 1.259 my ($self, $w, $h) = @_;
1326 root 1.75
1327     my ($ws, $hs) = $self->get_wh;
1328    
1329 root 1.238 my $req_w = (sum @$ws) || 1;
1330     my $req_h = (sum @$hs) || 1;
1331 root 1.78
1332     # TODO: nicer code && do row_expand
1333     my @col_expand = @{$self->{col_expand}};
1334     @col_expand = (1) x @$ws unless @col_expand;
1335     my $col_expand = (sum @col_expand) || 1;
1336 elmex 1.15
1337 root 1.75 # linearly scale sizes
1338 root 1.78 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws;
1339     $hs->[$_] *= 1 * $h / $req_h for 0 .. $#$hs;
1340 elmex 1.15
1341 root 1.112 CFClient::UI::harmonize $ws;
1342     CFClient::UI::harmonize $hs;
1343 root 1.106
1344 root 1.75 my $y;
1345 elmex 1.15
1346 root 1.75 for my $r (0 .. $#{$self->{children}}) {
1347     my $row = $self->{children}[$r]
1348     or next;
1349 elmex 1.15
1350     my $x = 0;
1351 root 1.75 my $row_h = $hs->[$r];
1352    
1353     for my $c (0 .. $#$row) {
1354     my $col_w = $ws->[$c];
1355 elmex 1.15
1356 root 1.83 if (my $widget = $row->[$c]) {
1357 root 1.128 $widget->configure ($x, $y, $col_w, $row_h);
1358 root 1.83 }
1359 elmex 1.15
1360 root 1.75 $x += $col_w;
1361 elmex 1.15 }
1362    
1363 root 1.75 $y += $row_h;
1364     }
1365    
1366     }
1367    
1368 root 1.76 sub find_widget {
1369     my ($self, $x, $y) = @_;
1370    
1371     $x -= $self->{x};
1372     $y -= $self->{y};
1373    
1374     my $res;
1375    
1376     for (grep $_, map @$_, grep $_, @{ $self->{children} }) {
1377     $res = $_->find_widget ($x, $y)
1378     and return $res;
1379     }
1380    
1381     $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
1382     }
1383    
1384 root 1.75 sub _draw {
1385     my ($self) = @_;
1386    
1387     for (grep $_, @{$self->{children}}) {
1388     $_->draw for grep $_, @$_;
1389 elmex 1.15 }
1390     }
1391    
1392 root 1.39 #############################################################################
1393    
1394 root 1.246 package CFClient::UI::Box;
1395 root 1.76
1396     our @ISA = CFClient::UI::Container::;
1397    
1398     sub size_request {
1399     my ($self) = @_;
1400    
1401 root 1.246 $self->{vertical}
1402     ? (
1403     (List::Util::max map $_->{req_w}, @{$self->{children}}),
1404     (List::Util::sum map $_->{req_h}, @{$self->{children}}),
1405     )
1406     : (
1407     (List::Util::sum map $_->{req_w}, @{$self->{children}}),
1408     (List::Util::max map $_->{req_h}, @{$self->{children}}),
1409     )
1410 root 1.76 }
1411    
1412     sub size_allocate {
1413 root 1.259 my ($self, $w, $h) = @_;
1414 root 1.76
1415 root 1.246 my $space = $self->{vertical} ? $h : $w;
1416 root 1.76 my $children = $self->{children};
1417    
1418 root 1.247 my @req;
1419 root 1.76
1420 root 1.247 if ($self->{homogeneous}) {
1421     @req = ($space / (@$children || 1)) x @$children;
1422 root 1.76 } else {
1423 root 1.247 @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @$children;
1424     my $req = List::Util::sum @req;
1425    
1426     if ($req > $space) {
1427     # ah well, not enough space
1428     $_ *= $space / $req for @req;
1429     } else {
1430     my $expand = (List::Util::sum map $_->{expand}, @$children) || 1;
1431    
1432     $space = ($space - $req) / $expand; # remaining space to give away
1433    
1434     $req[$_] += $space * $children->[$_]{expand}
1435     for 0 .. $#$children;
1436     }
1437 root 1.76 }
1438    
1439 root 1.246 CFClient::UI::harmonize \@req;
1440 root 1.112
1441 root 1.246 my $pos = 0;
1442 root 1.76 for (0 .. $#$children) {
1443 root 1.246 my $alloc = $req[$_];
1444     $children->[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h));
1445 root 1.76
1446 root 1.246 $pos += $alloc;
1447 root 1.76 }
1448 root 1.125
1449     1
1450 root 1.76 }
1451    
1452     #############################################################################
1453    
1454 root 1.246 package CFClient::UI::HBox;
1455 elmex 1.15
1456 root 1.246 our @ISA = CFClient::UI::Box::;
1457 root 1.76
1458 root 1.246 sub new {
1459     my $class = shift;
1460 elmex 1.15
1461 root 1.246 $class->SUPER::new (
1462     vertical => 0,
1463     @_,
1464 root 1.43 )
1465     }
1466    
1467 root 1.246 #############################################################################
1468 root 1.68
1469 root 1.246 package CFClient::UI::VBox;
1470 root 1.193
1471 root 1.246 our @ISA = CFClient::UI::Box::;
1472 root 1.68
1473 root 1.246 sub new {
1474     my $class = shift;
1475 root 1.68
1476 root 1.246 $class->SUPER::new (
1477     vertical => 1,
1478     @_,
1479     )
1480 elmex 1.36 }
1481    
1482 root 1.39 #############################################################################
1483    
1484 root 1.73 package CFClient::UI::Label;
1485 root 1.10
1486 root 1.209 our @ISA = CFClient::UI::DrawBG::;
1487 root 1.12
1488 root 1.138 use CFClient::OpenGL;
1489 root 1.10
1490     sub new {
1491 root 1.64 my ($class, %arg) = @_;
1492 root 1.51
1493 root 1.59 my $self = $class->SUPER::new (
1494 root 1.164 fg => [1, 1, 1],
1495 root 1.209 #bg => none
1496     #active_bg => none
1497 root 1.164 #font => default_font
1498 root 1.194 #text => initial text
1499     #markup => initial narkup
1500 root 1.213 #max_w => maximum pixel width
1501     ellipsise => 3, # end
1502 root 1.194 layout => (new CFClient::Layout),
1503 root 1.164 fontsize => 1,
1504     align => -1,
1505     valign => -1,
1506 root 1.258 padding_x => 2,
1507     padding_y => 2,
1508 elmex 1.150 can_events => 0,
1509 root 1.64 %arg
1510 root 1.59 );
1511 root 1.10
1512 root 1.141 if (exists $self->{template}) {
1513     my $layout = new CFClient::Layout;
1514     $layout->set_text (delete $self->{template});
1515     $self->{template} = $layout;
1516     }
1517 root 1.121
1518 root 1.194 if (exists $self->{markup}) {
1519     $self->set_markup (delete $self->{markup});
1520     } else {
1521     $self->set_text (delete $self->{text});
1522     }
1523 root 1.10
1524     $self
1525     }
1526    
1527 root 1.209 sub escape($) {
1528     local $_ = $_[0];
1529 root 1.68
1530     s/&/&amp;/g;
1531     s/>/&gt;/g;
1532     s/</&lt;/g;
1533    
1534 root 1.209 $_
1535 root 1.68 }
1536    
1537 root 1.173 sub update {
1538     my ($self) = @_;
1539    
1540     delete $self->{texture};
1541     $self->SUPER::update;
1542     }
1543    
1544 elmex 1.15 sub set_text {
1545     my ($self, $text) = @_;
1546 root 1.28
1547 root 1.173 return if $self->{text} eq "T$text";
1548     $self->{text} = "T$text";
1549    
1550 root 1.194 $self->{layout} = new CFClient::Layout if $self->{layout}->is_rgba;
1551 root 1.121 $self->{layout}->set_text ($text);
1552 root 1.28
1553 root 1.251 $self->realloc;
1554 root 1.252 $self->update;
1555 elmex 1.15 }
1556    
1557 root 1.121 sub set_markup {
1558     my ($self, $markup) = @_;
1559    
1560 root 1.173 return if $self->{text} eq "M$markup";
1561     $self->{text} = "M$markup";
1562    
1563 root 1.194 my $rgba = $markup =~ /span.*(?:foreground|background)/;
1564    
1565     $self->{layout} = new CFClient::Layout $rgba if $self->{layout}->is_rgba != $rgba;
1566 root 1.121 $self->{layout}->set_markup ($markup);
1567 root 1.28
1568 root 1.251 $self->realloc;
1569 root 1.252 $self->update;
1570 elmex 1.15 }
1571    
1572 root 1.14 sub size_request {
1573     my ($self) = @_;
1574    
1575 root 1.157 $self->{layout}->set_font ($self->{font}) if $self->{font};
1576     $self->{layout}->set_width ($self->{max_w} || -1);
1577 root 1.213 $self->{layout}->set_ellipsise ($self->{ellipsise});
1578     $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1579 root 1.134 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1580 root 1.121
1581 root 1.76 my ($w, $h) = $self->{layout}->size;
1582    
1583 root 1.141 if (exists $self->{template}) {
1584 root 1.157 $self->{template}->set_font ($self->{font}) if $self->{font};
1585 root 1.141 $self->{template}->set_height ($self->{fontsize} * $::FONTSIZE);
1586    
1587     my ($w2, $h2) = $self->{template}->size;
1588    
1589     $w = List::Util::max $w, $w2;
1590     $h = List::Util::max $h, $h2;
1591     }
1592    
1593 root 1.258 ($w, $h)
1594 root 1.59 }
1595 root 1.51
1596 root 1.59 sub size_allocate {
1597 root 1.259 my ($self, $w, $h) = @_;
1598 root 1.68
1599 root 1.269 delete $self->{ox};
1600    
1601 root 1.264 delete $self->{texture}
1602 root 1.266 unless $w >= $self->{req_w} && $self->{old_w} >= $self->{req_w};
1603 root 1.14 }
1604    
1605 elmex 1.146 sub set_fontsize {
1606     my ($self, $fontsize) = @_;
1607    
1608     $self->{fontsize} = $fontsize;
1609 root 1.152 delete $self->{texture};
1610 root 1.186
1611 root 1.251 $self->realloc;
1612 elmex 1.146 }
1613    
1614 elmex 1.11 sub _draw {
1615 root 1.10 my ($self) = @_;
1616    
1617 root 1.209 $self->SUPER::_draw; # draw background, if applicable
1618    
1619 root 1.59 my $tex = $self->{texture} ||= do {
1620 root 1.194 $self->{layout}->set_foreground (@{$self->{fg}});
1621 root 1.157 $self->{layout}->set_font ($self->{font}) if $self->{font};
1622 root 1.59 $self->{layout}->set_width ($self->{w});
1623 root 1.213 $self->{layout}->set_ellipsise ($self->{ellipsise});
1624     $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1625     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1626 root 1.194
1627 root 1.269 new_from_layout CFClient::Texture $self->{layout}
1628     };
1629 root 1.194
1630 root 1.269 unless (exists $self->{ox}) {
1631 root 1.258 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x}
1632     : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding_x}
1633 root 1.208 : ($self->{w} - $tex->{w}) * 0.5);
1634    
1635 root 1.258 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y}
1636     : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding_y}
1637 root 1.208 : ($self->{h} - $tex->{h}) * 0.5);
1638 root 1.59 };
1639 root 1.10
1640     glEnable GL_TEXTURE_2D;
1641 root 1.105 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1642 root 1.10
1643 root 1.194 if ($tex->{format} == GL_ALPHA) {
1644     glColor @{$self->{fg}};
1645     $tex->draw_quad_alpha ($self->{ox}, $self->{oy});
1646     } else {
1647     $tex->draw_quad_alpha_premultiplied ($self->{ox}, $self->{oy});
1648     }
1649 root 1.10
1650 root 1.74 glDisable GL_TEXTURE_2D;
1651 root 1.10 }
1652    
1653 root 1.39 #############################################################################
1654    
1655 root 1.121 package CFClient::UI::EntryBase;
1656 elmex 1.31
1657 root 1.73 our @ISA = CFClient::UI::Label::;
1658 elmex 1.31
1659 root 1.138 use CFClient::OpenGL;
1660 elmex 1.31
1661 root 1.68 sub new {
1662     my $class = shift;
1663    
1664     $class->SUPER::new (
1665 root 1.164 fg => [1, 1, 1],
1666     bg => [0, 0, 0, 0.2],
1667     active_bg => [1, 1, 1, 0.5],
1668     active_fg => [0, 0, 0],
1669     can_hover => 1,
1670     can_focus => 1,
1671     valign => 0,
1672 elmex 1.150 can_events => 1,
1673 root 1.225 #text => ...
1674 root 1.68 @_
1675     )
1676     }
1677    
1678     sub _set_text {
1679     my ($self, $text) = @_;
1680    
1681 root 1.121 delete $self->{cur_h};
1682    
1683     return if $self->{text} eq $text;
1684 elmex 1.100
1685 root 1.68 $self->{last_activity} = $::NOW;
1686     $self->{text} = $text;
1687 root 1.72
1688     $text =~ s/./*/g if $self->{hidden};
1689 root 1.121 $self->{layout}->set_text ("$text ");
1690 root 1.72
1691 root 1.231 $self->_emit (changed => $self->{text});
1692 root 1.276 $self->update;
1693 root 1.121 }
1694 root 1.68
1695 root 1.194 sub set_text {
1696     my ($self, $text) = @_;
1697    
1698     $self->{cursor} = length $text;
1699     $self->_set_text ($text);
1700 root 1.251
1701     $self->realloc;
1702 root 1.194 }
1703    
1704 root 1.121 sub get_text {
1705     $_[0]{text}
1706 root 1.68 }
1707    
1708     sub size_request {
1709     my ($self) = @_;
1710    
1711     my ($w, $h) = $self->SUPER::size_request;
1712    
1713     ($w + 1, $h) # add 1 for cursor
1714     }
1715    
1716 elmex 1.31 sub key_down {
1717     my ($self, $ev) = @_;
1718    
1719 root 1.137 my $mod = $ev->{mod};
1720     my $sym = $ev->{sym};
1721     my $uni = $ev->{unicode};
1722 elmex 1.31
1723     my $text = $self->get_text;
1724    
1725 root 1.200 if ($uni == 8) {
1726 root 1.68 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1727 root 1.200 } elsif ($uni == 127) {
1728 root 1.68 substr $text, $self->{cursor}, 1, "";
1729 root 1.136 } elsif ($sym == CFClient::SDLK_LEFT) {
1730 root 1.68 --$self->{cursor} if $self->{cursor};
1731 root 1.136 } elsif ($sym == CFClient::SDLK_RIGHT) {
1732 root 1.68 ++$self->{cursor} if $self->{cursor} < length $self->{text};
1733 root 1.136 } elsif ($sym == CFClient::SDLK_HOME) {
1734 root 1.76 $self->{cursor} = 0;
1735 root 1.136 } elsif ($sym == CFClient::SDLK_END) {
1736 root 1.76 $self->{cursor} = length $text;
1737 root 1.200 } elsif ($uni == 27) {
1738 root 1.231 $self->_emit ('escape');
1739 elmex 1.31 } elsif ($uni) {
1740 root 1.68 substr $text, $self->{cursor}++, 0, chr $uni;
1741 root 1.271 } else {
1742     return 0;
1743 elmex 1.31 }
1744 root 1.51
1745 root 1.68 $self->_set_text ($text);
1746 root 1.251
1747     $self->realloc;
1748 root 1.271
1749     1
1750 root 1.68 }
1751    
1752     sub focus_in {
1753     my ($self) = @_;
1754    
1755     $self->{last_activity} = $::NOW;
1756    
1757     $self->SUPER::focus_in;
1758 elmex 1.31 }
1759    
1760 root 1.51 sub button_down {
1761 root 1.68 my ($self, $ev, $x, $y) = @_;
1762    
1763     $self->SUPER::button_down ($ev, $x, $y);
1764    
1765     my $idx = $self->{layout}->xy_to_index ($x, $y);
1766    
1767     # byte-index to char-index
1768 root 1.76 my $text = $self->{text};
1769 root 1.68 utf8::encode $text;
1770     $self->{cursor} = length substr $text, 0, $idx;
1771 root 1.51
1772 root 1.68 $self->_set_text ($self->{text});
1773     $self->update;
1774 root 1.271
1775     1
1776 root 1.51 }
1777    
1778 root 1.58 sub mouse_motion {
1779     my ($self, $ev, $x, $y) = @_;
1780 root 1.68 # printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d#
1781 root 1.271
1782     0
1783 root 1.58 }
1784    
1785 root 1.51 sub _draw {
1786     my ($self) = @_;
1787    
1788 root 1.68 local $self->{fg} = $self->{fg};
1789    
1790 root 1.51 if ($FOCUS == $self) {
1791 root 1.278 glColor_premultiply @{$self->{active_bg}};
1792 root 1.68 $self->{fg} = $self->{active_fg};
1793 root 1.51 } else {
1794 root 1.278 glColor_premultiply @{$self->{bg}};
1795 root 1.51 }
1796    
1797 root 1.76 glEnable GL_BLEND;
1798 root 1.278 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
1799 root 1.51 glBegin GL_QUADS;
1800 root 1.68 glVertex 0 , 0;
1801     glVertex 0 , $self->{h};
1802     glVertex $self->{w}, $self->{h};
1803     glVertex $self->{w}, 0;
1804 root 1.51 glEnd;
1805 root 1.76 glDisable GL_BLEND;
1806 root 1.51
1807     $self->SUPER::_draw;
1808 root 1.68
1809     #TODO: force update every cursor change :(
1810     if ($FOCUS == $self && (($::NOW - $self->{last_activity}) & 1023) < 600) {
1811 root 1.121
1812     unless (exists $self->{cur_h}) {
1813     my $text = substr $self->{text}, 0, $self->{cursor};
1814     utf8::encode $text;
1815    
1816     @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text)
1817     }
1818    
1819 root 1.68 glColor @{$self->{fg}};
1820     glBegin GL_LINES;
1821 root 1.122 glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy};
1822     glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy} + $self->{cur_h};
1823 root 1.68 glEnd;
1824     }
1825     }
1826    
1827 root 1.121 package CFClient::UI::Entry;
1828 elmex 1.99
1829 root 1.121 our @ISA = CFClient::UI::EntryBase::;
1830 elmex 1.99
1831 root 1.138 use CFClient::OpenGL;
1832 elmex 1.99
1833     sub key_down {
1834     my ($self, $ev) = @_;
1835    
1836 root 1.137 my $sym = $ev->{sym};
1837 elmex 1.99
1838 root 1.136 if ($sym == 13) {
1839 elmex 1.167 unshift @{$self->{history}},
1840     my $txt = $self->get_text;
1841     $self->{history_pointer} = -1;
1842 elmex 1.169 $self->{history_saveback} = '';
1843 root 1.231 $self->_emit (activate => $txt);
1844 elmex 1.99 $self->update;
1845    
1846 elmex 1.167 } elsif ($sym == CFClient::SDLK_UP) {
1847     if ($self->{history_pointer} < 0) {
1848     $self->{history_saveback} = $self->get_text;
1849     }
1850 elmex 1.169 if (@{$self->{history} || []} > 0) {
1851     $self->{history_pointer}++;
1852     if ($self->{history_pointer} >= @{$self->{history} || []}) {
1853     $self->{history_pointer} = @{$self->{history} || []} - 1;
1854     }
1855     $self->set_text ($self->{history}->[$self->{history_pointer}]);
1856 elmex 1.167 }
1857    
1858     } elsif ($sym == CFClient::SDLK_DOWN) {
1859     $self->{history_pointer}--;
1860     $self->{history_pointer} = -1 if $self->{history_pointer} < 0;
1861    
1862     if ($self->{history_pointer} >= 0) {
1863     $self->set_text ($self->{history}->[$self->{history_pointer}]);
1864     } else {
1865     $self->set_text ($self->{history_saveback});
1866     }
1867    
1868 elmex 1.99 } else {
1869 root 1.271 return $self->SUPER::key_down ($ev)
1870 elmex 1.99 }
1871    
1872 root 1.271 1
1873 elmex 1.99 }
1874    
1875 root 1.68 #############################################################################
1876    
1877 root 1.79 package CFClient::UI::Button;
1878    
1879     our @ISA = CFClient::UI::Label::;
1880    
1881 root 1.138 use CFClient::OpenGL;
1882 root 1.79
1883 elmex 1.85 my @tex =
1884 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1885 elmex 1.85 qw(b1_button_active.png);
1886    
1887 root 1.79 sub new {
1888     my $class = shift;
1889    
1890     $class->SUPER::new (
1891 root 1.258 padding_x => 4,
1892     padding_y => 4,
1893 root 1.164 fg => [1, 1, 1],
1894     active_fg => [0, 0, 1],
1895     can_hover => 1,
1896     align => 0,
1897     valign => 0,
1898 elmex 1.150 can_events => 1,
1899 root 1.79 @_
1900     )
1901     }
1902    
1903 root 1.231 sub activate { }
1904    
1905 root 1.79 sub button_up {
1906     my ($self, $ev, $x, $y) = @_;
1907    
1908 root 1.231 $self->emit ("activate")
1909     if $x >= 0 && $x < $self->{w}
1910     && $y >= 0 && $y < $self->{h};
1911 root 1.271
1912     1
1913 root 1.79 }
1914    
1915     sub _draw {
1916     my ($self) = @_;
1917    
1918     local $self->{fg} = $self->{fg};
1919    
1920     if ($GRAB == $self) {
1921     $self->{fg} = $self->{active_fg};
1922     }
1923    
1924 root 1.119 glEnable GL_TEXTURE_2D;
1925 elmex 1.85 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1926 root 1.119 glColor 0, 0, 0, 1;
1927 elmex 1.85
1928 root 1.195 $tex[0]->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
1929 elmex 1.85
1930     glDisable GL_TEXTURE_2D;
1931 root 1.79
1932     $self->SUPER::_draw;
1933     }
1934    
1935     #############################################################################
1936    
1937 root 1.86 package CFClient::UI::CheckBox;
1938    
1939     our @ISA = CFClient::UI::DrawBG::;
1940    
1941 elmex 1.102 my @tex =
1942 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1943 elmex 1.102 qw(c1_checkbox_bg.png c1_checkbox_active.png);
1944    
1945 root 1.138 use CFClient::OpenGL;
1946 root 1.86
1947     sub new {
1948     my $class = shift;
1949    
1950     $class->SUPER::new (
1951 root 1.258 padding_x => 2,
1952     padding_y => 2,
1953 root 1.86 fg => [1, 1, 1],
1954     active_fg => [1, 1, 0],
1955 root 1.209 bg => [0, 0, 0, 0.2],
1956     active_bg => [1, 1, 1, 0.5],
1957 root 1.86 state => 0,
1958 root 1.97 can_hover => 1,
1959 root 1.86 @_
1960     )
1961     }
1962    
1963 root 1.87 sub size_request {
1964     my ($self) = @_;
1965    
1966 root 1.258 (6) x 2
1967 root 1.87 }
1968    
1969 root 1.86 sub button_down {
1970     my ($self, $ev, $x, $y) = @_;
1971    
1972 root 1.258 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
1973     && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
1974 root 1.86 $self->{state} = !$self->{state};
1975 root 1.231 $self->_emit (changed => $self->{state});
1976 root 1.271 } else {
1977     return 0
1978 root 1.86 }
1979 root 1.271
1980     1
1981 root 1.86 }
1982    
1983     sub _draw {
1984     my ($self) = @_;
1985    
1986 root 1.87 $self->SUPER::_draw;
1987 root 1.86
1988 root 1.258 glTranslate $self->{padding_x} + 0.375, $self->{padding_y} + 0.375, 0;
1989 root 1.86
1990 root 1.258 my ($w, $h) = @$self{qw(w h)};
1991    
1992     my $s = List::Util::min $w - $self->{padding_x} * 2, $h - $self->{padding_y} * 2;
1993 elmex 1.102
1994 root 1.87 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };
1995 root 1.86
1996 elmex 1.102 my $tex = $self->{state} ? $tex[1] : $tex[0];
1997    
1998 root 1.197 glEnable GL_TEXTURE_2D;
1999 root 1.195 $tex->draw_quad_alpha (0, 0, $s, $s);
2000 elmex 1.102 glDisable GL_TEXTURE_2D;
2001 root 1.86 }
2002    
2003     #############################################################################
2004    
2005 elmex 1.145 package CFClient::UI::Image;
2006    
2007     our @ISA = CFClient::UI::Base::;
2008    
2009     use CFClient::OpenGL;
2010     use Carp qw/confess/;
2011    
2012     our %loaded_images;
2013    
2014     sub new {
2015     my $class = shift;
2016    
2017 elmex 1.150 my $self = $class->SUPER::new (can_events => 0, @_);
2018 elmex 1.145
2019     $self->{image} or confess "Image has 'image' not set. This is a fatal error!";
2020    
2021     $loaded_images{$self->{image}} ||=
2022     new_from_file CFClient::Texture CFClient::find_rcfile $self->{image}, mipmap => 1;
2023    
2024     my $tex = $self->{tex} = $loaded_images{$self->{image}};
2025    
2026 root 1.147 Scalar::Util::weaken $loaded_images{$self->{image}};
2027    
2028 elmex 1.145 $self->{aspect} = $tex->{w} / $tex->{h};
2029    
2030     $self
2031     }
2032    
2033     sub size_request {
2034     my ($self) = @_;
2035    
2036     ($self->{tex}->{w}, $self->{tex}->{h})
2037     }
2038    
2039     sub _draw {
2040     my ($self) = @_;
2041    
2042     my $tex = $self->{tex};
2043    
2044     my ($w, $h) = ($self->{w}, $self->{h});
2045    
2046     if ($self->{rot90}) {
2047     glRotate 90, 0, 0, 1;
2048     glTranslate 0, -$self->{w}, 0;
2049    
2050     ($w, $h) = ($h, $w);
2051     }
2052    
2053     glEnable GL_TEXTURE_2D;
2054     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2055    
2056 root 1.195 $tex->draw_quad_alpha (0, 0, $w, $h);
2057 elmex 1.145
2058     glDisable GL_TEXTURE_2D;
2059     }
2060    
2061     #############################################################################
2062    
2063 elmex 1.124 package CFClient::UI::VGauge;
2064    
2065     our @ISA = CFClient::UI::Base::;
2066    
2067 root 1.158 use List::Util qw(min max);
2068    
2069 root 1.138 use CFClient::OpenGL;
2070 elmex 1.124
2071     my %tex = (
2072     food => [
2073 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2074 elmex 1.124 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/
2075     ],
2076     grace => [
2077 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2078 root 1.158 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/
2079 elmex 1.124 ],
2080     hp => [
2081 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2082 elmex 1.124 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/
2083     ],
2084     mana => [
2085 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2086 root 1.158 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/
2087 elmex 1.124 ],
2088     );
2089    
2090     # eg. VGauge->new (gauge => 'food'), default gauge: food
2091     sub new {
2092     my $class = shift;
2093    
2094 root 1.140 my $self = $class->SUPER::new (
2095 root 1.141 type => 'food',
2096 root 1.140 @_
2097     );
2098    
2099 root 1.141 $self->{aspect} = $tex{$self->{type}}[0]{w} / $tex{$self->{type}}[0]{h};
2100 elmex 1.124
2101     $self
2102     }
2103    
2104     sub size_request {
2105     my ($self) = @_;
2106    
2107 root 1.143 #my $tex = $tex{$self->{type}}[0];
2108     #@$tex{qw(w h)}
2109     (0, 0)
2110 elmex 1.124 }
2111    
2112     sub set_max {
2113     my ($self, $max) = @_;
2114 root 1.127
2115 root 1.173 return if $self->{max_val} == $max;
2116    
2117 elmex 1.124 $self->{max_val} = $max;
2118 root 1.173 $self->update;
2119 elmex 1.124 }
2120    
2121     sub set_value {
2122     my ($self, $val, $max) = @_;
2123    
2124     $self->set_max ($max)
2125     if defined $max;
2126    
2127 root 1.173 return if $self->{val} == $val;
2128    
2129 elmex 1.124 $self->{val} = $val;
2130     $self->update;
2131     }
2132    
2133     sub _draw {
2134     my ($self) = @_;
2135    
2136 root 1.141 my $tex = $tex{$self->{type}};
2137 root 1.158 my ($t1, $t2, $t3) = @$tex;
2138 elmex 1.124
2139     my ($w, $h) = ($self->{w}, $self->{h});
2140    
2141 elmex 1.142 if ($self->{vertical}) {
2142     glRotate 90, 0, 0, 1;
2143     glTranslate 0, -$self->{w}, 0;
2144    
2145     ($w, $h) = ($h, $w);
2146     }
2147    
2148 elmex 1.124 my $ycut = $self->{val} / ($self->{max_val} || 1);
2149    
2150 root 1.158 my $ycut1 = max 0, min 1, $ycut;
2151     my $ycut2 = max 0, min 1, $ycut - 1;
2152    
2153     my $h1 = $self->{h} * (1 - $ycut1);
2154     my $h2 = $self->{h} * (1 - $ycut2);
2155 elmex 1.124
2156     glEnable GL_BLEND;
2157 root 1.278 glBlendFuncSeparate GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
2158     GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2159 elmex 1.124 glEnable GL_TEXTURE_2D;
2160     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2161    
2162 root 1.131 glBindTexture GL_TEXTURE_2D, $t1->{name};
2163     glBegin GL_QUADS;
2164 root 1.158 glTexCoord 0 , 0; glVertex 0 , 0;
2165     glTexCoord 0 , $t1->{t} * (1 - $ycut1); glVertex 0 , $h1;
2166     glTexCoord $t1->{s}, $t1->{t} * (1 - $ycut1); glVertex $w, $h1;
2167     glTexCoord $t1->{s}, 0; glVertex $w, 0;
2168 root 1.131 glEnd;
2169 elmex 1.124
2170 root 1.158 my $ycut1 = List::Util::min 1, $ycut;
2171 root 1.131 glBindTexture GL_TEXTURE_2D, $t2->{name};
2172     glBegin GL_QUADS;
2173 root 1.158 glTexCoord 0 , $t2->{t} * (1 - $ycut1); glVertex 0 , $h1;
2174     glTexCoord 0 , $t2->{t} * (1 - $ycut2); glVertex 0 , $h2;
2175     glTexCoord $t2->{s}, $t2->{t} * (1 - $ycut2); glVertex $w, $h2;
2176     glTexCoord $t2->{s}, $t2->{t} * (1 - $ycut1); glVertex $w, $h1;
2177 root 1.131 glEnd;
2178 elmex 1.124
2179 root 1.158 if ($t3) {
2180     glBindTexture GL_TEXTURE_2D, $t3->{name};
2181     glBegin GL_QUADS;
2182     glTexCoord 0 , $t3->{t} * (1 - $ycut2); glVertex 0 , $h2;
2183     glTexCoord 0 , $t3->{t}; glVertex 0 , $self->{h};
2184     glTexCoord $t3->{s}, $t3->{t}; glVertex $w, $self->{h};
2185     glTexCoord $t3->{s}, $t3->{t} * (1 - $ycut2); glVertex $w, $h2;
2186     glEnd;
2187     }
2188    
2189 elmex 1.124 glDisable GL_BLEND;
2190     glDisable GL_TEXTURE_2D;
2191     }
2192    
2193     #############################################################################
2194    
2195 root 1.141 package CFClient::UI::Gauge;
2196    
2197     our @ISA = CFClient::UI::VBox::;
2198    
2199     sub new {
2200 root 1.151 my ($class, %arg) = @_;
2201 root 1.141
2202     my $self = $class->SUPER::new (
2203 root 1.171 tooltip => $arg{type},
2204     can_hover => 1,
2205     can_events => 1,
2206 root 1.151 %arg,
2207 root 1.141 );
2208    
2209 root 1.161 $self->add ($self->{value} = new CFClient::UI::Label valign => +1, align => 0, template => "999");
2210     $self->add ($self->{gauge} = new CFClient::UI::VGauge type => $self->{type}, expand => 1, can_hover => 1);
2211     $self->add ($self->{max} = new CFClient::UI::Label valign => -1, align => 0, template => "999");
2212 root 1.141
2213     $self
2214     }
2215    
2216 elmex 1.146 sub set_fontsize {
2217     my ($self, $fsize) = @_;
2218    
2219     $self->{value}->set_fontsize ($fsize);
2220     $self->{max} ->set_fontsize ($fsize);
2221     }
2222    
2223 root 1.173 sub set_max {
2224     my ($self, $max) = @_;
2225    
2226     $self->{gauge}->set_max ($max);
2227     $self->{max}->set_text ($max);
2228     }
2229    
2230 root 1.141 sub set_value {
2231     my ($self, $val, $max) = @_;
2232    
2233     $self->set_max ($max)
2234     if defined $max;
2235    
2236     $self->{gauge}->set_value ($val, $max);
2237     $self->{value}->set_text ($val);
2238     }
2239    
2240     #############################################################################
2241    
2242 root 1.73 package CFClient::UI::Slider;
2243 root 1.68
2244     use strict;
2245    
2246 root 1.138 use CFClient::OpenGL;
2247 root 1.68
2248 root 1.73 our @ISA = CFClient::UI::DrawBG::;
2249 root 1.68
2250 elmex 1.99 my @tex =
2251     map { new_from_file CFClient::Texture CFClient::find_rcfile $_ }
2252     qw(s1_slider.png s1_slider_bg.png);
2253    
2254 root 1.68 sub new {
2255     my $class = shift;
2256    
2257 root 1.206 # range [value, low, high, page, unit]
2258 root 1.68
2259 root 1.97 # TODO: 0-width page
2260     # TODO: req_w/h are wrong with vertical
2261     # TODO: calculations are off
2262 root 1.76 my $self = $class->SUPER::new (
2263 root 1.68 fg => [1, 1, 1],
2264     active_fg => [0, 0, 0],
2265 root 1.209 bg => [0, 0, 0, 0.2],
2266     active_bg => [1, 1, 1, 0.5],
2267 root 1.227 range => [0, 0, 100, 10, 0],
2268 root 1.257 min_w => $::WIDTH / 80,
2269     min_h => $::WIDTH / 80,
2270 root 1.76 vertical => 0,
2271 root 1.97 can_hover => 1,
2272 root 1.217 inner_pad => 0.02,
2273 root 1.68 @_
2274 root 1.76 );
2275    
2276 root 1.206 $self->set_value ($self->{range}[0]);
2277     $self->update;
2278    
2279 root 1.76 $self
2280     }
2281    
2282 root 1.251 sub changed { }
2283    
2284 root 1.225 sub set_range {
2285     my ($self, $range) = @_;
2286    
2287 root 1.239 ($range, $self->{range}) = ($self->{range}, $range);
2288 root 1.225
2289 root 1.239 $self->update
2290     if "@$range" ne "@{$self->{range}}";
2291 root 1.225 }
2292    
2293 root 1.206 sub set_value {
2294     my ($self, $value) = @_;
2295    
2296     my ($old_value, $lo, $hi, $page, $unit) = @{$self->{range}};
2297    
2298     $hi = $lo + 1 if $hi <= $lo;
2299    
2300 root 1.227 $page = $hi - $lo if $page > $hi - $lo;
2301    
2302     $value = $lo if $value < $lo;
2303     $value = $hi - $page if $value > $hi - $page;
2304 root 1.206
2305     $value = $lo + $unit * int +($value - $lo + $unit * 0.5) / $unit
2306     if $unit;
2307    
2308     @{$self->{range}} = ($value, $lo, $hi, $page, $unit);
2309    
2310     if ($value != $old_value) {
2311 root 1.231 $self->_emit (changed => $value);
2312 root 1.206 $self->update;
2313     }
2314     }
2315    
2316 root 1.76 sub size_request {
2317     my ($self) = @_;
2318    
2319 root 1.257 ($self->{req_w}, $self->{req_h})
2320 root 1.68 }
2321    
2322 root 1.69 sub button_down {
2323     my ($self, $ev, $x, $y) = @_;
2324    
2325     $self->SUPER::button_down ($ev, $x, $y);
2326 root 1.227
2327     $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x];
2328    
2329 root 1.271 $self->mouse_motion ($ev, $x, $y)
2330 root 1.69 }
2331    
2332     sub mouse_motion {
2333     my ($self, $ev, $x, $y) = @_;
2334    
2335     if ($GRAB == $self) {
2336 root 1.71 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w});
2337    
2338 root 1.206 my (undef, $lo, $hi, $page) = @{$self->{range}};
2339 elmex 1.103
2340 root 1.227 $x = ($x - $self->{click}[1]) / ($w * $self->{scale});
2341 root 1.69
2342 root 1.227 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo));
2343 root 1.271 } else {
2344     return 0;
2345 root 1.69 }
2346 root 1.271
2347     1
2348 root 1.69 }
2349    
2350 root 1.206 sub update {
2351     my ($self) = @_;
2352    
2353 root 1.275 delete $self->{knob_w};
2354     $self->SUPER::update;
2355     }
2356    
2357     sub _draw {
2358     my ($self) = @_;
2359    
2360     unless ($self->{knob_w}) {
2361 root 1.206 $self->set_value ($self->{range}[0]);
2362    
2363     my ($value, $lo, $hi, $page) = @{$self->{range}};
2364 root 1.227 my $range = ($hi - $page - $lo) || 1e-100;
2365 root 1.206
2366 root 1.227 my $knob_w = List::Util::min 1, $page / ($hi - $lo) || 0.1;
2367 root 1.206
2368 root 1.227 $self->{offset} = List::Util::max $self->{inner_pad}, $knob_w * 0.5;
2369     $self->{scale} = 1 - 2 * $self->{offset} || 1e-100;
2370 root 1.206
2371 root 1.227 $value = ($value - $lo) / $range;
2372     $value = $value * $self->{scale} + $self->{offset};
2373 root 1.206
2374 root 1.227 $self->{knob_x} = $value - $knob_w * 0.5;
2375     $self->{knob_w} = $knob_w;
2376 root 1.275 }
2377 root 1.68
2378     $self->SUPER::_draw ();
2379    
2380 root 1.206 glScale $self->{w}, $self->{h};
2381 root 1.68
2382     if ($self->{vertical}) {
2383     # draw a vertical slider like a rotated horizontal slider
2384    
2385 root 1.214 glTranslate 1, 0, 0;
2386 root 1.68 glRotate 90, 0, 0, 1;
2387     }
2388    
2389     my $fg = $FOCUS == $self ? $self->{active_fg} : $self->{fg};
2390     my $bg = $FOCUS == $self ? $self->{active_bg} : $self->{bg};
2391    
2392 elmex 1.99 glEnable GL_TEXTURE_2D;
2393     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2394    
2395     # draw background
2396 root 1.206 $tex[1]->draw_quad_alpha (0, 0, 1, 1);
2397 root 1.69
2398 elmex 1.99 # draw handle
2399 root 1.206 $tex[0]->draw_quad_alpha ($self->{knob_x}, 0, $self->{knob_w}, 1);
2400 root 1.69
2401 elmex 1.99 glDisable GL_TEXTURE_2D;
2402 root 1.51 }
2403    
2404 root 1.39 #############################################################################
2405    
2406 root 1.225 package CFClient::UI::ValSlider;
2407    
2408     our @ISA = CFClient::UI::HBox::;
2409    
2410     sub new {
2411     my ($class, %arg) = @_;
2412    
2413     my $range = delete $arg{range};
2414    
2415     my $self = $class->SUPER::new (
2416     slider => (new CFClient::UI::Slider expand => 1, range => $range),
2417     entry => (new CFClient::UI::Label text => "", template => delete $arg{template}),
2418     to_value => sub { shift },
2419     from_value => sub { shift },
2420     %arg,
2421     );
2422    
2423     $self->{slider}->connect (changed => sub {
2424     my ($self, $value) = @_;
2425     $self->{parent}{entry}->set_text ($self->{parent}{to_value}->($value));
2426     $self->{parent}->emit (changed => $value);
2427     });
2428    
2429     # $self->{entry}->connect (changed => sub {
2430     # my ($self, $value) = @_;
2431     # $self->{parent}{slider}->set_value ($self->{parent}{from_value}->($value));
2432     # $self->{parent}->emit (changed => $value);
2433     # });
2434    
2435     $self->add ($self->{slider}, $self->{entry});
2436    
2437     $self->{slider}->emit (changed => $self->{slider}{range}[0]);
2438    
2439     $self
2440     }
2441    
2442     sub set_range { shift->{slider}->set_range (@_) }
2443     sub set_value { shift->{slider}->set_value (@_) }
2444    
2445     #############################################################################
2446    
2447 root 1.97 package CFClient::UI::TextView;
2448    
2449     our @ISA = CFClient::UI::HBox::;
2450    
2451 root 1.138 use CFClient::OpenGL;
2452 root 1.97
2453     sub new {
2454     my $class = shift;
2455    
2456     my $self = $class->SUPER::new (
2457 root 1.164 fontsize => 1,
2458     can_events => 0,
2459     #font => default_font
2460 root 1.105 @_,
2461 root 1.164
2462 root 1.195 layout => (new CFClient::Layout 1),
2463 root 1.164 par => [],
2464     height => 0,
2465     children => [
2466 root 1.97 (new CFClient::UI::Empty expand => 1),
2467     (new CFClient::UI::Slider vertical => 1),
2468     ],
2469     );
2470    
2471 root 1.176 $self->{children}[1]->connect (changed => sub { $self->update });
2472 root 1.107
2473 root 1.97 $self
2474     }
2475    
2476 root 1.107 sub set_fontsize {
2477     my ($self, $fontsize) = @_;
2478    
2479     $self->{fontsize} = $fontsize;
2480     $self->reflow;
2481     }
2482    
2483 root 1.220 sub size_allocate {
2484 root 1.259 my ($self, $w, $h) = @_;
2485 root 1.220
2486 root 1.259 $self->SUPER::size_allocate ($w, $h);
2487 root 1.220
2488     $self->{layout}->set_font ($self->{font}) if $self->{font};
2489     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
2490     $self->{layout}->set_width ($self->{children}[0]{w});
2491    
2492     $self->reflow;
2493     }
2494    
2495 root 1.228 sub text_size {
2496 root 1.220 my ($self, $text, $indent) = @_;
2497 root 1.105
2498     my $layout = $self->{layout};
2499    
2500 root 1.134 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2501 root 1.220 $layout->set_width ($self->{children}[0]{w} - $indent);
2502 root 1.195 $layout->set_markup ($text);
2503 root 1.105
2504 root 1.228 $layout->size
2505 root 1.105 }
2506    
2507     sub reflow {
2508     my ($self) = @_;
2509    
2510 root 1.107 $self->{need_reflow}++;
2511     $self->update;
2512 root 1.105 }
2513    
2514 root 1.227 sub set_offset {
2515     my ($self, $offset) = @_;
2516    
2517     # todo: base offset on lines or so, not on pixels
2518     $self->{children}[1]->set_value ($offset);
2519     }
2520    
2521 root 1.226 sub clear {
2522     my ($self) = @_;
2523    
2524     $self->{par} = [];
2525     $self->{height} = 0;
2526 root 1.227 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]);
2527 root 1.226 }
2528    
2529 root 1.97 sub add_paragraph {
2530 root 1.220 my ($self, $color, $text, $indent) = @_;
2531 root 1.97
2532 root 1.220 for my $line (split /\n/, $text) {
2533 root 1.228 my ($w, $h) = $self->text_size ($line);
2534     $self->{height} += $h;
2535     push @{$self->{par}}, [$w + $indent, $h, $color, $indent, $line];
2536 root 1.220 }
2537 root 1.105
2538 root 1.227 $self->{children}[1]->set_range ([$self->{height}, 0, $self->{height}, $self->{h}, 1]);
2539 root 1.97 }
2540    
2541 root 1.105 sub update {
2542 root 1.97 my ($self) = @_;
2543    
2544 root 1.105 $self->SUPER::update;
2545    
2546     return unless $self->{h} > 0;
2547    
2548 root 1.107 delete $self->{texture};
2549    
2550 root 1.198 $ROOT->on_post_alloc ($self, sub {
2551 root 1.228 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
2552    
2553 root 1.107 if (delete $self->{need_reflow}) {
2554     my $height = 0;
2555    
2556 root 1.228 my $layout = $self->{layout};
2557    
2558     $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2559    
2560     for (@{$self->{par}}) {
2561     if (1 || $_->[0] >= $W) { # TODO: works,but needs reconfigure etc. support
2562     $layout->set_width ($W - $_->[3]);
2563     $layout->set_markup ($_->[4]);
2564     my ($w, $h) = $layout->size;
2565     $_->[0] = $w + $_->[3];
2566     $_->[1] = $h;
2567     }
2568    
2569     $height += $_->[1];
2570     }
2571 root 1.107
2572     $self->{height} = $height;
2573    
2574 root 1.228 $self->{children}[1]->set_range ([$height, 0, $height, $H, 1]);
2575 root 1.107
2576     delete $self->{texture};
2577     }
2578    
2579 root 1.228 $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub {
2580 root 1.215 glClearColor 0.5, 0.5, 0.5, 0;
2581 root 1.107 glClear GL_COLOR_BUFFER_BIT;
2582    
2583     my $top = int $self->{children}[1]{range}[0];
2584 root 1.105
2585 root 1.107 my $y0 = $top;
2586 root 1.228 my $y1 = $top + $H;
2587 root 1.105
2588 root 1.107 my $y = 0;
2589 root 1.97
2590 root 1.107 my $layout = $self->{layout};
2591 root 1.97
2592 root 1.157 $layout->set_font ($self->{font}) if $self->{font};
2593    
2594 root 1.220 glEnable GL_BLEND;
2595 root 1.228 #TODO# not correct in windows where rgba is forced off
2596 root 1.220 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2597    
2598 root 1.107 for my $par (@{$self->{par}}) {
2599 root 1.228 my $h = $par->[1];
2600 root 1.97
2601 root 1.107 if ($y0 < $y + $h && $y < $y1) {
2602 root 1.228 $layout->set_foreground (@{ $par->[2] });
2603     $layout->set_width ($W - $par->[3]);
2604     $layout->set_markup ($par->[4]);
2605 root 1.220
2606     my ($w, $h, $data, $format, $internalformat) = $layout->render;
2607 root 1.105
2608 root 1.228 glRasterPos $par->[3], $y - $y0;
2609 root 1.220 glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data;
2610 root 1.107 }
2611    
2612     $y += $h;
2613 root 1.105 }
2614    
2615 root 1.220 glDisable GL_BLEND;
2616 root 1.107 };
2617     });
2618 root 1.105 }
2619 root 1.97
2620 root 1.105 sub _draw {
2621     my ($self) = @_;
2622 root 1.97
2623 root 1.176 glEnable GL_TEXTURE_2D;
2624     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2625     glColor 1, 1, 1, 1;
2626 root 1.216 $self->{texture}->draw_quad_alpha (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h});
2627 root 1.176 glDisable GL_TEXTURE_2D;
2628 root 1.97
2629 root 1.106 $self->{children}[1]->draw;
2630    
2631 root 1.97 }
2632    
2633     #############################################################################
2634    
2635 root 1.73 package CFClient::UI::Animator;
2636 root 1.35
2637 root 1.138 use CFClient::OpenGL;
2638 root 1.35
2639 root 1.73 our @ISA = CFClient::UI::Bin::;
2640 root 1.35
2641     sub moveto {
2642     my ($self, $x, $y) = @_;
2643    
2644     $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
2645 root 1.56 $self->{speed} = 0.001;
2646 root 1.35 $self->{time} = 1;
2647    
2648     ::animation_start $self;
2649     }
2650    
2651     sub animate {
2652     my ($self, $interval) = @_;
2653    
2654     $self->{time} -= $interval * $self->{speed};
2655     if ($self->{time} <= 0) {
2656     $self->{time} = 0;
2657     ::animation_stop $self;
2658     }
2659    
2660     my ($x0, $y0, $x1, $y1) = @{$self->{moveto}};
2661    
2662     $self->{x} = $x0 * $self->{time} + $x1 * (1 - $self->{time});
2663     $self->{y} = $y0 * $self->{time} + $y1 * (1 - $self->{time});
2664     }
2665    
2666     sub _draw {
2667     my ($self) = @_;
2668    
2669     glPushMatrix;
2670 root 1.51 glRotate $self->{time} * 1000, 0, 1, 0;
2671 root 1.38 $self->{children}[0]->draw;
2672 root 1.35 glPopMatrix;
2673     }
2674    
2675 root 1.51 #############################################################################
2676    
2677 root 1.96 package CFClient::UI::Flopper;
2678    
2679     our @ISA = CFClient::UI::Button::;
2680    
2681     sub new {
2682     my $class = shift;
2683    
2684     my $self = $class->SUPER::new (
2685 root 1.243 state => 0,
2686     on_activate => \&toggle_flopper,
2687 root 1.96 @_
2688     );
2689    
2690     $self
2691     }
2692    
2693     sub toggle_flopper {
2694     my ($self) = @_;
2695    
2696 elmex 1.245 $self->{other}->toggle_visibility;
2697 root 1.96 }
2698    
2699     #############################################################################
2700    
2701 root 1.153 package CFClient::UI::Tooltip;
2702    
2703     our @ISA = CFClient::UI::Bin::;
2704    
2705     use CFClient::OpenGL;
2706    
2707     sub new {
2708     my $class = shift;
2709    
2710     $class->SUPER::new (
2711     @_,
2712     can_events => 0,
2713     )
2714     }
2715    
2716 root 1.196 sub set_tooltip_from {
2717     my ($self, $widget) = @_;
2718 root 1.195
2719 root 1.259 my $tooltip = $widget->{tooltip};
2720    
2721     if ($ENV{CFPLUS_DEBUG} & 2) {
2722     $tooltip .= "\n\n" . (ref $widget) . "\n"
2723     . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n"
2724     . "req $widget->{req_w} $widget->{req_h}\n"
2725     . "visible $widget->{visible}";
2726     }
2727    
2728 root 1.197 $self->add (new CFClient::UI::Label
2729 root 1.259 markup => $tooltip,
2730 root 1.213 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH,
2731     fontsize => 0.8,
2732     fg => [0, 0, 0, 1],
2733     ellipsise => 0,
2734     font => ($widget->{tooltip_font} || $::FONT_PROP),
2735 root 1.197 );
2736 root 1.153 }
2737    
2738     sub size_request {
2739     my ($self) = @_;
2740    
2741     my ($w, $h) = @{$self->child}{qw(req_w req_h)};
2742    
2743 root 1.154 ($w + 4, $h + 4)
2744     }
2745    
2746 root 1.162 sub size_allocate {
2747 root 1.259 my ($self, $w, $h) = @_;
2748 root 1.162
2749 root 1.259 $self->SUPER::size_allocate ($w - 4, $h - 4);
2750 root 1.162 }
2751    
2752 root 1.253 sub visibility_change {
2753     my ($self, $visible) = @_;
2754    
2755     return unless $visible;
2756    
2757     $self->{root}->on_post_alloc ("move_$self" => sub {
2758 root 1.254 my $widget = $self->{owner}
2759     or return;
2760 root 1.253
2761     my ($x, $y) = $widget->coord2global ($widget->{w}, 0);
2762    
2763     ($x, $y) = $widget->coord2global (-$self->{w}, 0)
2764     if $x + $self->{w} > $::WIDTH;
2765    
2766 root 1.256 $self->move_abs ($x, $y);
2767 root 1.253 });
2768     }
2769    
2770 root 1.154 sub _draw {
2771     my ($self) = @_;
2772    
2773     glTranslate 0.375, 0.375;
2774    
2775     my ($w, $h) = @$self{qw(w h)};
2776    
2777     glColor 1, 0.8, 0.4;
2778     glBegin GL_QUADS;
2779     glVertex 0 , 0;
2780     glVertex 0 , $h;
2781     glVertex $w, $h;
2782     glVertex $w, 0;
2783     glEnd;
2784    
2785     glColor 0, 0, 0;
2786     glBegin GL_LINE_LOOP;
2787     glVertex 0 , 0;
2788     glVertex 0 , $h;
2789     glVertex $w, $h;
2790     glVertex $w, 0;
2791     glEnd;
2792    
2793 root 1.197 glTranslate 2 - 0.375, 2 - 0.375;
2794 root 1.252
2795 root 1.154 $self->SUPER::_draw;
2796 root 1.153 }
2797    
2798     #############################################################################
2799    
2800 root 1.162 package CFClient::UI::Face;
2801    
2802     our @ISA = CFClient::UI::Base::;
2803    
2804     use CFClient::OpenGL;
2805    
2806     sub new {
2807     my $class = shift;
2808    
2809 root 1.217 my $self = $class->SUPER::new (
2810 root 1.234 aspect => 1,
2811     can_events => 0,
2812 root 1.162 @_,
2813 root 1.217 );
2814    
2815     if ($self->{anim} && $self->{animspeed}) {
2816     Scalar::Util::weaken (my $widget = $self);
2817    
2818     $self->{timer} = Event->timer (
2819     at => $self->{animspeed} * int $::NOW / $self->{animspeed},
2820     hard => 1,
2821     interval => $self->{animspeed},
2822     cb => sub {
2823     ++$widget->{frame};
2824     $widget->update;
2825     },
2826     );
2827     }
2828    
2829     $self
2830 root 1.162 }
2831    
2832     sub size_request {
2833     (32, 8)
2834     }
2835    
2836 root 1.222 sub update {
2837     my ($self) = @_;
2838    
2839     return unless $self->{visible};
2840    
2841     $self->SUPER::update;
2842     }
2843    
2844 elmex 1.179 sub _draw {
2845 root 1.162 my ($self) = @_;
2846    
2847 root 1.227 return unless $::CONN;
2848 root 1.162
2849 root 1.217 my $face;
2850    
2851     if ($self->{frame}) {
2852     my $anim = $::CONN->{anim}[$self->{anim}];
2853    
2854     $face = $anim->[ $self->{frame} % @$anim ]
2855     if $anim && @$anim;
2856     }
2857    
2858     my $tex = $::CONN->{texture}[$::CONN->{faceid}[$face || $self->{face}]];
2859    
2860 root 1.162 if ($tex) {
2861     glEnable GL_TEXTURE_2D;
2862     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2863     glColor 1, 1, 1, 1;
2864 root 1.195 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2865 root 1.162 glDisable GL_TEXTURE_2D;
2866     }
2867     }
2868    
2869 root 1.217 sub DESTROY {
2870     my ($self) = @_;
2871    
2872     $self->{timer}->cancel
2873     if $self->{timer};
2874    
2875     $self->SUPER::DESTROY;
2876     }
2877    
2878 root 1.162 #############################################################################
2879    
2880 root 1.272 package CFClient::UI::Buttonbar;
2881    
2882     our @ISA = CFClient::UI::HBox::;
2883    
2884     # TODO: should actualyl wrap buttons and other goodies.
2885    
2886     #############################################################################
2887    
2888 root 1.178 package CFClient::UI::Menu;
2889    
2890     our @ISA = CFClient::UI::FancyFrame::;
2891    
2892     use CFClient::OpenGL;
2893    
2894     sub new {
2895     my $class = shift;
2896    
2897     my $self = $class->SUPER::new (
2898     items => [],
2899     z => 100,
2900     @_,
2901     );
2902    
2903     $self->add ($self->{vbox} = new CFClient::UI::VBox);
2904    
2905     for my $item (@{ $self->{items} }) {
2906     my ($widget, $cb) = @$item;
2907    
2908     # handle various types of items, only text for now
2909     if (!ref $widget) {
2910     $widget = new CFClient::UI::Label
2911     can_hover => 1,
2912     can_events => 1,
2913     text => $widget;
2914     }
2915    
2916     $self->{item}{$widget} = $item;
2917    
2918     $self->{vbox}->add ($widget);
2919     }
2920    
2921     $self
2922     }
2923    
2924     # popup given the event (must be a mouse button down event currently)
2925     sub popup {
2926     my ($self, $ev) = @_;
2927    
2928 root 1.231 $self->_emit ("popdown");
2929 root 1.178
2930     # maybe save $GRAB? must be careful about events...
2931     $GRAB = $self;
2932     $self->{button} = $ev->{button};
2933    
2934     $self->show;
2935 root 1.258 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5);
2936 root 1.178 }
2937    
2938     sub mouse_motion {
2939     my ($self, $ev, $x, $y) = @_;
2940    
2941 root 1.182 # TODO: should use vbox->find_widget or so
2942 root 1.178 $HOVER = $ROOT->find_widget ($ev->{x}, $ev->{y});
2943     $self->{hover} = $self->{item}{$HOVER};
2944 root 1.271
2945     0
2946 root 1.178 }
2947    
2948     sub button_up {
2949     my ($self, $ev, $x, $y) = @_;
2950    
2951     if ($ev->{button} == $self->{button}) {
2952     undef $GRAB;
2953     $self->hide;
2954    
2955 root 1.231 $self->_emit ("popdown");
2956 root 1.178 $self->{hover}[1]->() if $self->{hover};
2957 root 1.271 } else {
2958     return 0
2959 root 1.178 }
2960 root 1.271
2961     1
2962 root 1.178 }
2963    
2964     #############################################################################
2965    
2966 root 1.272 package CFClient::UI::Multiplexer;
2967    
2968     our @ISA = CFClient::UI::Container::;
2969    
2970     sub new {
2971     my $class = shift;
2972    
2973     my $self = $class->SUPER::new (
2974     @_,
2975     );
2976    
2977     $self->{current} = $self->{children}[0]
2978     if @{ $self->{children} };
2979    
2980     $self
2981     }
2982    
2983     sub add {
2984     my ($self, @widgets) = @_;
2985    
2986     $self->SUPER::add (@widgets);
2987    
2988     $self->{current} = $self->{children}[0]
2989     if @{ $self->{children} };
2990     }
2991    
2992     sub set_current_page {
2993     my ($self, $page_or_widget) = @_;
2994    
2995     my $widget = ref $page_or_widget
2996     ? $page_or_widget
2997     : $self->{children}[$page_or_widget];
2998    
2999     $self->{current} = $widget;
3000     $self->{current}->configure (0, 0, $self->{w}, $self->{h});
3001    
3002     $self->_emit (page_changed => $self->{current});
3003    
3004     $self->realloc;
3005     }
3006    
3007     sub visible_children {
3008     $_[0]{current}
3009     }
3010    
3011     sub size_request {
3012     my ($self) = @_;
3013    
3014     $self->{current}->size_request
3015     }
3016    
3017     sub size_allocate {
3018     my ($self, $w, $h) = @_;
3019    
3020     $self->{current}->configure (0, 0, $w, $h);
3021     }
3022    
3023     sub _draw {
3024     my ($self) = @_;
3025    
3026     $self->{current}->draw;
3027     }
3028    
3029     #############################################################################
3030    
3031     package CFClient::UI::Notebook;
3032    
3033     our @ISA = CFClient::UI::VBox::;
3034    
3035     sub new {
3036     my $class = shift;
3037    
3038     my $self = $class->SUPER::new (
3039     buttonbar => (new CFClient::UI::Buttonbar),
3040     multiplexer => (new CFClient::UI::Multiplexer expand => 1),
3041 root 1.273 # filter => # will be put between multiplexer and $self
3042 root 1.272 @_,
3043     );
3044 root 1.273
3045     $self->{filter}->add ($self->{multiplexer}) if $self->{filter};
3046     $self->SUPER::add ($self->{buttonbar}, $self->{filter} || $self->{multiplexer});
3047 root 1.272
3048     $self
3049     }
3050    
3051     sub add {
3052     my ($self, $title, $widget, $tooltip) = @_;
3053    
3054     Scalar::Util::weaken $self;
3055    
3056     $self->{buttonbar}->add (new CFClient::UI::Button
3057     markup => $title,
3058     tooltip => $tooltip,
3059     on_activate => sub { $self->set_current_page ($widget) },
3060     );
3061    
3062     $self->{multiplexer}->add ($widget);
3063     }
3064    
3065     sub set_current_page {
3066     my ($self, $page) = @_;
3067    
3068     $self->{multiplexer}->set_current_page ($page);
3069     $self->_emit (page_changed => $self->{multiplexer}{current});
3070     }
3071    
3072     #############################################################################
3073    
3074 root 1.194 package CFClient::UI::Statusbox;
3075    
3076     our @ISA = CFClient::UI::VBox::;
3077    
3078 root 1.210 sub new {
3079     my $class = shift;
3080    
3081     $class->SUPER::new (
3082     fontsize => 0.8,
3083     @_,
3084     )
3085     }
3086    
3087 root 1.194 sub reorder {
3088     my ($self) = @_;
3089     my $NOW = time;
3090    
3091     while (my ($k, $v) = each %{ $self->{item} }) {
3092     delete $self->{item}{$k} if $v->{timeout} < $NOW;
3093     }
3094    
3095     my @widgets;
3096 root 1.197
3097     my @items = sort {
3098     $a->{pri} <=> $b->{pri}
3099     or $b->{id} <=> $a->{id}
3100     } values %{ $self->{item} };
3101    
3102 root 1.194 my $count = 10 + 1;
3103     for my $item (@items) {
3104     last unless --$count;
3105    
3106     push @widgets, $item->{label} ||= do {
3107     # TODO: doesn't handle markup well (read as: at all)
3108 root 1.197 my $short = $item->{count} > 1
3109     ? "<b>$item->{count} ×</b> $item->{text}"
3110     : $item->{text};
3111    
3112 root 1.194 for ($short) {
3113     s/^\s+//;
3114 root 1.205 s/\s+/ /g;
3115 root 1.194 }
3116    
3117     new CFClient::UI::Label
3118 root 1.196 markup => $short,
3119 root 1.197 tooltip => $item->{tooltip},
3120 root 1.196 tooltip_font => $::FONT_PROP,
3121 root 1.197 tooltip_width => 0.67,
3122 root 1.213 fontsize => $item->{fontsize} || $self->{fontsize},
3123     max_w => $::WIDTH * 0.44,
3124 root 1.205 fg => $item->{fg},
3125 root 1.196 can_events => 1,
3126 root 1.197 can_hover => 1
3127 root 1.194 };
3128     }
3129    
3130     $self->clear;
3131 root 1.197 $self->SUPER::add (reverse @widgets);
3132 root 1.194 }
3133    
3134     sub add {
3135     my ($self, $text, %arg) = @_;
3136    
3137 root 1.198 $text =~ s/^\s+//;
3138     $text =~ s/\s+$//;
3139    
3140 root 1.233 return unless $text;
3141    
3142 root 1.197 my $timeout = time + ((delete $arg{timeout}) || 60);
3143 root 1.194
3144 root 1.197 my $group = exists $arg{group} ? $arg{group} : ++$self->{id};
3145 root 1.194
3146 root 1.197 if (my $item = $self->{item}{$group}) {
3147     if ($item->{text} eq $text) {
3148     $item->{count}++;
3149     } else {
3150     $item->{count} = 1;
3151     $item->{text} = $item->{tooltip} = $text;
3152     }
3153 root 1.198 $item->{id} = ++$self->{id};
3154 root 1.197 $item->{timeout} = $timeout;
3155     delete $item->{label};
3156     } else {
3157     $self->{item}{$group} = {
3158     id => ++$self->{id},
3159     text => $text,
3160     timeout => $timeout,
3161     tooltip => $text,
3162 root 1.205 fg => [0.8, 0.8, 0.8, 0.8],
3163 root 1.197 pri => 0,
3164     count => 1,
3165     %arg,
3166     };
3167     }
3168 root 1.194
3169     $self->reorder;
3170     }
3171    
3172 root 1.213 sub reconfigure {
3173     my ($self) = @_;
3174    
3175     delete $_->{label}
3176     for values %{ $self->{item} || {} };
3177    
3178     $self->reorder;
3179     $self->SUPER::reconfigure;
3180     }
3181    
3182 root 1.194 #############################################################################
3183    
3184 root 1.265 package CFClient::UI::Inventory;
3185 root 1.51
3186 root 1.265 our @ISA = CFClient::UI::ScrolledWindow::;
3187 root 1.107
3188 root 1.191 sub new {
3189     my $class = shift;
3190    
3191 root 1.251 my $self = $class->SUPER::new (
3192 root 1.273 child => (new CFClient::UI::Table col_expand => [0, 1, 0]),
3193 root 1.191 @_,
3194 root 1.251 );
3195    
3196     $self
3197 root 1.191 }
3198    
3199 root 1.265 sub set_items {
3200     my ($self, $items) = @_;
3201    
3202 root 1.273 $self->{child}->clear;
3203 root 1.265 return unless $items;
3204 root 1.186
3205 root 1.265 my @items = sort {
3206     ($a->{type} <=> $b->{type})
3207     or ($a->{name} cmp $b->{name})
3208     } @$items;
3209 root 1.186
3210 root 1.265 $self->{real_items} = \@items;
3211 root 1.256
3212 root 1.265 my $row = 0;
3213     for my $item (@items) {
3214     CFClient::Item::update_widgets $item;
3215 root 1.256
3216 root 1.273 $self->{child}->add (0, $row, $item->{face_widget});
3217     $self->{child}->add (1, $row, $item->{desc_widget});
3218     $self->{child}->add (2, $row, $item->{weight_widget});
3219 root 1.256
3220 root 1.265 $row++;
3221     }
3222 root 1.256 }
3223    
3224 root 1.265 #############################################################################
3225 root 1.186
3226 root 1.265 package CFClient::UI::BindEditor;
3227 root 1.149
3228 root 1.265 our @ISA = CFClient::UI::FancyFrame::;
3229 root 1.205
3230 root 1.265 sub new {
3231     my $class = shift;
3232 root 1.205
3233 root 1.265 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3234 root 1.191
3235 root 1.265 $self->add (my $vb = new CFClient::UI::VBox);
3236 root 1.191
3237 root 1.51
3238 root 1.265 $vb->add ($self->{rec_btn} = new CFClient::UI::Button
3239     text => "start recording",
3240     tooltip => "Start/Stops recording of actions."
3241     ."All subsequent actions after the recording started will be captured."
3242     ."The actions are displayed after the record was stopped."
3243     ."To bind the action you have to click on the 'Bind' button",
3244     on_activate => sub {
3245     unless ($self->{recording}) {
3246     $self->start;
3247     } else {
3248     $self->stop;
3249     }
3250     });
3251 root 1.58
3252 root 1.265 $vb->add (new CFClient::UI::Label text => "Actions:");
3253     $vb->add ($self->{cmdbox} = new CFClient::UI::VBox);
3254 root 1.58
3255 root 1.265 $vb->add (new CFClient::UI::Label text => "Bound to: ");
3256     $vb->add (my $hb = new CFClient::UI::HBox);
3257     $hb->add ($self->{keylbl} = new CFClient::UI::Label expand => 1);
3258     $hb->add (new CFClient::UI::Button
3259     text => "bind",
3260     tooltip => "This opens a query where you have to press the key combination to bind the recorded actions",
3261     on_activate => sub {
3262     $self->ask_for_bind;
3263     });
3264 root 1.51
3265 root 1.265 $vb->add (my $hb = new CFClient::UI::HBox);
3266     $hb->add (new CFClient::UI::Button
3267     text => "ok",
3268     expand => 1,
3269     tooltip => "This closes the binding editor and saves the binding",
3270     on_activate => sub {
3271     $self->hide;
3272     $self->commit;
3273     });
3274 root 1.51
3275 root 1.265 $hb->add (new CFClient::UI::Button
3276     text => "cancel",
3277     expand => 1,
3278     tooltip => "This closes the binding editor without saving",
3279     on_activate => sub {
3280     $self->hide;
3281     $self->{binding_cancel}->()
3282     if $self->{binding_cancel};
3283     });
3284 root 1.203
3285 root 1.265 $self->update_binding_widgets;
3286 elmex 1.146
3287 root 1.265 $self
3288 root 1.222 }
3289    
3290 root 1.265 sub commit {
3291     my ($self) = @_;
3292     my ($mod, $sym, $cmds) = $self->get_binding;
3293     if ($sym != 0 && @$cmds > 0) {
3294     $::STATUSBOX->add ("Bound actions to '".CFClient::Binder::keycombo_to_name ($mod, $sym)
3295     ."'. Don't forget 'Save Config'!");
3296     $self->{binding_change}->($mod, $sym, $cmds)
3297     if $self->{binding_change};
3298     } else {
3299     $::STATUSBOX->add ("No action bound, no key or action specified!");
3300     $self->{binding_cancel}->()
3301     if $self->{binding_cancel};
3302 root 1.222 }
3303 root 1.51 }
3304    
3305 root 1.265 sub start {
3306     my ($self) = @_;
3307 root 1.107
3308 root 1.265 $self->{rec_btn}->set_text ("stop recording");
3309     $self->{recording} = 1;
3310     $self->clear_command_list;
3311     $::CONN->start_record if $::CONN;
3312 root 1.107 }
3313    
3314 root 1.265 sub stop {
3315 root 1.51 my ($self) = @_;
3316    
3317 root 1.265 $self->{rec_btn}->set_text ("start recording");
3318     $self->{recording} = 0;
3319 root 1.198
3320 root 1.265 my $rec;
3321     $rec = $::CONN->stop_record if $::CONN;
3322     return unless ref $rec eq 'ARRAY';
3323     $self->set_command_list ($rec);
3324     }
3325 root 1.191
3326 elmex 1.270
3327     sub ask_for_bind_and_commit {
3328     my ($self) = @_;
3329     $self->ask_for_bind (1);
3330     }
3331    
3332 root 1.265 sub ask_for_bind {
3333     my ($self, $commit) = @_;
3334 root 1.243
3335 root 1.265 CFClient::Binder::open_binding_dialog (sub {
3336     my ($mod, $sym) = @_;
3337     $self->{binding} = [$mod, $sym]; # XXX: how to stop that memleak?
3338     $self->update_binding_widgets;
3339     $self->commit if $commit;
3340     });
3341     }
3342 root 1.259
3343 root 1.265 # $mod and $sym are the modifiers and key symbol
3344     # $cmds is a array ref of strings (the commands)
3345     # $cb is the callback that is executed on OK
3346     # $ccb is the callback that is executed on CANCEL and
3347     # when the binding was unsuccessful on OK
3348     sub set_binding {
3349     my ($self, $mod, $sym, $cmds, $cb, $ccb) = @_;
3350 root 1.191
3351 root 1.265 $self->clear_command_list;
3352     $self->{recording} = 0;
3353     $self->{rec_btn}->set_text ("start recording");
3354 root 1.243
3355 root 1.265 $self->{binding} = [$mod, $sym];
3356     $self->{commands} = $cmds;
3357 root 1.191
3358 root 1.265 $self->{binding_change} = $cb;
3359     $self->{binding_cancel} = $ccb;
3360 root 1.256
3361 root 1.265 $self->update_binding_widgets;
3362     }
3363 root 1.257
3364 root 1.265 # this is a shortcut method that asks for a binding
3365     # and then just binds it.
3366     sub do_quick_binding {
3367     my ($self, $cmds) = @_;
3368     $self->set_binding (undef, undef, $cmds, sub {
3369     $::CFG->{bindings}->{$_[0]}->{$_[1]} = $_[2];
3370     });
3371     $self->ask_for_bind (1);
3372     }
3373 root 1.191
3374 root 1.265 sub update_binding_widgets {
3375     my ($self) = @_;
3376     my ($mod, $sym, $cmds) = $self->get_binding;
3377     $self->{keylbl}->set_text (CFClient::Binder::keycombo_to_name ($mod, $sym));
3378     $self->set_command_list ($cmds);
3379     }
3380 root 1.259
3381 root 1.265 sub get_binding {
3382     my ($self) = @_;
3383     return (
3384     $self->{binding}->[0],
3385     $self->{binding}->[1],
3386     [ grep { defined $_ } @{$self->{commands}} ]
3387     );
3388     }
3389 root 1.259
3390 root 1.265 sub clear_command_list {
3391     my ($self) = @_;
3392     $self->{cmdbox}->clear ();
3393     }
3394 root 1.191
3395 root 1.265 sub set_command_list {
3396     my ($self, $cmds) = @_;
3397 root 1.191
3398 root 1.265 $self->{cmdbox}->clear ();
3399     $self->{commands} = $cmds;
3400 root 1.250
3401 root 1.265 my $idx = 0;
3402 root 1.191
3403 root 1.265 for (@$cmds) {
3404     $self->{cmdbox}->add (my $hb = new CFClient::UI::HBox);
3405 root 1.256
3406 root 1.265 my $i = $idx;
3407     $hb->add (new CFClient::UI::Label text => $_);
3408     $hb->add (new CFClient::UI::Button
3409     text => "delete",
3410     tooltip => "Deletes the action from the record",
3411     on_activate => sub {
3412     $self->{cmdbox}->remove ($hb);
3413     $cmds->[$i] = undef;
3414     });
3415 root 1.256
3416 root 1.252
3417 root 1.265 $idx++
3418 root 1.107 }
3419 root 1.51 }
3420    
3421     #############################################################################
3422    
3423 root 1.264 package CFClient::UI::SpellList;
3424    
3425 root 1.273 our @ISA = CFClient::UI::Table::;
3426 root 1.264
3427     sub new {
3428     my $class = shift;
3429    
3430 root 1.272 my $self = $class->SUPER::new (
3431     binding => [],
3432     commands => [],
3433     @_,
3434     )
3435 root 1.264 }
3436    
3437     # XXX: Do sorting? Argl...
3438     sub add_spell {
3439     my ($self, $spell) = @_;
3440     $self->{spells}->{$spell->{name}} = $spell;
3441    
3442 root 1.273 $self->add (0, $self->{tbl_idx}, new CFClient::UI::Face
3443 root 1.264 face => $spell->{face},
3444     can_hover => 1,
3445     can_events => 1,
3446     tooltip => $spell->{message});
3447    
3448 root 1.273 $self->add (1, $self->{tbl_idx}, new CFClient::UI::Label
3449 root 1.264 text => $spell->{name},
3450     can_hover => 1,
3451     can_events => 1,
3452     tooltip => $spell->{message},
3453     expand => 1);
3454    
3455 root 1.273 $self->add (2, $self->{tbl_idx}, new CFClient::UI::Label
3456 root 1.264 text => (sprintf "lvl: %2d sp: %2d dmg: %2d",
3457     $spell->{level}, ($spell->{mana} || $spell->{grace}), $spell->{damage}),
3458     expand => 1);
3459    
3460 root 1.273 $self->add (3, $self->{tbl_idx}++, new CFClient::UI::Button
3461 root 1.264 text => "bind to key",
3462     on_activate => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) });
3463     }
3464    
3465     sub rebuild_spell_list {
3466     my ($self) = @_;
3467     $self->{tbl_idx} = 0;
3468     $self->add_spell ($_) for values %{$self->{spells}};
3469     }
3470    
3471     sub remove_spell {
3472     my ($self, $spell) = @_;
3473     delete $self->{spells}->{$spell->{name}};
3474     $self->rebuild_spell_list;
3475     }
3476    
3477     #############################################################################
3478    
3479 root 1.265 package CFClient::UI::Root;
3480    
3481     our @ISA = CFClient::UI::Container::;
3482 elmex 1.260
3483 root 1.265 use CFClient::OpenGL;
3484 elmex 1.260
3485     sub new {
3486     my $class = shift;
3487    
3488 root 1.265 my $self = $class->SUPER::new (
3489     visible => 1,
3490     @_,
3491     );
3492    
3493     Scalar::Util::weaken ($self->{root} = $self);
3494    
3495     $self
3496     }
3497    
3498     sub size_request {
3499     my ($self) = @_;
3500    
3501     ($self->{w}, $self->{h})
3502     }
3503 elmex 1.260
3504 root 1.265 sub _to_pixel {
3505     my ($coord, $size, $max) = @_;
3506 elmex 1.260
3507 root 1.265 $coord =
3508     $coord eq "center" ? ($max - $size) * 0.5
3509     : $coord eq "max" ? $max
3510     : $coord;
3511 elmex 1.260
3512 root 1.265 $coord = 0 if $coord < 0;
3513     $coord = $max - $size if $coord > $max - $size;
3514 elmex 1.260
3515 root 1.265 int $coord + 0.5
3516     }
3517 elmex 1.260
3518 root 1.265 sub size_allocate {
3519     my ($self, $w, $h) = @_;
3520 elmex 1.261
3521 root 1.265 for my $child ($self->children) {
3522     my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)};
3523 elmex 1.260
3524 root 1.265 $X = $child->{force_x} if exists $child->{force_x};
3525     $Y = $child->{force_y} if exists $child->{force_y};
3526 elmex 1.260
3527 root 1.265 $X = _to_pixel $X, $W, $self->{w};
3528     $Y = _to_pixel $Y, $H, $self->{h};
3529 elmex 1.260
3530 root 1.265 $child->configure ($X, $Y, $W, $H);
3531     }
3532 elmex 1.260 }
3533    
3534 root 1.265 sub coord2local {
3535     my ($self, $x, $y) = @_;
3536    
3537     ($x, $y)
3538 elmex 1.260 }
3539    
3540 root 1.265 sub coord2global {
3541     my ($self, $x, $y) = @_;
3542 elmex 1.260
3543 root 1.265 ($x, $y)
3544 elmex 1.260 }
3545    
3546 root 1.265 sub update {
3547 elmex 1.260 my ($self) = @_;
3548    
3549 root 1.265 $::WANT_REFRESH++;
3550     }
3551 elmex 1.260
3552 root 1.265 sub add {
3553     my ($self, @children) = @_;
3554 elmex 1.260
3555 root 1.265 $_->{is_toplevel} = 1
3556     for @children;
3557 elmex 1.260
3558 root 1.265 $self->SUPER::add (@children);
3559 elmex 1.260 }
3560    
3561 root 1.265 sub remove {
3562     my ($self, @children) = @_;
3563    
3564     $self->SUPER::remove (@children);
3565 elmex 1.260
3566 root 1.265 delete $self->{is_toplevel}
3567     for @children;
3568 elmex 1.260
3569 root 1.265 while (@children) {
3570     my $w = pop @children;
3571     push @children, $w->children;
3572     $w->set_invisible;
3573     }
3574     }
3575 elmex 1.260
3576 root 1.265 sub on_refresh {
3577     my ($self, $id, $cb) = @_;
3578 elmex 1.260
3579 root 1.265 $self->{refresh_hook}{$id} = $cb;
3580 elmex 1.260 }
3581    
3582 root 1.265 sub on_post_alloc {
3583     my ($self, $id, $cb) = @_;
3584    
3585     $self->{post_alloc_hook}{$id} = $cb;
3586 elmex 1.262 }
3587    
3588 root 1.265 sub draw {
3589 elmex 1.260 my ($self) = @_;
3590    
3591 root 1.265 while ($self->{refresh_hook}) {
3592     $_->()
3593     for values %{delete $self->{refresh_hook}};
3594     }
3595    
3596     if ($self->{realloc}) {
3597 root 1.266 my %queue;
3598 root 1.265 my @queue;
3599 root 1.266 my $widget;
3600 root 1.265
3601 root 1.266 outer:
3602 root 1.265 while () {
3603 root 1.266 if (my $realloc = delete $self->{realloc}) {
3604     for $widget (values %$realloc) {
3605     $widget->{visible} or next; # do not resize invisible widgets
3606 root 1.265
3607 root 1.266 $queue{$widget+0}++ and next; # duplicates are common
3608 root 1.265
3609 root 1.266 push @{ $queue[$widget->{visible}] }, $widget;
3610     }
3611 root 1.265 }
3612    
3613 root 1.266 while () {
3614     @queue or last outer;
3615    
3616     $widget = pop @{ $queue[-1] || [] }
3617     and last;
3618    
3619     pop @queue;
3620     }
3621 root 1.265
3622 root 1.266 delete $queue{$widget+0};
3623 root 1.265
3624     my ($w, $h) = $widget->size_request;
3625    
3626     $w = List::Util::max $widget->{min_w}, $w + $widget->{padding_x} * 2;
3627     $h = List::Util::max $widget->{min_h}, $h + $widget->{padding_y} * 2;
3628    
3629     $w = $widget->{force_w} if exists $widget->{force_w};
3630     $h = $widget->{force_h} if exists $widget->{force_h};
3631    
3632     if ($widget->{req_w} != $w || $widget->{req_h} != $h
3633     || delete $widget->{force_realloc}) {
3634     $widget->{req_w} = $w;
3635     $widget->{req_h} = $h;
3636    
3637     $self->{size_alloc}{$widget+0} = $widget;
3638    
3639     if (my $parent = $widget->{parent}) {
3640 root 1.266 $self->{realloc}{$parent+0} = $parent
3641     unless $queue{$parent+0};
3642    
3643 root 1.265 $parent->{force_size_alloc} = 1;
3644     $self->{size_alloc}{$parent+0} = $parent;
3645     }
3646     }
3647    
3648     delete $self->{realloc}{$widget+0};
3649     }
3650     }
3651 elmex 1.260
3652 root 1.265 while (my $size_alloc = delete $self->{size_alloc}) {
3653     my @queue = sort { $b->{visible} <=> $a->{visible} }
3654     values %$size_alloc;
3655 elmex 1.260
3656 root 1.265 while () {
3657     my $widget = pop @queue || last;
3658 elmex 1.260
3659 root 1.265 my ($w, $h) = @$widget{qw(alloc_w alloc_h)};
3660 elmex 1.260
3661 root 1.265 $w = 0 if $w < 0;
3662     $h = 0 if $h < 0;
3663 elmex 1.260
3664 root 1.265 $w = int $w + 0.5;
3665     $h = int $h + 0.5;
3666 elmex 1.260
3667 root 1.265 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) {
3668 root 1.266 $widget->{old_w} = $widget->{w};
3669     $widget->{old_h} = $widget->{h};
3670    
3671 root 1.265 $widget->{w} = $w;
3672     $widget->{h} = $h;
3673 elmex 1.260
3674 root 1.265 $widget->emit (size_allocate => $w, $h);
3675     }
3676     }
3677     }
3678 elmex 1.260
3679 root 1.265 while ($self->{post_alloc_hook}) {
3680     $_->()
3681     for values %{delete $self->{post_alloc_hook}};
3682 elmex 1.260 }
3683 root 1.265
3684    
3685     glViewport 0, 0, $::WIDTH, $::HEIGHT;
3686     glClearColor +($::CFG->{fow_intensity}) x 3, 1;
3687     glClear GL_COLOR_BUFFER_BIT;
3688    
3689     glMatrixMode GL_PROJECTION;
3690     glLoadIdentity;
3691     glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000;
3692     glMatrixMode GL_MODELVIEW;
3693     glLoadIdentity;
3694    
3695 root 1.267 {
3696     package CFClient::UI::Base;
3697    
3698     ($draw_x, $draw_y, $draw_w, $draw_h) =
3699     (0, 0, $self->{w}, $self->{h});
3700     }
3701    
3702 root 1.265 $self->_draw;
3703 elmex 1.260 }
3704    
3705 elmex 1.262 #############################################################################
3706    
3707 root 1.73 package CFClient::UI;
3708 root 1.51
3709 root 1.113 $ROOT = new CFClient::UI::Root;
3710 root 1.213 $TOOLTIP = new CFClient::UI::Tooltip z => 900;
3711 root 1.51
3712     1
3713 root 1.5