ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.272
Committed: Sat Jun 3 01:47:14 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.271: +135 -14 lines
Log Message:
very crude setup dialog - the audio section looks funny

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