ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.288
Committed: Mon Jun 5 21:14:40 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.287: +6 -2 lines
Log Message:
fix labels not becoming smaller after a reconfigure

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