ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.311
Committed: Fri Jun 23 22:35:16 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.310: +19 -5 lines
Log Message:
fix textview colors, preliminary hypertext support

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