ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.283
Committed: Mon Jun 5 02:28:30 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.282: +5 -3 lines
Log Message:
optimise label widget some more

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