ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.275
Committed: Sat Jun 3 22:50:48 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.274: +9 -8 lines
Log Message:
*** empty log message ***

File Contents

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