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