ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.279
Committed: Mon Jun 5 00:17:47 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.278: +10 -19 lines
Log Message:
play around some more

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