ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.282
Committed: Mon Jun 5 02:25:10 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.281: +26 -14 lines
Log Message:
fix stat window, optimise label widget a bit

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     delete $self->{req_h};
1548     $self->SUPER::realloc;
1549     }
1550    
1551 elmex 1.15 sub set_text {
1552     my ($self, $text) = @_;
1553 root 1.28
1554 root 1.173 return if $self->{text} eq "T$text";
1555     $self->{text} = "T$text";
1556    
1557 root 1.194 $self->{layout} = new CFClient::Layout if $self->{layout}->is_rgba;
1558 root 1.121 $self->{layout}->set_text ($text);
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.28
1575 root 1.251 $self->realloc;
1576 root 1.252 $self->update;
1577 elmex 1.15 }
1578    
1579 root 1.14 sub size_request {
1580     my ($self) = @_;
1581    
1582 root 1.282 if (exists $self->{req_h}) {
1583     @$self{qw(req_w req_h)}
1584     } else {
1585     $self->{layout}->set_font ($self->{font}) if $self->{font};
1586     $self->{layout}->set_width ($self->{max_w} || -1);
1587     $self->{layout}->set_ellipsise ($self->{ellipsise});
1588     $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1589     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1590    
1591     my ($w, $h) = $self->{layout}->size;
1592 root 1.121
1593 root 1.282 if (exists $self->{template}) {
1594     $self->{template}->set_font ($self->{font}) if $self->{font};
1595     $self->{template}->set_height ($self->{fontsize} * $::FONTSIZE);
1596 root 1.76
1597 root 1.282 my ($w2, $h2) = $self->{template}->size;
1598 root 1.141
1599 root 1.282 $w = List::Util::max $w, $w2;
1600     $h = List::Util::max $h, $h2;
1601     }
1602 root 1.141
1603 root 1.282 ($w, $h)
1604 root 1.141 }
1605 root 1.59 }
1606 root 1.51
1607 root 1.59 sub size_allocate {
1608 root 1.259 my ($self, $w, $h) = @_;
1609 root 1.68
1610 root 1.269 delete $self->{ox};
1611    
1612 root 1.264 delete $self->{texture}
1613 root 1.266 unless $w >= $self->{req_w} && $self->{old_w} >= $self->{req_w};
1614 root 1.14 }
1615    
1616 elmex 1.146 sub set_fontsize {
1617     my ($self, $fontsize) = @_;
1618    
1619     $self->{fontsize} = $fontsize;
1620 root 1.152 delete $self->{texture};
1621 root 1.186
1622 root 1.251 $self->realloc;
1623 elmex 1.146 }
1624    
1625 elmex 1.11 sub _draw {
1626 root 1.10 my ($self) = @_;
1627    
1628 root 1.209 $self->SUPER::_draw; # draw background, if applicable
1629    
1630 root 1.59 my $tex = $self->{texture} ||= do {
1631 root 1.194 $self->{layout}->set_foreground (@{$self->{fg}});
1632 root 1.157 $self->{layout}->set_font ($self->{font}) if $self->{font};
1633 root 1.59 $self->{layout}->set_width ($self->{w});
1634 root 1.213 $self->{layout}->set_ellipsise ($self->{ellipsise});
1635     $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1636     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1637 root 1.194
1638 root 1.269 new_from_layout CFClient::Texture $self->{layout}
1639     };
1640 root 1.194
1641 root 1.269 unless (exists $self->{ox}) {
1642 root 1.258 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x}
1643     : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding_x}
1644 root 1.208 : ($self->{w} - $tex->{w}) * 0.5);
1645    
1646 root 1.258 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y}
1647     : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding_y}
1648 root 1.208 : ($self->{h} - $tex->{h}) * 0.5);
1649 root 1.59 };
1650 root 1.10
1651     glEnable GL_TEXTURE_2D;
1652 root 1.105 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1653 root 1.10
1654 root 1.279 glColor_premultiply @{$self->{fg}}
1655     if $tex->{format} == GL_ALPHA;
1656    
1657     $tex->draw_quad_alpha_premultiplied ($self->{ox}, $self->{oy});
1658 root 1.10
1659 root 1.74 glDisable GL_TEXTURE_2D;
1660 root 1.10 }
1661    
1662 root 1.39 #############################################################################
1663    
1664 root 1.121 package CFClient::UI::EntryBase;
1665 elmex 1.31
1666 root 1.73 our @ISA = CFClient::UI::Label::;
1667 elmex 1.31
1668 root 1.138 use CFClient::OpenGL;
1669 elmex 1.31
1670 root 1.68 sub new {
1671     my $class = shift;
1672    
1673     $class->SUPER::new (
1674 root 1.164 fg => [1, 1, 1],
1675     bg => [0, 0, 0, 0.2],
1676     active_bg => [1, 1, 1, 0.5],
1677     active_fg => [0, 0, 0],
1678     can_hover => 1,
1679     can_focus => 1,
1680     valign => 0,
1681 elmex 1.150 can_events => 1,
1682 root 1.225 #text => ...
1683 root 1.68 @_
1684     )
1685     }
1686    
1687     sub _set_text {
1688     my ($self, $text) = @_;
1689    
1690 root 1.121 delete $self->{cur_h};
1691    
1692     return if $self->{text} eq $text;
1693 elmex 1.100
1694 root 1.68 $self->{last_activity} = $::NOW;
1695     $self->{text} = $text;
1696 root 1.72
1697     $text =~ s/./*/g if $self->{hidden};
1698 root 1.121 $self->{layout}->set_text ("$text ");
1699 root 1.72
1700 root 1.231 $self->_emit (changed => $self->{text});
1701 root 1.276 $self->update;
1702 root 1.121 }
1703 root 1.68
1704 root 1.194 sub set_text {
1705     my ($self, $text) = @_;
1706    
1707     $self->{cursor} = length $text;
1708     $self->_set_text ($text);
1709 root 1.251
1710     $self->realloc;
1711 root 1.194 }
1712    
1713 root 1.121 sub get_text {
1714     $_[0]{text}
1715 root 1.68 }
1716    
1717     sub size_request {
1718     my ($self) = @_;
1719    
1720     my ($w, $h) = $self->SUPER::size_request;
1721    
1722     ($w + 1, $h) # add 1 for cursor
1723     }
1724    
1725 elmex 1.31 sub key_down {
1726     my ($self, $ev) = @_;
1727    
1728 root 1.137 my $mod = $ev->{mod};
1729     my $sym = $ev->{sym};
1730     my $uni = $ev->{unicode};
1731 elmex 1.31
1732     my $text = $self->get_text;
1733    
1734 root 1.200 if ($uni == 8) {
1735 root 1.68 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1736 root 1.200 } elsif ($uni == 127) {
1737 root 1.68 substr $text, $self->{cursor}, 1, "";
1738 root 1.136 } elsif ($sym == CFClient::SDLK_LEFT) {
1739 root 1.68 --$self->{cursor} if $self->{cursor};
1740 root 1.136 } elsif ($sym == CFClient::SDLK_RIGHT) {
1741 root 1.68 ++$self->{cursor} if $self->{cursor} < length $self->{text};
1742 root 1.136 } elsif ($sym == CFClient::SDLK_HOME) {
1743 root 1.76 $self->{cursor} = 0;
1744 root 1.136 } elsif ($sym == CFClient::SDLK_END) {
1745 root 1.76 $self->{cursor} = length $text;
1746 root 1.200 } elsif ($uni == 27) {
1747 root 1.231 $self->_emit ('escape');
1748 elmex 1.31 } elsif ($uni) {
1749 root 1.68 substr $text, $self->{cursor}++, 0, chr $uni;
1750 root 1.271 } else {
1751     return 0;
1752 elmex 1.31 }
1753 root 1.51
1754 root 1.68 $self->_set_text ($text);
1755 root 1.251
1756     $self->realloc;
1757 root 1.271
1758     1
1759 root 1.68 }
1760    
1761     sub focus_in {
1762     my ($self) = @_;
1763    
1764     $self->{last_activity} = $::NOW;
1765    
1766     $self->SUPER::focus_in;
1767 elmex 1.31 }
1768    
1769 root 1.51 sub button_down {
1770 root 1.68 my ($self, $ev, $x, $y) = @_;
1771    
1772     $self->SUPER::button_down ($ev, $x, $y);
1773    
1774     my $idx = $self->{layout}->xy_to_index ($x, $y);
1775    
1776     # byte-index to char-index
1777 root 1.76 my $text = $self->{text};
1778 root 1.68 utf8::encode $text;
1779     $self->{cursor} = length substr $text, 0, $idx;
1780 root 1.51
1781 root 1.68 $self->_set_text ($self->{text});
1782     $self->update;
1783 root 1.271
1784     1
1785 root 1.51 }
1786    
1787 root 1.58 sub mouse_motion {
1788     my ($self, $ev, $x, $y) = @_;
1789 root 1.68 # printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d#
1790 root 1.271
1791     0
1792 root 1.58 }
1793    
1794 root 1.51 sub _draw {
1795     my ($self) = @_;
1796    
1797 root 1.68 local $self->{fg} = $self->{fg};
1798    
1799 root 1.51 if ($FOCUS == $self) {
1800 root 1.278 glColor_premultiply @{$self->{active_bg}};
1801 root 1.68 $self->{fg} = $self->{active_fg};
1802 root 1.51 } else {
1803 root 1.278 glColor_premultiply @{$self->{bg}};
1804 root 1.51 }
1805    
1806 root 1.76 glEnable GL_BLEND;
1807 root 1.278 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
1808 root 1.51 glBegin GL_QUADS;
1809 root 1.68 glVertex 0 , 0;
1810     glVertex 0 , $self->{h};
1811     glVertex $self->{w}, $self->{h};
1812     glVertex $self->{w}, 0;
1813 root 1.51 glEnd;
1814 root 1.76 glDisable GL_BLEND;
1815 root 1.51
1816     $self->SUPER::_draw;
1817 root 1.68
1818     #TODO: force update every cursor change :(
1819     if ($FOCUS == $self && (($::NOW - $self->{last_activity}) & 1023) < 600) {
1820 root 1.121
1821     unless (exists $self->{cur_h}) {
1822     my $text = substr $self->{text}, 0, $self->{cursor};
1823     utf8::encode $text;
1824    
1825     @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text)
1826     }
1827    
1828 root 1.68 glColor @{$self->{fg}};
1829     glBegin GL_LINES;
1830 root 1.122 glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy};
1831     glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy} + $self->{cur_h};
1832 root 1.68 glEnd;
1833     }
1834     }
1835    
1836 root 1.121 package CFClient::UI::Entry;
1837 elmex 1.99
1838 root 1.121 our @ISA = CFClient::UI::EntryBase::;
1839 elmex 1.99
1840 root 1.138 use CFClient::OpenGL;
1841 elmex 1.99
1842     sub key_down {
1843     my ($self, $ev) = @_;
1844    
1845 root 1.137 my $sym = $ev->{sym};
1846 elmex 1.99
1847 root 1.136 if ($sym == 13) {
1848 elmex 1.167 unshift @{$self->{history}},
1849     my $txt = $self->get_text;
1850     $self->{history_pointer} = -1;
1851 elmex 1.169 $self->{history_saveback} = '';
1852 root 1.231 $self->_emit (activate => $txt);
1853 elmex 1.99 $self->update;
1854    
1855 elmex 1.167 } elsif ($sym == CFClient::SDLK_UP) {
1856     if ($self->{history_pointer} < 0) {
1857     $self->{history_saveback} = $self->get_text;
1858     }
1859 elmex 1.169 if (@{$self->{history} || []} > 0) {
1860     $self->{history_pointer}++;
1861     if ($self->{history_pointer} >= @{$self->{history} || []}) {
1862     $self->{history_pointer} = @{$self->{history} || []} - 1;
1863     }
1864     $self->set_text ($self->{history}->[$self->{history_pointer}]);
1865 elmex 1.167 }
1866    
1867     } elsif ($sym == CFClient::SDLK_DOWN) {
1868     $self->{history_pointer}--;
1869     $self->{history_pointer} = -1 if $self->{history_pointer} < 0;
1870    
1871     if ($self->{history_pointer} >= 0) {
1872     $self->set_text ($self->{history}->[$self->{history_pointer}]);
1873     } else {
1874     $self->set_text ($self->{history_saveback});
1875     }
1876    
1877 elmex 1.99 } else {
1878 root 1.271 return $self->SUPER::key_down ($ev)
1879 elmex 1.99 }
1880    
1881 root 1.271 1
1882 elmex 1.99 }
1883    
1884 root 1.68 #############################################################################
1885    
1886 root 1.79 package CFClient::UI::Button;
1887    
1888     our @ISA = CFClient::UI::Label::;
1889    
1890 root 1.138 use CFClient::OpenGL;
1891 root 1.79
1892 elmex 1.85 my @tex =
1893 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1894 elmex 1.85 qw(b1_button_active.png);
1895    
1896 root 1.79 sub new {
1897     my $class = shift;
1898    
1899     $class->SUPER::new (
1900 root 1.258 padding_x => 4,
1901     padding_y => 4,
1902 root 1.164 fg => [1, 1, 1],
1903     active_fg => [0, 0, 1],
1904     can_hover => 1,
1905     align => 0,
1906     valign => 0,
1907 elmex 1.150 can_events => 1,
1908 root 1.79 @_
1909     )
1910     }
1911    
1912 root 1.231 sub activate { }
1913    
1914 root 1.79 sub button_up {
1915     my ($self, $ev, $x, $y) = @_;
1916    
1917 root 1.231 $self->emit ("activate")
1918     if $x >= 0 && $x < $self->{w}
1919     && $y >= 0 && $y < $self->{h};
1920 root 1.271
1921     1
1922 root 1.79 }
1923    
1924     sub _draw {
1925     my ($self) = @_;
1926    
1927 root 1.279 local $self->{fg} = $GRAB == $self ? $self->{active_fg} : $self->{fg};
1928 root 1.79
1929 root 1.119 glEnable GL_TEXTURE_2D;
1930 elmex 1.85 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1931 root 1.119 glColor 0, 0, 0, 1;
1932 elmex 1.85
1933 root 1.195 $tex[0]->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
1934 elmex 1.85
1935     glDisable GL_TEXTURE_2D;
1936 root 1.79
1937     $self->SUPER::_draw;
1938     }
1939    
1940     #############################################################################
1941    
1942 root 1.86 package CFClient::UI::CheckBox;
1943    
1944     our @ISA = CFClient::UI::DrawBG::;
1945    
1946 elmex 1.102 my @tex =
1947 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1948 elmex 1.102 qw(c1_checkbox_bg.png c1_checkbox_active.png);
1949    
1950 root 1.138 use CFClient::OpenGL;
1951 root 1.86
1952     sub new {
1953     my $class = shift;
1954    
1955     $class->SUPER::new (
1956 root 1.258 padding_x => 2,
1957     padding_y => 2,
1958 root 1.86 fg => [1, 1, 1],
1959     active_fg => [1, 1, 0],
1960 root 1.209 bg => [0, 0, 0, 0.2],
1961     active_bg => [1, 1, 1, 0.5],
1962 root 1.86 state => 0,
1963 root 1.97 can_hover => 1,
1964 root 1.86 @_
1965     )
1966     }
1967    
1968 root 1.87 sub size_request {
1969     my ($self) = @_;
1970    
1971 root 1.258 (6) x 2
1972 root 1.87 }
1973    
1974 root 1.86 sub button_down {
1975     my ($self, $ev, $x, $y) = @_;
1976    
1977 root 1.258 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
1978     && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
1979 root 1.86 $self->{state} = !$self->{state};
1980 root 1.231 $self->_emit (changed => $self->{state});
1981 root 1.271 } else {
1982     return 0
1983 root 1.86 }
1984 root 1.271
1985     1
1986 root 1.86 }
1987    
1988     sub _draw {
1989     my ($self) = @_;
1990    
1991 root 1.87 $self->SUPER::_draw;
1992 root 1.86
1993 root 1.258 glTranslate $self->{padding_x} + 0.375, $self->{padding_y} + 0.375, 0;
1994 root 1.86
1995 root 1.258 my ($w, $h) = @$self{qw(w h)};
1996    
1997     my $s = List::Util::min $w - $self->{padding_x} * 2, $h - $self->{padding_y} * 2;
1998 elmex 1.102
1999 root 1.87 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };
2000 root 1.86
2001 elmex 1.102 my $tex = $self->{state} ? $tex[1] : $tex[0];
2002    
2003 root 1.197 glEnable GL_TEXTURE_2D;
2004 root 1.195 $tex->draw_quad_alpha (0, 0, $s, $s);
2005 elmex 1.102 glDisable GL_TEXTURE_2D;
2006 root 1.86 }
2007    
2008     #############################################################################
2009    
2010 elmex 1.145 package CFClient::UI::Image;
2011    
2012     our @ISA = CFClient::UI::Base::;
2013    
2014     use CFClient::OpenGL;
2015     use Carp qw/confess/;
2016    
2017     our %loaded_images;
2018    
2019     sub new {
2020     my $class = shift;
2021    
2022 elmex 1.150 my $self = $class->SUPER::new (can_events => 0, @_);
2023 elmex 1.145
2024     $self->{image} or confess "Image has 'image' not set. This is a fatal error!";
2025    
2026     $loaded_images{$self->{image}} ||=
2027     new_from_file CFClient::Texture CFClient::find_rcfile $self->{image}, mipmap => 1;
2028    
2029     my $tex = $self->{tex} = $loaded_images{$self->{image}};
2030    
2031 root 1.147 Scalar::Util::weaken $loaded_images{$self->{image}};
2032    
2033 elmex 1.145 $self->{aspect} = $tex->{w} / $tex->{h};
2034    
2035     $self
2036     }
2037    
2038     sub size_request {
2039     my ($self) = @_;
2040    
2041     ($self->{tex}->{w}, $self->{tex}->{h})
2042     }
2043    
2044     sub _draw {
2045     my ($self) = @_;
2046    
2047     my $tex = $self->{tex};
2048    
2049     my ($w, $h) = ($self->{w}, $self->{h});
2050    
2051     if ($self->{rot90}) {
2052     glRotate 90, 0, 0, 1;
2053     glTranslate 0, -$self->{w}, 0;
2054    
2055     ($w, $h) = ($h, $w);
2056     }
2057    
2058     glEnable GL_TEXTURE_2D;
2059     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2060    
2061 root 1.195 $tex->draw_quad_alpha (0, 0, $w, $h);
2062 elmex 1.145
2063     glDisable GL_TEXTURE_2D;
2064     }
2065    
2066     #############################################################################
2067    
2068 elmex 1.124 package CFClient::UI::VGauge;
2069    
2070     our @ISA = CFClient::UI::Base::;
2071    
2072 root 1.158 use List::Util qw(min max);
2073    
2074 root 1.138 use CFClient::OpenGL;
2075 elmex 1.124
2076     my %tex = (
2077     food => [
2078 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2079 elmex 1.124 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/
2080     ],
2081     grace => [
2082 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2083 root 1.158 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/
2084 elmex 1.124 ],
2085     hp => [
2086 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2087 elmex 1.124 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/
2088     ],
2089     mana => [
2090 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2091 root 1.158 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/
2092 elmex 1.124 ],
2093     );
2094    
2095     # eg. VGauge->new (gauge => 'food'), default gauge: food
2096     sub new {
2097     my $class = shift;
2098    
2099 root 1.140 my $self = $class->SUPER::new (
2100 root 1.141 type => 'food',
2101 root 1.140 @_
2102     );
2103    
2104 root 1.141 $self->{aspect} = $tex{$self->{type}}[0]{w} / $tex{$self->{type}}[0]{h};
2105 elmex 1.124
2106     $self
2107     }
2108    
2109     sub size_request {
2110     my ($self) = @_;
2111    
2112 root 1.143 #my $tex = $tex{$self->{type}}[0];
2113     #@$tex{qw(w h)}
2114     (0, 0)
2115 elmex 1.124 }
2116    
2117     sub set_max {
2118     my ($self, $max) = @_;
2119 root 1.127
2120 root 1.173 return if $self->{max_val} == $max;
2121    
2122 elmex 1.124 $self->{max_val} = $max;
2123 root 1.173 $self->update;
2124 elmex 1.124 }
2125    
2126     sub set_value {
2127     my ($self, $val, $max) = @_;
2128    
2129     $self->set_max ($max)
2130     if defined $max;
2131    
2132 root 1.173 return if $self->{val} == $val;
2133    
2134 elmex 1.124 $self->{val} = $val;
2135     $self->update;
2136     }
2137    
2138     sub _draw {
2139     my ($self) = @_;
2140    
2141 root 1.141 my $tex = $tex{$self->{type}};
2142 root 1.158 my ($t1, $t2, $t3) = @$tex;
2143 elmex 1.124
2144     my ($w, $h) = ($self->{w}, $self->{h});
2145    
2146 elmex 1.142 if ($self->{vertical}) {
2147     glRotate 90, 0, 0, 1;
2148     glTranslate 0, -$self->{w}, 0;
2149    
2150     ($w, $h) = ($h, $w);
2151     }
2152    
2153 elmex 1.124 my $ycut = $self->{val} / ($self->{max_val} || 1);
2154    
2155 root 1.158 my $ycut1 = max 0, min 1, $ycut;
2156     my $ycut2 = max 0, min 1, $ycut - 1;
2157    
2158     my $h1 = $self->{h} * (1 - $ycut1);
2159     my $h2 = $self->{h} * (1 - $ycut2);
2160 elmex 1.124
2161     glEnable GL_BLEND;
2162 root 1.278 glBlendFuncSeparate GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
2163     GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2164 elmex 1.124 glEnable GL_TEXTURE_2D;
2165     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2166    
2167 root 1.131 glBindTexture GL_TEXTURE_2D, $t1->{name};
2168     glBegin GL_QUADS;
2169 root 1.158 glTexCoord 0 , 0; glVertex 0 , 0;
2170     glTexCoord 0 , $t1->{t} * (1 - $ycut1); glVertex 0 , $h1;
2171     glTexCoord $t1->{s}, $t1->{t} * (1 - $ycut1); glVertex $w, $h1;
2172     glTexCoord $t1->{s}, 0; glVertex $w, 0;
2173 root 1.131 glEnd;
2174 elmex 1.124
2175 root 1.158 my $ycut1 = List::Util::min 1, $ycut;
2176 root 1.131 glBindTexture GL_TEXTURE_2D, $t2->{name};
2177     glBegin GL_QUADS;
2178 root 1.158 glTexCoord 0 , $t2->{t} * (1 - $ycut1); glVertex 0 , $h1;
2179     glTexCoord 0 , $t2->{t} * (1 - $ycut2); glVertex 0 , $h2;
2180     glTexCoord $t2->{s}, $t2->{t} * (1 - $ycut2); glVertex $w, $h2;
2181     glTexCoord $t2->{s}, $t2->{t} * (1 - $ycut1); glVertex $w, $h1;
2182 root 1.131 glEnd;
2183 elmex 1.124
2184 root 1.158 if ($t3) {
2185     glBindTexture GL_TEXTURE_2D, $t3->{name};
2186     glBegin GL_QUADS;
2187     glTexCoord 0 , $t3->{t} * (1 - $ycut2); glVertex 0 , $h2;
2188     glTexCoord 0 , $t3->{t}; glVertex 0 , $self->{h};
2189     glTexCoord $t3->{s}, $t3->{t}; glVertex $w, $self->{h};
2190     glTexCoord $t3->{s}, $t3->{t} * (1 - $ycut2); glVertex $w, $h2;
2191     glEnd;
2192     }
2193    
2194 elmex 1.124 glDisable GL_BLEND;
2195     glDisable GL_TEXTURE_2D;
2196     }
2197    
2198     #############################################################################
2199    
2200 root 1.141 package CFClient::UI::Gauge;
2201    
2202     our @ISA = CFClient::UI::VBox::;
2203    
2204     sub new {
2205 root 1.151 my ($class, %arg) = @_;
2206 root 1.141
2207     my $self = $class->SUPER::new (
2208 root 1.171 tooltip => $arg{type},
2209     can_hover => 1,
2210     can_events => 1,
2211 root 1.151 %arg,
2212 root 1.141 );
2213    
2214 root 1.161 $self->add ($self->{value} = new CFClient::UI::Label valign => +1, align => 0, template => "999");
2215     $self->add ($self->{gauge} = new CFClient::UI::VGauge type => $self->{type}, expand => 1, can_hover => 1);
2216     $self->add ($self->{max} = new CFClient::UI::Label valign => -1, align => 0, template => "999");
2217 root 1.141
2218     $self
2219     }
2220    
2221 elmex 1.146 sub set_fontsize {
2222     my ($self, $fsize) = @_;
2223    
2224     $self->{value}->set_fontsize ($fsize);
2225     $self->{max} ->set_fontsize ($fsize);
2226     }
2227    
2228 root 1.173 sub set_max {
2229     my ($self, $max) = @_;
2230    
2231     $self->{gauge}->set_max ($max);
2232     $self->{max}->set_text ($max);
2233     }
2234    
2235 root 1.141 sub set_value {
2236     my ($self, $val, $max) = @_;
2237    
2238     $self->set_max ($max)
2239     if defined $max;
2240    
2241     $self->{gauge}->set_value ($val, $max);
2242     $self->{value}->set_text ($val);
2243     }
2244    
2245     #############################################################################
2246    
2247 root 1.73 package CFClient::UI::Slider;
2248 root 1.68
2249     use strict;
2250    
2251 root 1.138 use CFClient::OpenGL;
2252 root 1.68
2253 root 1.73 our @ISA = CFClient::UI::DrawBG::;
2254 root 1.68
2255 elmex 1.99 my @tex =
2256     map { new_from_file CFClient::Texture CFClient::find_rcfile $_ }
2257     qw(s1_slider.png s1_slider_bg.png);
2258    
2259 root 1.68 sub new {
2260     my $class = shift;
2261    
2262 root 1.206 # range [value, low, high, page, unit]
2263 root 1.68
2264 root 1.97 # TODO: 0-width page
2265     # TODO: req_w/h are wrong with vertical
2266     # TODO: calculations are off
2267 root 1.76 my $self = $class->SUPER::new (
2268 root 1.68 fg => [1, 1, 1],
2269     active_fg => [0, 0, 0],
2270 root 1.209 bg => [0, 0, 0, 0.2],
2271     active_bg => [1, 1, 1, 0.5],
2272 root 1.227 range => [0, 0, 100, 10, 0],
2273 root 1.257 min_w => $::WIDTH / 80,
2274     min_h => $::WIDTH / 80,
2275 root 1.76 vertical => 0,
2276 root 1.97 can_hover => 1,
2277 root 1.217 inner_pad => 0.02,
2278 root 1.68 @_
2279 root 1.76 );
2280    
2281 root 1.206 $self->set_value ($self->{range}[0]);
2282     $self->update;
2283    
2284 root 1.76 $self
2285     }
2286    
2287 root 1.251 sub changed { }
2288    
2289 root 1.225 sub set_range {
2290     my ($self, $range) = @_;
2291    
2292 root 1.239 ($range, $self->{range}) = ($self->{range}, $range);
2293 root 1.225
2294 root 1.239 $self->update
2295     if "@$range" ne "@{$self->{range}}";
2296 root 1.225 }
2297    
2298 root 1.206 sub set_value {
2299     my ($self, $value) = @_;
2300    
2301     my ($old_value, $lo, $hi, $page, $unit) = @{$self->{range}};
2302    
2303     $hi = $lo + 1 if $hi <= $lo;
2304    
2305 root 1.227 $page = $hi - $lo if $page > $hi - $lo;
2306    
2307     $value = $lo if $value < $lo;
2308     $value = $hi - $page if $value > $hi - $page;
2309 root 1.206
2310     $value = $lo + $unit * int +($value - $lo + $unit * 0.5) / $unit
2311     if $unit;
2312    
2313     @{$self->{range}} = ($value, $lo, $hi, $page, $unit);
2314    
2315     if ($value != $old_value) {
2316 root 1.231 $self->_emit (changed => $value);
2317 root 1.206 $self->update;
2318     }
2319     }
2320    
2321 root 1.76 sub size_request {
2322     my ($self) = @_;
2323    
2324 root 1.257 ($self->{req_w}, $self->{req_h})
2325 root 1.68 }
2326    
2327 root 1.69 sub button_down {
2328     my ($self, $ev, $x, $y) = @_;
2329    
2330     $self->SUPER::button_down ($ev, $x, $y);
2331 root 1.227
2332     $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x];
2333    
2334 root 1.271 $self->mouse_motion ($ev, $x, $y)
2335 root 1.69 }
2336    
2337     sub mouse_motion {
2338     my ($self, $ev, $x, $y) = @_;
2339    
2340     if ($GRAB == $self) {
2341 root 1.71 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w});
2342    
2343 root 1.206 my (undef, $lo, $hi, $page) = @{$self->{range}};
2344 elmex 1.103
2345 root 1.227 $x = ($x - $self->{click}[1]) / ($w * $self->{scale});
2346 root 1.69
2347 root 1.227 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo));
2348 root 1.271 } else {
2349     return 0;
2350 root 1.69 }
2351 root 1.271
2352     1
2353 root 1.69 }
2354    
2355 root 1.206 sub update {
2356     my ($self) = @_;
2357    
2358 root 1.275 delete $self->{knob_w};
2359     $self->SUPER::update;
2360     }
2361    
2362     sub _draw {
2363     my ($self) = @_;
2364    
2365     unless ($self->{knob_w}) {
2366 root 1.206 $self->set_value ($self->{range}[0]);
2367    
2368     my ($value, $lo, $hi, $page) = @{$self->{range}};
2369 root 1.227 my $range = ($hi - $page - $lo) || 1e-100;
2370 root 1.206
2371 root 1.227 my $knob_w = List::Util::min 1, $page / ($hi - $lo) || 0.1;
2372 root 1.206
2373 root 1.227 $self->{offset} = List::Util::max $self->{inner_pad}, $knob_w * 0.5;
2374     $self->{scale} = 1 - 2 * $self->{offset} || 1e-100;
2375 root 1.206
2376 root 1.227 $value = ($value - $lo) / $range;
2377     $value = $value * $self->{scale} + $self->{offset};
2378 root 1.206
2379 root 1.227 $self->{knob_x} = $value - $knob_w * 0.5;
2380     $self->{knob_w} = $knob_w;
2381 root 1.275 }
2382 root 1.68
2383     $self->SUPER::_draw ();
2384    
2385 root 1.206 glScale $self->{w}, $self->{h};
2386 root 1.68
2387     if ($self->{vertical}) {
2388     # draw a vertical slider like a rotated horizontal slider
2389    
2390 root 1.214 glTranslate 1, 0, 0;
2391 root 1.68 glRotate 90, 0, 0, 1;
2392     }
2393    
2394     my $fg = $FOCUS == $self ? $self->{active_fg} : $self->{fg};
2395     my $bg = $FOCUS == $self ? $self->{active_bg} : $self->{bg};
2396    
2397 elmex 1.99 glEnable GL_TEXTURE_2D;
2398     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2399    
2400     # draw background
2401 root 1.206 $tex[1]->draw_quad_alpha (0, 0, 1, 1);
2402 root 1.69
2403 elmex 1.99 # draw handle
2404 root 1.206 $tex[0]->draw_quad_alpha ($self->{knob_x}, 0, $self->{knob_w}, 1);
2405 root 1.69
2406 elmex 1.99 glDisable GL_TEXTURE_2D;
2407 root 1.51 }
2408    
2409 root 1.39 #############################################################################
2410    
2411 root 1.225 package CFClient::UI::ValSlider;
2412    
2413     our @ISA = CFClient::UI::HBox::;
2414    
2415     sub new {
2416     my ($class, %arg) = @_;
2417    
2418     my $range = delete $arg{range};
2419    
2420     my $self = $class->SUPER::new (
2421     slider => (new CFClient::UI::Slider expand => 1, range => $range),
2422     entry => (new CFClient::UI::Label text => "", template => delete $arg{template}),
2423     to_value => sub { shift },
2424     from_value => sub { shift },
2425     %arg,
2426     );
2427    
2428     $self->{slider}->connect (changed => sub {
2429     my ($self, $value) = @_;
2430     $self->{parent}{entry}->set_text ($self->{parent}{to_value}->($value));
2431     $self->{parent}->emit (changed => $value);
2432     });
2433    
2434     # $self->{entry}->connect (changed => sub {
2435     # my ($self, $value) = @_;
2436     # $self->{parent}{slider}->set_value ($self->{parent}{from_value}->($value));
2437     # $self->{parent}->emit (changed => $value);
2438     # });
2439    
2440     $self->add ($self->{slider}, $self->{entry});
2441    
2442     $self->{slider}->emit (changed => $self->{slider}{range}[0]);
2443    
2444     $self
2445     }
2446    
2447     sub set_range { shift->{slider}->set_range (@_) }
2448     sub set_value { shift->{slider}->set_value (@_) }
2449    
2450     #############################################################################
2451    
2452 root 1.97 package CFClient::UI::TextView;
2453    
2454     our @ISA = CFClient::UI::HBox::;
2455    
2456 root 1.138 use CFClient::OpenGL;
2457 root 1.97
2458     sub new {
2459     my $class = shift;
2460    
2461     my $self = $class->SUPER::new (
2462 root 1.164 fontsize => 1,
2463     can_events => 0,
2464     #font => default_font
2465 root 1.105 @_,
2466 root 1.164
2467 root 1.195 layout => (new CFClient::Layout 1),
2468 root 1.164 par => [],
2469     height => 0,
2470     children => [
2471 root 1.97 (new CFClient::UI::Empty expand => 1),
2472     (new CFClient::UI::Slider vertical => 1),
2473     ],
2474     );
2475    
2476 root 1.176 $self->{children}[1]->connect (changed => sub { $self->update });
2477 root 1.107
2478 root 1.97 $self
2479     }
2480    
2481 root 1.107 sub set_fontsize {
2482     my ($self, $fontsize) = @_;
2483    
2484     $self->{fontsize} = $fontsize;
2485     $self->reflow;
2486     }
2487    
2488 root 1.220 sub size_allocate {
2489 root 1.259 my ($self, $w, $h) = @_;
2490 root 1.220
2491 root 1.259 $self->SUPER::size_allocate ($w, $h);
2492 root 1.220
2493     $self->{layout}->set_font ($self->{font}) if $self->{font};
2494     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
2495     $self->{layout}->set_width ($self->{children}[0]{w});
2496    
2497     $self->reflow;
2498     }
2499    
2500 root 1.228 sub text_size {
2501 root 1.220 my ($self, $text, $indent) = @_;
2502 root 1.105
2503     my $layout = $self->{layout};
2504    
2505 root 1.134 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2506 root 1.220 $layout->set_width ($self->{children}[0]{w} - $indent);
2507 root 1.195 $layout->set_markup ($text);
2508 root 1.105
2509 root 1.228 $layout->size
2510 root 1.105 }
2511    
2512     sub reflow {
2513     my ($self) = @_;
2514    
2515 root 1.107 $self->{need_reflow}++;
2516     $self->update;
2517 root 1.105 }
2518    
2519 root 1.227 sub set_offset {
2520     my ($self, $offset) = @_;
2521    
2522     # todo: base offset on lines or so, not on pixels
2523     $self->{children}[1]->set_value ($offset);
2524     }
2525    
2526 root 1.226 sub clear {
2527     my ($self) = @_;
2528    
2529     $self->{par} = [];
2530     $self->{height} = 0;
2531 root 1.227 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]);
2532 root 1.226 }
2533    
2534 root 1.97 sub add_paragraph {
2535 root 1.220 my ($self, $color, $text, $indent) = @_;
2536 root 1.97
2537 root 1.220 for my $line (split /\n/, $text) {
2538 root 1.228 my ($w, $h) = $self->text_size ($line);
2539     $self->{height} += $h;
2540     push @{$self->{par}}, [$w + $indent, $h, $color, $indent, $line];
2541 root 1.220 }
2542 root 1.105
2543 root 1.227 $self->{children}[1]->set_range ([$self->{height}, 0, $self->{height}, $self->{h}, 1]);
2544 root 1.97 }
2545    
2546 root 1.105 sub update {
2547 root 1.97 my ($self) = @_;
2548    
2549 root 1.105 $self->SUPER::update;
2550    
2551     return unless $self->{h} > 0;
2552    
2553 root 1.107 delete $self->{texture};
2554    
2555 root 1.198 $ROOT->on_post_alloc ($self, sub {
2556 root 1.228 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
2557    
2558 root 1.107 if (delete $self->{need_reflow}) {
2559     my $height = 0;
2560    
2561 root 1.228 my $layout = $self->{layout};
2562    
2563     $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2564    
2565     for (@{$self->{par}}) {
2566     if (1 || $_->[0] >= $W) { # TODO: works,but needs reconfigure etc. support
2567     $layout->set_width ($W - $_->[3]);
2568     $layout->set_markup ($_->[4]);
2569     my ($w, $h) = $layout->size;
2570     $_->[0] = $w + $_->[3];
2571     $_->[1] = $h;
2572     }
2573    
2574     $height += $_->[1];
2575     }
2576 root 1.107
2577     $self->{height} = $height;
2578    
2579 root 1.228 $self->{children}[1]->set_range ([$height, 0, $height, $H, 1]);
2580 root 1.107
2581     delete $self->{texture};
2582     }
2583    
2584 root 1.228 $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub {
2585 root 1.279 glClearColor 0, 0, 0, 0;
2586 root 1.107 glClear GL_COLOR_BUFFER_BIT;
2587    
2588     my $top = int $self->{children}[1]{range}[0];
2589 root 1.105
2590 root 1.107 my $y0 = $top;
2591 root 1.228 my $y1 = $top + $H;
2592 root 1.105
2593 root 1.107 my $y = 0;
2594 root 1.97
2595 root 1.107 my $layout = $self->{layout};
2596 root 1.97
2597 root 1.157 $layout->set_font ($self->{font}) if $self->{font};
2598    
2599 root 1.220 glEnable GL_BLEND;
2600 root 1.228 #TODO# not correct in windows where rgba is forced off
2601 root 1.220 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2602    
2603 root 1.107 for my $par (@{$self->{par}}) {
2604 root 1.228 my $h = $par->[1];
2605 root 1.97
2606 root 1.107 if ($y0 < $y + $h && $y < $y1) {
2607 root 1.228 $layout->set_foreground (@{ $par->[2] });
2608     $layout->set_width ($W - $par->[3]);
2609     $layout->set_markup ($par->[4]);
2610 root 1.220
2611     my ($w, $h, $data, $format, $internalformat) = $layout->render;
2612 root 1.105
2613 root 1.228 glRasterPos $par->[3], $y - $y0;
2614 root 1.220 glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data;
2615 root 1.107 }
2616    
2617     $y += $h;
2618 root 1.105 }
2619    
2620 root 1.220 glDisable GL_BLEND;
2621 root 1.107 };
2622     });
2623 root 1.105 }
2624 root 1.97
2625 root 1.105 sub _draw {
2626     my ($self) = @_;
2627 root 1.97
2628 root 1.176 glEnable GL_TEXTURE_2D;
2629     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2630 root 1.279 glColor 0, 0, 0, 1;
2631     $self->{texture}->draw_quad_alpha_premultiplied (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h});
2632 root 1.176 glDisable GL_TEXTURE_2D;
2633 root 1.97
2634 root 1.106 $self->{children}[1]->draw;
2635    
2636 root 1.97 }
2637    
2638     #############################################################################
2639    
2640 root 1.73 package CFClient::UI::Animator;
2641 root 1.35
2642 root 1.138 use CFClient::OpenGL;
2643 root 1.35
2644 root 1.73 our @ISA = CFClient::UI::Bin::;
2645 root 1.35
2646     sub moveto {
2647     my ($self, $x, $y) = @_;
2648    
2649     $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
2650 root 1.56 $self->{speed} = 0.001;
2651 root 1.35 $self->{time} = 1;
2652    
2653     ::animation_start $self;
2654     }
2655    
2656     sub animate {
2657     my ($self, $interval) = @_;
2658    
2659     $self->{time} -= $interval * $self->{speed};
2660     if ($self->{time} <= 0) {
2661     $self->{time} = 0;
2662     ::animation_stop $self;
2663     }
2664    
2665     my ($x0, $y0, $x1, $y1) = @{$self->{moveto}};
2666    
2667     $self->{x} = $x0 * $self->{time} + $x1 * (1 - $self->{time});
2668     $self->{y} = $y0 * $self->{time} + $y1 * (1 - $self->{time});
2669     }
2670    
2671     sub _draw {
2672     my ($self) = @_;
2673    
2674     glPushMatrix;
2675 root 1.51 glRotate $self->{time} * 1000, 0, 1, 0;
2676 root 1.38 $self->{children}[0]->draw;
2677 root 1.35 glPopMatrix;
2678     }
2679    
2680 root 1.51 #############################################################################
2681    
2682 root 1.96 package CFClient::UI::Flopper;
2683    
2684     our @ISA = CFClient::UI::Button::;
2685    
2686     sub new {
2687     my $class = shift;
2688    
2689     my $self = $class->SUPER::new (
2690 root 1.243 state => 0,
2691     on_activate => \&toggle_flopper,
2692 root 1.96 @_
2693     );
2694    
2695     $self
2696     }
2697    
2698     sub toggle_flopper {
2699     my ($self) = @_;
2700    
2701 elmex 1.245 $self->{other}->toggle_visibility;
2702 root 1.96 }
2703    
2704     #############################################################################
2705    
2706 root 1.153 package CFClient::UI::Tooltip;
2707    
2708     our @ISA = CFClient::UI::Bin::;
2709    
2710     use CFClient::OpenGL;
2711    
2712     sub new {
2713     my $class = shift;
2714    
2715     $class->SUPER::new (
2716     @_,
2717     can_events => 0,
2718     )
2719     }
2720    
2721 root 1.196 sub set_tooltip_from {
2722     my ($self, $widget) = @_;
2723 root 1.195
2724 root 1.259 my $tooltip = $widget->{tooltip};
2725    
2726     if ($ENV{CFPLUS_DEBUG} & 2) {
2727     $tooltip .= "\n\n" . (ref $widget) . "\n"
2728     . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n"
2729     . "req $widget->{req_w} $widget->{req_h}\n"
2730     . "visible $widget->{visible}";
2731     }
2732    
2733 root 1.197 $self->add (new CFClient::UI::Label
2734 root 1.259 markup => $tooltip,
2735 root 1.213 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH,
2736     fontsize => 0.8,
2737     fg => [0, 0, 0, 1],
2738     ellipsise => 0,
2739     font => ($widget->{tooltip_font} || $::FONT_PROP),
2740 root 1.197 );
2741 root 1.153 }
2742    
2743     sub size_request {
2744     my ($self) = @_;
2745    
2746     my ($w, $h) = @{$self->child}{qw(req_w req_h)};
2747    
2748 root 1.154 ($w + 4, $h + 4)
2749     }
2750    
2751 root 1.162 sub size_allocate {
2752 root 1.259 my ($self, $w, $h) = @_;
2753 root 1.162
2754 root 1.259 $self->SUPER::size_allocate ($w - 4, $h - 4);
2755 root 1.162 }
2756    
2757 root 1.253 sub visibility_change {
2758     my ($self, $visible) = @_;
2759    
2760     return unless $visible;
2761    
2762     $self->{root}->on_post_alloc ("move_$self" => sub {
2763 root 1.254 my $widget = $self->{owner}
2764     or return;
2765 root 1.253
2766     my ($x, $y) = $widget->coord2global ($widget->{w}, 0);
2767    
2768     ($x, $y) = $widget->coord2global (-$self->{w}, 0)
2769     if $x + $self->{w} > $::WIDTH;
2770    
2771 root 1.256 $self->move_abs ($x, $y);
2772 root 1.253 });
2773     }
2774    
2775 root 1.154 sub _draw {
2776     my ($self) = @_;
2777    
2778     glTranslate 0.375, 0.375;
2779    
2780     my ($w, $h) = @$self{qw(w h)};
2781    
2782     glColor 1, 0.8, 0.4;
2783     glBegin GL_QUADS;
2784     glVertex 0 , 0;
2785     glVertex 0 , $h;
2786     glVertex $w, $h;
2787     glVertex $w, 0;
2788     glEnd;
2789    
2790     glColor 0, 0, 0;
2791     glBegin GL_LINE_LOOP;
2792     glVertex 0 , 0;
2793     glVertex 0 , $h;
2794     glVertex $w, $h;
2795     glVertex $w, 0;
2796     glEnd;
2797    
2798 root 1.197 glTranslate 2 - 0.375, 2 - 0.375;
2799 root 1.252
2800 root 1.154 $self->SUPER::_draw;
2801 root 1.153 }
2802    
2803     #############################################################################
2804    
2805 root 1.162 package CFClient::UI::Face;
2806    
2807     our @ISA = CFClient::UI::Base::;
2808    
2809     use CFClient::OpenGL;
2810    
2811     sub new {
2812     my $class = shift;
2813    
2814 root 1.217 my $self = $class->SUPER::new (
2815 root 1.234 aspect => 1,
2816     can_events => 0,
2817 root 1.162 @_,
2818 root 1.217 );
2819    
2820     if ($self->{anim} && $self->{animspeed}) {
2821     Scalar::Util::weaken (my $widget = $self);
2822    
2823     $self->{timer} = Event->timer (
2824     at => $self->{animspeed} * int $::NOW / $self->{animspeed},
2825     hard => 1,
2826     interval => $self->{animspeed},
2827     cb => sub {
2828     ++$widget->{frame};
2829     $widget->update;
2830     },
2831     );
2832     }
2833    
2834     $self
2835 root 1.162 }
2836    
2837     sub size_request {
2838     (32, 8)
2839     }
2840    
2841 root 1.222 sub update {
2842     my ($self) = @_;
2843    
2844     return unless $self->{visible};
2845    
2846     $self->SUPER::update;
2847     }
2848    
2849 elmex 1.179 sub _draw {
2850 root 1.162 my ($self) = @_;
2851    
2852 root 1.227 return unless $::CONN;
2853 root 1.162
2854 root 1.217 my $face;
2855    
2856     if ($self->{frame}) {
2857     my $anim = $::CONN->{anim}[$self->{anim}];
2858    
2859     $face = $anim->[ $self->{frame} % @$anim ]
2860     if $anim && @$anim;
2861     }
2862    
2863     my $tex = $::CONN->{texture}[$::CONN->{faceid}[$face || $self->{face}]];
2864    
2865 root 1.162 if ($tex) {
2866     glEnable GL_TEXTURE_2D;
2867     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2868 root 1.279 glColor 0, 0, 0, 1;
2869 root 1.195 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2870 root 1.162 glDisable GL_TEXTURE_2D;
2871     }
2872     }
2873    
2874 root 1.217 sub DESTROY {
2875     my ($self) = @_;
2876    
2877     $self->{timer}->cancel
2878     if $self->{timer};
2879    
2880     $self->SUPER::DESTROY;
2881     }
2882    
2883 root 1.162 #############################################################################
2884    
2885 root 1.272 package CFClient::UI::Buttonbar;
2886    
2887     our @ISA = CFClient::UI::HBox::;
2888    
2889     # TODO: should actualyl wrap buttons and other goodies.
2890    
2891     #############################################################################
2892    
2893 root 1.178 package CFClient::UI::Menu;
2894    
2895     our @ISA = CFClient::UI::FancyFrame::;
2896    
2897     use CFClient::OpenGL;
2898    
2899     sub new {
2900     my $class = shift;
2901    
2902     my $self = $class->SUPER::new (
2903     items => [],
2904     z => 100,
2905     @_,
2906     );
2907    
2908     $self->add ($self->{vbox} = new CFClient::UI::VBox);
2909    
2910     for my $item (@{ $self->{items} }) {
2911     my ($widget, $cb) = @$item;
2912    
2913     # handle various types of items, only text for now
2914     if (!ref $widget) {
2915     $widget = new CFClient::UI::Label
2916     can_hover => 1,
2917     can_events => 1,
2918     text => $widget;
2919     }
2920    
2921     $self->{item}{$widget} = $item;
2922    
2923     $self->{vbox}->add ($widget);
2924     }
2925    
2926     $self
2927     }
2928    
2929     # popup given the event (must be a mouse button down event currently)
2930     sub popup {
2931     my ($self, $ev) = @_;
2932    
2933 root 1.231 $self->_emit ("popdown");
2934 root 1.178
2935     # maybe save $GRAB? must be careful about events...
2936     $GRAB = $self;
2937     $self->{button} = $ev->{button};
2938    
2939     $self->show;
2940 root 1.258 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5);
2941 root 1.178 }
2942    
2943     sub mouse_motion {
2944     my ($self, $ev, $x, $y) = @_;
2945    
2946 root 1.182 # TODO: should use vbox->find_widget or so
2947 root 1.178 $HOVER = $ROOT->find_widget ($ev->{x}, $ev->{y});
2948     $self->{hover} = $self->{item}{$HOVER};
2949 root 1.271
2950     0
2951 root 1.178 }
2952    
2953     sub button_up {
2954     my ($self, $ev, $x, $y) = @_;
2955    
2956     if ($ev->{button} == $self->{button}) {
2957     undef $GRAB;
2958     $self->hide;
2959    
2960 root 1.231 $self->_emit ("popdown");
2961 root 1.178 $self->{hover}[1]->() if $self->{hover};
2962 root 1.271 } else {
2963     return 0
2964 root 1.178 }
2965 root 1.271
2966     1
2967 root 1.178 }
2968    
2969     #############################################################################
2970    
2971 root 1.272 package CFClient::UI::Multiplexer;
2972    
2973     our @ISA = CFClient::UI::Container::;
2974    
2975     sub new {
2976     my $class = shift;
2977    
2978     my $self = $class->SUPER::new (
2979     @_,
2980     );
2981    
2982     $self->{current} = $self->{children}[0]
2983     if @{ $self->{children} };
2984    
2985     $self
2986     }
2987    
2988     sub add {
2989     my ($self, @widgets) = @_;
2990    
2991     $self->SUPER::add (@widgets);
2992    
2993     $self->{current} = $self->{children}[0]
2994     if @{ $self->{children} };
2995     }
2996    
2997     sub set_current_page {
2998     my ($self, $page_or_widget) = @_;
2999    
3000     my $widget = ref $page_or_widget
3001     ? $page_or_widget
3002     : $self->{children}[$page_or_widget];
3003    
3004     $self->{current} = $widget;
3005     $self->{current}->configure (0, 0, $self->{w}, $self->{h});
3006    
3007     $self->_emit (page_changed => $self->{current});
3008    
3009     $self->realloc;
3010     }
3011    
3012     sub visible_children {
3013     $_[0]{current}
3014     }
3015    
3016     sub size_request {
3017     my ($self) = @_;
3018    
3019     $self->{current}->size_request
3020     }
3021    
3022     sub size_allocate {
3023     my ($self, $w, $h) = @_;
3024    
3025     $self->{current}->configure (0, 0, $w, $h);
3026     }
3027    
3028     sub _draw {
3029     my ($self) = @_;
3030    
3031     $self->{current}->draw;
3032     }
3033    
3034     #############################################################################
3035    
3036     package CFClient::UI::Notebook;
3037    
3038     our @ISA = CFClient::UI::VBox::;
3039    
3040     sub new {
3041     my $class = shift;
3042    
3043     my $self = $class->SUPER::new (
3044     buttonbar => (new CFClient::UI::Buttonbar),
3045     multiplexer => (new CFClient::UI::Multiplexer expand => 1),
3046 root 1.273 # filter => # will be put between multiplexer and $self
3047 root 1.272 @_,
3048     );
3049 root 1.273
3050     $self->{filter}->add ($self->{multiplexer}) if $self->{filter};
3051     $self->SUPER::add ($self->{buttonbar}, $self->{filter} || $self->{multiplexer});
3052 root 1.272
3053     $self
3054     }
3055    
3056     sub add {
3057     my ($self, $title, $widget, $tooltip) = @_;
3058    
3059     Scalar::Util::weaken $self;
3060    
3061     $self->{buttonbar}->add (new CFClient::UI::Button
3062     markup => $title,
3063     tooltip => $tooltip,
3064     on_activate => sub { $self->set_current_page ($widget) },
3065     );
3066    
3067     $self->{multiplexer}->add ($widget);
3068     }
3069    
3070     sub set_current_page {
3071     my ($self, $page) = @_;
3072    
3073     $self->{multiplexer}->set_current_page ($page);
3074     $self->_emit (page_changed => $self->{multiplexer}{current});
3075     }
3076    
3077     #############################################################################
3078    
3079 root 1.194 package CFClient::UI::Statusbox;
3080    
3081     our @ISA = CFClient::UI::VBox::;
3082    
3083 root 1.210 sub new {
3084     my $class = shift;
3085    
3086 root 1.280 my $self = $class->SUPER::new (
3087 root 1.210 fontsize => 0.8,
3088     @_,
3089 root 1.280 );
3090    
3091     Scalar::Util::weaken (my $this = $self);
3092    
3093 root 1.281 $self->{timer} = Event->timer (after => 1, interval => 1, cb => sub { $this->reorder });
3094 root 1.280
3095     $self
3096 root 1.210 }
3097    
3098 root 1.194 sub reorder {
3099     my ($self) = @_;
3100 root 1.280 my $NOW = Time::HiRes::time;
3101 root 1.194
3102 root 1.281 # freeze display when hovering over any label
3103     return if $CFClient::UI::TOOLTIP->{owner}
3104     && grep $CFClient::UI::TOOLTIP->{owner} == $_->{label},
3105     values %{ $self->{item} };
3106    
3107 root 1.194 while (my ($k, $v) = each %{ $self->{item} }) {
3108     delete $self->{item}{$k} if $v->{timeout} < $NOW;
3109     }
3110    
3111     my @widgets;
3112 root 1.197
3113     my @items = sort {
3114     $a->{pri} <=> $b->{pri}
3115     or $b->{id} <=> $a->{id}
3116     } values %{ $self->{item} };
3117    
3118 root 1.280 $self->{timer}->interval (1);
3119    
3120 root 1.194 my $count = 10 + 1;
3121     for my $item (@items) {
3122     last unless --$count;
3123    
3124 root 1.281 my $label = $item->{label} ||= do {
3125 root 1.194 # TODO: doesn't handle markup well (read as: at all)
3126 root 1.197 my $short = $item->{count} > 1
3127     ? "<b>$item->{count} ×</b> $item->{text}"
3128     : $item->{text};
3129    
3130 root 1.194 for ($short) {
3131     s/^\s+//;
3132 root 1.205 s/\s+/ /g;
3133 root 1.194 }
3134    
3135     new CFClient::UI::Label
3136 root 1.196 markup => $short,
3137 root 1.197 tooltip => $item->{tooltip},
3138 root 1.196 tooltip_font => $::FONT_PROP,
3139 root 1.197 tooltip_width => 0.67,
3140 root 1.213 fontsize => $item->{fontsize} || $self->{fontsize},
3141     max_w => $::WIDTH * 0.44,
3142 root 1.281 fg => [@{ $item->{fg} }],
3143 root 1.196 can_events => 1,
3144 root 1.197 can_hover => 1
3145 root 1.194 };
3146 root 1.280
3147     if ((my $diff = $item->{timeout} - $NOW) < 2) {
3148 root 1.281 $label->{fg}[3] = ($item->{fg}[3] || 1) * $diff / 2;
3149     $label->update;
3150     $label->set_max_size (undef, $label->{req_h} * $diff)
3151     if $diff < 1;
3152 root 1.280 $self->{timer}->interval (1/30);
3153 root 1.281 } else {
3154     $label->{fg}[3] = $item->{fg}[3] || 1;
3155 root 1.280 }
3156 root 1.281
3157     push @widgets, $label;
3158 root 1.194 }
3159    
3160     $self->clear;
3161 root 1.197 $self->SUPER::add (reverse @widgets);
3162 root 1.194 }
3163    
3164     sub add {
3165     my ($self, $text, %arg) = @_;
3166    
3167 root 1.198 $text =~ s/^\s+//;
3168     $text =~ s/\s+$//;
3169    
3170 root 1.233 return unless $text;
3171    
3172 root 1.280 my $timeout = (int time) + ((delete $arg{timeout}) || 60);
3173 root 1.194
3174 root 1.197 my $group = exists $arg{group} ? $arg{group} : ++$self->{id};
3175 root 1.194
3176 root 1.197 if (my $item = $self->{item}{$group}) {
3177     if ($item->{text} eq $text) {
3178     $item->{count}++;
3179     } else {
3180     $item->{count} = 1;
3181     $item->{text} = $item->{tooltip} = $text;
3182     }
3183 root 1.198 $item->{id} = ++$self->{id};
3184 root 1.197 $item->{timeout} = $timeout;
3185     delete $item->{label};
3186     } else {
3187     $self->{item}{$group} = {
3188     id => ++$self->{id},
3189     text => $text,
3190     timeout => $timeout,
3191     tooltip => $text,
3192 root 1.205 fg => [0.8, 0.8, 0.8, 0.8],
3193 root 1.197 pri => 0,
3194     count => 1,
3195     %arg,
3196     };
3197     }
3198 root 1.194
3199     $self->reorder;
3200     }
3201    
3202 root 1.213 sub reconfigure {
3203     my ($self) = @_;
3204    
3205     delete $_->{label}
3206     for values %{ $self->{item} || {} };
3207    
3208     $self->reorder;
3209     $self->SUPER::reconfigure;
3210     }
3211    
3212 root 1.280 sub DESTROY {
3213     my ($self) = @_;
3214    
3215     $self->{timer}->cancel;
3216    
3217     $self->SUPER::DESTROY;
3218     }
3219    
3220 root 1.194 #############################################################################
3221    
3222 root 1.265 package CFClient::UI::Inventory;
3223 root 1.51
3224 root 1.265 our @ISA = CFClient::UI::ScrolledWindow::;
3225 root 1.107
3226 root 1.191 sub new {
3227     my $class = shift;
3228    
3229 root 1.251 my $self = $class->SUPER::new (
3230 root 1.273 child => (new CFClient::UI::Table col_expand => [0, 1, 0]),
3231 root 1.191 @_,
3232 root 1.251 );
3233    
3234     $self
3235 root 1.191 }
3236    
3237 root 1.265 sub set_items {
3238     my ($self, $items) = @_;
3239    
3240 root 1.273 $self->{child}->clear;
3241 root 1.265 return unless $items;
3242 root 1.186
3243 root 1.265 my @items = sort {
3244     ($a->{type} <=> $b->{type})
3245     or ($a->{name} cmp $b->{name})
3246     } @$items;
3247 root 1.186
3248 root 1.265 $self->{real_items} = \@items;
3249 root 1.256
3250 root 1.265 my $row = 0;
3251     for my $item (@items) {
3252     CFClient::Item::update_widgets $item;
3253 root 1.256
3254 root 1.273 $self->{child}->add (0, $row, $item->{face_widget});
3255     $self->{child}->add (1, $row, $item->{desc_widget});
3256     $self->{child}->add (2, $row, $item->{weight_widget});
3257 root 1.256
3258 root 1.265 $row++;
3259     }
3260 root 1.256 }
3261    
3262 root 1.265 #############################################################################
3263 root 1.186
3264 root 1.265 package CFClient::UI::BindEditor;
3265 root 1.149
3266 root 1.265 our @ISA = CFClient::UI::FancyFrame::;
3267 root 1.205
3268 root 1.265 sub new {
3269     my $class = shift;
3270 root 1.205
3271 root 1.265 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3272 root 1.191
3273 root 1.265 $self->add (my $vb = new CFClient::UI::VBox);
3274 root 1.191
3275 root 1.51
3276 root 1.265 $vb->add ($self->{rec_btn} = new CFClient::UI::Button
3277     text => "start recording",
3278     tooltip => "Start/Stops recording of actions."
3279     ."All subsequent actions after the recording started will be captured."
3280     ."The actions are displayed after the record was stopped."
3281     ."To bind the action you have to click on the 'Bind' button",
3282     on_activate => sub {
3283     unless ($self->{recording}) {
3284     $self->start;
3285     } else {
3286     $self->stop;
3287     }
3288     });
3289 root 1.58
3290 root 1.265 $vb->add (new CFClient::UI::Label text => "Actions:");
3291     $vb->add ($self->{cmdbox} = new CFClient::UI::VBox);
3292 root 1.58
3293 root 1.265 $vb->add (new CFClient::UI::Label text => "Bound to: ");
3294     $vb->add (my $hb = new CFClient::UI::HBox);
3295     $hb->add ($self->{keylbl} = new CFClient::UI::Label expand => 1);
3296     $hb->add (new CFClient::UI::Button
3297     text => "bind",
3298     tooltip => "This opens a query where you have to press the key combination to bind the recorded actions",
3299     on_activate => sub {
3300     $self->ask_for_bind;
3301     });
3302 root 1.51
3303 root 1.265 $vb->add (my $hb = new CFClient::UI::HBox);
3304     $hb->add (new CFClient::UI::Button
3305     text => "ok",
3306     expand => 1,
3307     tooltip => "This closes the binding editor and saves the binding",
3308     on_activate => sub {
3309     $self->hide;
3310     $self->commit;
3311     });
3312 root 1.51
3313 root 1.265 $hb->add (new CFClient::UI::Button
3314     text => "cancel",
3315     expand => 1,
3316     tooltip => "This closes the binding editor without saving",
3317     on_activate => sub {
3318     $self->hide;
3319     $self->{binding_cancel}->()
3320     if $self->{binding_cancel};
3321     });
3322 root 1.203
3323 root 1.265 $self->update_binding_widgets;
3324 elmex 1.146
3325 root 1.265 $self
3326 root 1.222 }
3327    
3328 root 1.265 sub commit {
3329     my ($self) = @_;
3330     my ($mod, $sym, $cmds) = $self->get_binding;
3331     if ($sym != 0 && @$cmds > 0) {
3332     $::STATUSBOX->add ("Bound actions to '".CFClient::Binder::keycombo_to_name ($mod, $sym)
3333     ."'. Don't forget 'Save Config'!");
3334     $self->{binding_change}->($mod, $sym, $cmds)
3335     if $self->{binding_change};
3336     } else {
3337     $::STATUSBOX->add ("No action bound, no key or action specified!");
3338     $self->{binding_cancel}->()
3339     if $self->{binding_cancel};
3340 root 1.222 }
3341 root 1.51 }
3342    
3343 root 1.265 sub start {
3344     my ($self) = @_;
3345 root 1.107
3346 root 1.265 $self->{rec_btn}->set_text ("stop recording");
3347     $self->{recording} = 1;
3348     $self->clear_command_list;
3349     $::CONN->start_record if $::CONN;
3350 root 1.107 }
3351    
3352 root 1.265 sub stop {
3353 root 1.51 my ($self) = @_;
3354    
3355 root 1.265 $self->{rec_btn}->set_text ("start recording");
3356     $self->{recording} = 0;
3357 root 1.198
3358 root 1.265 my $rec;
3359     $rec = $::CONN->stop_record if $::CONN;
3360     return unless ref $rec eq 'ARRAY';
3361     $self->set_command_list ($rec);
3362     }
3363 root 1.191
3364 elmex 1.270
3365     sub ask_for_bind_and_commit {
3366     my ($self) = @_;
3367     $self->ask_for_bind (1);
3368     }
3369    
3370 root 1.265 sub ask_for_bind {
3371     my ($self, $commit) = @_;
3372 root 1.243
3373 root 1.265 CFClient::Binder::open_binding_dialog (sub {
3374     my ($mod, $sym) = @_;
3375     $self->{binding} = [$mod, $sym]; # XXX: how to stop that memleak?
3376     $self->update_binding_widgets;
3377     $self->commit if $commit;
3378     });
3379     }
3380 root 1.259
3381 root 1.265 # $mod and $sym are the modifiers and key symbol
3382     # $cmds is a array ref of strings (the commands)
3383     # $cb is the callback that is executed on OK
3384     # $ccb is the callback that is executed on CANCEL and
3385     # when the binding was unsuccessful on OK
3386     sub set_binding {
3387     my ($self, $mod, $sym, $cmds, $cb, $ccb) = @_;
3388 root 1.191
3389 root 1.265 $self->clear_command_list;
3390     $self->{recording} = 0;
3391     $self->{rec_btn}->set_text ("start recording");
3392 root 1.243
3393 root 1.265 $self->{binding} = [$mod, $sym];
3394     $self->{commands} = $cmds;
3395 root 1.191
3396 root 1.265 $self->{binding_change} = $cb;
3397     $self->{binding_cancel} = $ccb;
3398 root 1.256
3399 root 1.265 $self->update_binding_widgets;
3400     }
3401 root 1.257
3402 root 1.265 # this is a shortcut method that asks for a binding
3403     # and then just binds it.
3404     sub do_quick_binding {
3405     my ($self, $cmds) = @_;
3406     $self->set_binding (undef, undef, $cmds, sub {
3407     $::CFG->{bindings}->{$_[0]}->{$_[1]} = $_[2];
3408     });
3409     $self->ask_for_bind (1);
3410     }
3411 root 1.191
3412 root 1.265 sub update_binding_widgets {
3413     my ($self) = @_;
3414     my ($mod, $sym, $cmds) = $self->get_binding;
3415     $self->{keylbl}->set_text (CFClient::Binder::keycombo_to_name ($mod, $sym));
3416     $self->set_command_list ($cmds);
3417     }
3418 root 1.259
3419 root 1.265 sub get_binding {
3420     my ($self) = @_;
3421     return (
3422     $self->{binding}->[0],
3423     $self->{binding}->[1],
3424     [ grep { defined $_ } @{$self->{commands}} ]
3425     );
3426     }
3427 root 1.259
3428 root 1.265 sub clear_command_list {
3429     my ($self) = @_;
3430     $self->{cmdbox}->clear ();
3431     }
3432 root 1.191
3433 root 1.265 sub set_command_list {
3434     my ($self, $cmds) = @_;
3435 root 1.191
3436 root 1.265 $self->{cmdbox}->clear ();
3437     $self->{commands} = $cmds;
3438 root 1.250
3439 root 1.265 my $idx = 0;
3440 root 1.191
3441 root 1.265 for (@$cmds) {
3442     $self->{cmdbox}->add (my $hb = new CFClient::UI::HBox);
3443 root 1.256
3444 root 1.265 my $i = $idx;
3445     $hb->add (new CFClient::UI::Label text => $_);
3446     $hb->add (new CFClient::UI::Button
3447     text => "delete",
3448     tooltip => "Deletes the action from the record",
3449     on_activate => sub {
3450     $self->{cmdbox}->remove ($hb);
3451     $cmds->[$i] = undef;
3452     });
3453 root 1.256
3454 root 1.252
3455 root 1.265 $idx++
3456 root 1.107 }
3457 root 1.51 }
3458    
3459     #############################################################################
3460    
3461 root 1.264 package CFClient::UI::SpellList;
3462    
3463 root 1.273 our @ISA = CFClient::UI::Table::;
3464 root 1.264
3465     sub new {
3466     my $class = shift;
3467    
3468 root 1.272 my $self = $class->SUPER::new (
3469     binding => [],
3470     commands => [],
3471     @_,
3472     )
3473 root 1.264 }
3474    
3475     # XXX: Do sorting? Argl...
3476     sub add_spell {
3477     my ($self, $spell) = @_;
3478     $self->{spells}->{$spell->{name}} = $spell;
3479    
3480 root 1.273 $self->add (0, $self->{tbl_idx}, new CFClient::UI::Face
3481 root 1.264 face => $spell->{face},
3482     can_hover => 1,
3483     can_events => 1,
3484     tooltip => $spell->{message});
3485    
3486 root 1.273 $self->add (1, $self->{tbl_idx}, new CFClient::UI::Label
3487 root 1.264 text => $spell->{name},
3488     can_hover => 1,
3489     can_events => 1,
3490     tooltip => $spell->{message},
3491     expand => 1);
3492    
3493 root 1.273 $self->add (2, $self->{tbl_idx}, new CFClient::UI::Label
3494 root 1.264 text => (sprintf "lvl: %2d sp: %2d dmg: %2d",
3495     $spell->{level}, ($spell->{mana} || $spell->{grace}), $spell->{damage}),
3496     expand => 1);
3497    
3498 root 1.273 $self->add (3, $self->{tbl_idx}++, new CFClient::UI::Button
3499 root 1.264 text => "bind to key",
3500     on_activate => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) });
3501     }
3502    
3503     sub rebuild_spell_list {
3504     my ($self) = @_;
3505     $self->{tbl_idx} = 0;
3506     $self->add_spell ($_) for values %{$self->{spells}};
3507     }
3508    
3509     sub remove_spell {
3510     my ($self, $spell) = @_;
3511     delete $self->{spells}->{$spell->{name}};
3512     $self->rebuild_spell_list;
3513     }
3514    
3515     #############################################################################
3516    
3517 root 1.265 package CFClient::UI::Root;
3518    
3519     our @ISA = CFClient::UI::Container::;
3520 elmex 1.260
3521 root 1.280 use List::Util qw(min max);
3522    
3523 root 1.265 use CFClient::OpenGL;
3524 elmex 1.260
3525     sub new {
3526     my $class = shift;
3527    
3528 root 1.265 my $self = $class->SUPER::new (
3529     visible => 1,
3530     @_,
3531     );
3532    
3533     Scalar::Util::weaken ($self->{root} = $self);
3534    
3535     $self
3536     }
3537    
3538     sub size_request {
3539     my ($self) = @_;
3540    
3541     ($self->{w}, $self->{h})
3542     }
3543 elmex 1.260
3544 root 1.265 sub _to_pixel {
3545     my ($coord, $size, $max) = @_;
3546 elmex 1.260
3547 root 1.265 $coord =
3548     $coord eq "center" ? ($max - $size) * 0.5
3549     : $coord eq "max" ? $max
3550     : $coord;
3551 elmex 1.260
3552 root 1.265 $coord = 0 if $coord < 0;
3553     $coord = $max - $size if $coord > $max - $size;
3554 elmex 1.260
3555 root 1.265 int $coord + 0.5
3556     }
3557 elmex 1.260
3558 root 1.265 sub size_allocate {
3559     my ($self, $w, $h) = @_;
3560 elmex 1.261
3561 root 1.265 for my $child ($self->children) {
3562     my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)};
3563 elmex 1.260
3564 root 1.265 $X = $child->{force_x} if exists $child->{force_x};
3565     $Y = $child->{force_y} if exists $child->{force_y};
3566 elmex 1.260
3567 root 1.265 $X = _to_pixel $X, $W, $self->{w};
3568     $Y = _to_pixel $Y, $H, $self->{h};
3569 elmex 1.260
3570 root 1.265 $child->configure ($X, $Y, $W, $H);
3571     }
3572 elmex 1.260 }
3573    
3574 root 1.265 sub coord2local {
3575     my ($self, $x, $y) = @_;
3576    
3577     ($x, $y)
3578 elmex 1.260 }
3579    
3580 root 1.265 sub coord2global {
3581     my ($self, $x, $y) = @_;
3582 elmex 1.260
3583 root 1.265 ($x, $y)
3584 elmex 1.260 }
3585    
3586 root 1.265 sub update {
3587 elmex 1.260 my ($self) = @_;
3588    
3589 root 1.265 $::WANT_REFRESH++;
3590     }
3591 elmex 1.260
3592 root 1.265 sub add {
3593     my ($self, @children) = @_;
3594 elmex 1.260
3595 root 1.265 $_->{is_toplevel} = 1
3596     for @children;
3597 elmex 1.260
3598 root 1.265 $self->SUPER::add (@children);
3599 elmex 1.260 }
3600    
3601 root 1.265 sub remove {
3602     my ($self, @children) = @_;
3603    
3604     $self->SUPER::remove (@children);
3605 elmex 1.260
3606 root 1.265 delete $self->{is_toplevel}
3607     for @children;
3608 elmex 1.260
3609 root 1.265 while (@children) {
3610     my $w = pop @children;
3611     push @children, $w->children;
3612     $w->set_invisible;
3613     }
3614     }
3615 elmex 1.260
3616 root 1.265 sub on_refresh {
3617     my ($self, $id, $cb) = @_;
3618 elmex 1.260
3619 root 1.265 $self->{refresh_hook}{$id} = $cb;
3620 elmex 1.260 }
3621    
3622 root 1.265 sub on_post_alloc {
3623     my ($self, $id, $cb) = @_;
3624    
3625     $self->{post_alloc_hook}{$id} = $cb;
3626 elmex 1.262 }
3627    
3628 root 1.265 sub draw {
3629 elmex 1.260 my ($self) = @_;
3630    
3631 root 1.265 while ($self->{refresh_hook}) {
3632     $_->()
3633     for values %{delete $self->{refresh_hook}};
3634     }
3635    
3636     if ($self->{realloc}) {
3637 root 1.266 my %queue;
3638 root 1.265 my @queue;
3639 root 1.266 my $widget;
3640 root 1.265
3641 root 1.266 outer:
3642 root 1.265 while () {
3643 root 1.266 if (my $realloc = delete $self->{realloc}) {
3644     for $widget (values %$realloc) {
3645     $widget->{visible} or next; # do not resize invisible widgets
3646 root 1.265
3647 root 1.266 $queue{$widget+0}++ and next; # duplicates are common
3648 root 1.265
3649 root 1.266 push @{ $queue[$widget->{visible}] }, $widget;
3650     }
3651 root 1.265 }
3652    
3653 root 1.266 while () {
3654     @queue or last outer;
3655    
3656     $widget = pop @{ $queue[-1] || [] }
3657     and last;
3658    
3659     pop @queue;
3660     }
3661 root 1.265
3662 root 1.266 delete $queue{$widget+0};
3663 root 1.265
3664     my ($w, $h) = $widget->size_request;
3665    
3666 root 1.280 $w = max $widget->{min_w}, $w + $widget->{padding_x} * 2;
3667     $h = max $widget->{min_h}, $h + $widget->{padding_y} * 2;
3668    
3669     $w = min $widget->{max_w}, $w if exists $widget->{max_w};
3670     $h = min $widget->{max_h}, $h if exists $widget->{max_h};
3671 root 1.265
3672     $w = $widget->{force_w} if exists $widget->{force_w};
3673     $h = $widget->{force_h} if exists $widget->{force_h};
3674    
3675     if ($widget->{req_w} != $w || $widget->{req_h} != $h
3676     || delete $widget->{force_realloc}) {
3677     $widget->{req_w} = $w;
3678     $widget->{req_h} = $h;
3679    
3680     $self->{size_alloc}{$widget+0} = $widget;
3681    
3682     if (my $parent = $widget->{parent}) {
3683 root 1.266 $self->{realloc}{$parent+0} = $parent
3684     unless $queue{$parent+0};
3685    
3686 root 1.265 $parent->{force_size_alloc} = 1;
3687     $self->{size_alloc}{$parent+0} = $parent;
3688     }
3689     }
3690    
3691     delete $self->{realloc}{$widget+0};
3692     }
3693     }
3694 elmex 1.260
3695 root 1.265 while (my $size_alloc = delete $self->{size_alloc}) {
3696     my @queue = sort { $b->{visible} <=> $a->{visible} }
3697     values %$size_alloc;
3698 elmex 1.260
3699 root 1.265 while () {
3700     my $widget = pop @queue || last;
3701 elmex 1.260
3702 root 1.265 my ($w, $h) = @$widget{qw(alloc_w alloc_h)};
3703 elmex 1.260
3704 root 1.265 $w = 0 if $w < 0;
3705     $h = 0 if $h < 0;
3706 elmex 1.260
3707 root 1.265 $w = int $w + 0.5;
3708     $h = int $h + 0.5;
3709 elmex 1.260
3710 root 1.265 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) {
3711 root 1.266 $widget->{old_w} = $widget->{w};
3712     $widget->{old_h} = $widget->{h};
3713    
3714 root 1.265 $widget->{w} = $w;
3715     $widget->{h} = $h;
3716 elmex 1.260
3717 root 1.265 $widget->emit (size_allocate => $w, $h);
3718     }
3719     }
3720     }
3721 elmex 1.260
3722 root 1.265 while ($self->{post_alloc_hook}) {
3723     $_->()
3724     for values %{delete $self->{post_alloc_hook}};
3725 elmex 1.260 }
3726 root 1.265
3727    
3728     glViewport 0, 0, $::WIDTH, $::HEIGHT;
3729     glClearColor +($::CFG->{fow_intensity}) x 3, 1;
3730     glClear GL_COLOR_BUFFER_BIT;
3731    
3732     glMatrixMode GL_PROJECTION;
3733     glLoadIdentity;
3734     glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000;
3735     glMatrixMode GL_MODELVIEW;
3736     glLoadIdentity;
3737    
3738 root 1.267 {
3739     package CFClient::UI::Base;
3740    
3741     ($draw_x, $draw_y, $draw_w, $draw_h) =
3742     (0, 0, $self->{w}, $self->{h});
3743     }
3744    
3745 root 1.265 $self->_draw;
3746 elmex 1.260 }
3747    
3748 elmex 1.262 #############################################################################
3749    
3750 root 1.73 package CFClient::UI;
3751 root 1.51
3752 root 1.113 $ROOT = new CFClient::UI::Root;
3753 root 1.213 $TOOLTIP = new CFClient::UI::Tooltip z => 900;
3754 root 1.51
3755     1
3756 root 1.5