ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.267
Committed: Thu Jun 1 04:10:29 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.266: +33 -8 lines
Log Message:
make an effort of not drawing widgets that are outside the drawing area

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