ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.307
Committed: Sun Jun 18 19:13:20 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.306: +1 -1 lines
Log Message:
fix lookat once more (or actually add a magic +1, as the server seems to do it somewhere, too)

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