ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.280
Committed: Mon Jun 5 01:22:08 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.279: +43 -6 lines
Log Message:
*** empty log message ***

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