ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.271
Committed: Fri Jun 2 22:13:47 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.270: +40 -7 lines
Log Message:
add boolean results to callbacks

File Contents

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