ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.308
Committed: Mon Jun 19 05:18:14 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.307: +4 -3 lines
Log Message:
*** empty log message ***

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