ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.268
Committed: Thu Jun 1 05:04:41 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.267: +10 -8 lines
Log Message:
fix rounding problem, improve draw rectangle test

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