ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.273
Committed: Sat Jun 3 02:32:35 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.272: +26 -18 lines
Log Message:
put dialog settings into a scrolled window, improve scrolled window and notebook

File Contents

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