ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.286
Committed: Mon Jun 5 03:48:49 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.285: +6 -4 lines
Log Message:
switch off cairo antialiasing on win32, its unbearable otherwise. re-enable pangofc, which was disabled by accident

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