ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.269
Committed: Fri Jun 2 06:22:55 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.268: +41 -16 lines
Log Message:
add window titles back

File Contents

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