ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.274
Committed: Sat Jun 3 22:20:52 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.273: +2 -0 lines
Log Message:
*** empty log message ***

File Contents

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