ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.313
Committed: Sat Jun 24 00:24:09 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.312: +3 -0 lines
Log Message:
minor fixes

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