ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.310
Committed: Fri Jun 23 20:28:20 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.309: +104 -58 lines
Log Message:
faster textviewer, embeddable widgets, no scroll-to-bottom for docviewer

File Contents

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