ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.312
Committed: Fri Jun 23 23:54:30 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.311: +6 -4 lines
Log Message:
hypertext for npc dialogs

File Contents

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