ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.290
Committed: Mon Jun 5 22:30:35 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.289: +58 -30 lines
Log Message:
partially reimplemented the spell list widget

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.288
1560 root 1.289 delete $self->{size_req};
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.288
1576 root 1.289 delete $self->{size_req};
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.289 $self->{size_req} ||= do {
1585 root 1.282 $self->{layout}->set_font ($self->{font}) if $self->{font};
1586     $self->{layout}->set_width ($self->{max_w} || -1);
1587     $self->{layout}->set_ellipsise ($self->{ellipsise});
1588     $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1589     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1590    
1591     my ($w, $h) = $self->{layout}->size;
1592 root 1.121
1593 root 1.282 if (exists $self->{template}) {
1594     $self->{template}->set_font ($self->{font}) if $self->{font};
1595     $self->{template}->set_height ($self->{fontsize} * $::FONTSIZE);
1596 root 1.76
1597 root 1.282 my ($w2, $h2) = $self->{template}->size;
1598 root 1.141
1599 root 1.282 $w = List::Util::max $w, $w2;
1600     $h = List::Util::max $h, $h2;
1601     }
1602 root 1.141
1603 root 1.289 [$w, $h]
1604     };
1605    
1606     @{ $self->{size_req} }
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 root 1.289 sub reconfigure {
1628     my ($self) = @_;
1629    
1630     delete $self->{size_req};
1631    
1632     $self->SUPER::reconfigure;
1633     }
1634    
1635 elmex 1.11 sub _draw {
1636 root 1.10 my ($self) = @_;
1637    
1638 root 1.209 $self->SUPER::_draw; # draw background, if applicable
1639    
1640 root 1.59 my $tex = $self->{texture} ||= do {
1641 root 1.194 $self->{layout}->set_foreground (@{$self->{fg}});
1642 root 1.157 $self->{layout}->set_font ($self->{font}) if $self->{font};
1643 root 1.59 $self->{layout}->set_width ($self->{w});
1644 root 1.213 $self->{layout}->set_ellipsise ($self->{ellipsise});
1645     $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1646     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1647 root 1.194
1648 root 1.269 new_from_layout CFClient::Texture $self->{layout}
1649     };
1650 root 1.194
1651 root 1.269 unless (exists $self->{ox}) {
1652 root 1.258 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x}
1653     : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding_x}
1654 root 1.208 : ($self->{w} - $tex->{w}) * 0.5);
1655    
1656 root 1.258 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y}
1657     : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding_y}
1658 root 1.208 : ($self->{h} - $tex->{h}) * 0.5);
1659 root 1.59 };
1660 root 1.10
1661     glEnable GL_TEXTURE_2D;
1662    
1663 root 1.286 if ($tex->{format} == GL_ALPHA) {
1664 root 1.287 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
1665 root 1.286 glColor @{$self->{fg}};
1666     $tex->draw_quad_alpha ($self->{ox}, $self->{oy});
1667     } else {
1668 root 1.287 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1669 root 1.286 $tex->draw_quad_alpha_premultiplied ($self->{ox}, $self->{oy});
1670     }
1671 root 1.10
1672 root 1.74 glDisable GL_TEXTURE_2D;
1673 root 1.10 }
1674    
1675 root 1.39 #############################################################################
1676    
1677 root 1.121 package CFClient::UI::EntryBase;
1678 elmex 1.31
1679 root 1.73 our @ISA = CFClient::UI::Label::;
1680 elmex 1.31
1681 root 1.138 use CFClient::OpenGL;
1682 elmex 1.31
1683 root 1.68 sub new {
1684     my $class = shift;
1685    
1686     $class->SUPER::new (
1687 root 1.164 fg => [1, 1, 1],
1688     bg => [0, 0, 0, 0.2],
1689     active_bg => [1, 1, 1, 0.5],
1690     active_fg => [0, 0, 0],
1691     can_hover => 1,
1692     can_focus => 1,
1693     valign => 0,
1694 elmex 1.150 can_events => 1,
1695 root 1.225 #text => ...
1696 root 1.68 @_
1697     )
1698     }
1699    
1700     sub _set_text {
1701     my ($self, $text) = @_;
1702    
1703 root 1.121 delete $self->{cur_h};
1704    
1705     return if $self->{text} eq $text;
1706 elmex 1.100
1707 root 1.68 $self->{last_activity} = $::NOW;
1708     $self->{text} = $text;
1709 root 1.72
1710     $text =~ s/./*/g if $self->{hidden};
1711 root 1.121 $self->{layout}->set_text ("$text ");
1712 root 1.289 delete $self->{size_req};
1713 root 1.72
1714 root 1.231 $self->_emit (changed => $self->{text});
1715 root 1.283
1716     $self->realloc;
1717 root 1.276 $self->update;
1718 root 1.121 }
1719 root 1.68
1720 root 1.194 sub set_text {
1721     my ($self, $text) = @_;
1722    
1723     $self->{cursor} = length $text;
1724     $self->_set_text ($text);
1725     }
1726    
1727 root 1.121 sub get_text {
1728     $_[0]{text}
1729 root 1.68 }
1730    
1731     sub size_request {
1732     my ($self) = @_;
1733    
1734     my ($w, $h) = $self->SUPER::size_request;
1735    
1736     ($w + 1, $h) # add 1 for cursor
1737     }
1738    
1739 elmex 1.31 sub key_down {
1740     my ($self, $ev) = @_;
1741    
1742 root 1.137 my $mod = $ev->{mod};
1743     my $sym = $ev->{sym};
1744     my $uni = $ev->{unicode};
1745 elmex 1.31
1746     my $text = $self->get_text;
1747    
1748 root 1.200 if ($uni == 8) {
1749 root 1.68 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1750 root 1.200 } elsif ($uni == 127) {
1751 root 1.68 substr $text, $self->{cursor}, 1, "";
1752 root 1.136 } elsif ($sym == CFClient::SDLK_LEFT) {
1753 root 1.68 --$self->{cursor} if $self->{cursor};
1754 root 1.136 } elsif ($sym == CFClient::SDLK_RIGHT) {
1755 root 1.68 ++$self->{cursor} if $self->{cursor} < length $self->{text};
1756 root 1.136 } elsif ($sym == CFClient::SDLK_HOME) {
1757 root 1.76 $self->{cursor} = 0;
1758 root 1.136 } elsif ($sym == CFClient::SDLK_END) {
1759 root 1.76 $self->{cursor} = length $text;
1760 root 1.200 } elsif ($uni == 27) {
1761 root 1.231 $self->_emit ('escape');
1762 elmex 1.31 } elsif ($uni) {
1763 root 1.68 substr $text, $self->{cursor}++, 0, chr $uni;
1764 root 1.271 } else {
1765     return 0;
1766 elmex 1.31 }
1767 root 1.51
1768 root 1.68 $self->_set_text ($text);
1769 root 1.251
1770     $self->realloc;
1771 root 1.271
1772     1
1773 root 1.68 }
1774    
1775     sub focus_in {
1776     my ($self) = @_;
1777    
1778     $self->{last_activity} = $::NOW;
1779    
1780     $self->SUPER::focus_in;
1781 elmex 1.31 }
1782    
1783 root 1.51 sub button_down {
1784 root 1.68 my ($self, $ev, $x, $y) = @_;
1785    
1786     $self->SUPER::button_down ($ev, $x, $y);
1787    
1788     my $idx = $self->{layout}->xy_to_index ($x, $y);
1789    
1790     # byte-index to char-index
1791 root 1.76 my $text = $self->{text};
1792 root 1.68 utf8::encode $text;
1793     $self->{cursor} = length substr $text, 0, $idx;
1794 root 1.51
1795 root 1.68 $self->_set_text ($self->{text});
1796     $self->update;
1797 root 1.271
1798     1
1799 root 1.51 }
1800    
1801 root 1.58 sub mouse_motion {
1802     my ($self, $ev, $x, $y) = @_;
1803 root 1.68 # printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d#
1804 root 1.271
1805     0
1806 root 1.58 }
1807    
1808 root 1.51 sub _draw {
1809     my ($self) = @_;
1810    
1811 root 1.68 local $self->{fg} = $self->{fg};
1812    
1813 root 1.51 if ($FOCUS == $self) {
1814 root 1.278 glColor_premultiply @{$self->{active_bg}};
1815 root 1.68 $self->{fg} = $self->{active_fg};
1816 root 1.51 } else {
1817 root 1.278 glColor_premultiply @{$self->{bg}};
1818 root 1.51 }
1819    
1820 root 1.76 glEnable GL_BLEND;
1821 root 1.278 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
1822 root 1.51 glBegin GL_QUADS;
1823 root 1.68 glVertex 0 , 0;
1824     glVertex 0 , $self->{h};
1825     glVertex $self->{w}, $self->{h};
1826     glVertex $self->{w}, 0;
1827 root 1.51 glEnd;
1828 root 1.76 glDisable GL_BLEND;
1829 root 1.51
1830     $self->SUPER::_draw;
1831 root 1.68
1832     #TODO: force update every cursor change :(
1833     if ($FOCUS == $self && (($::NOW - $self->{last_activity}) & 1023) < 600) {
1834 root 1.121
1835     unless (exists $self->{cur_h}) {
1836     my $text = substr $self->{text}, 0, $self->{cursor};
1837     utf8::encode $text;
1838    
1839     @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text)
1840     }
1841    
1842 root 1.68 glColor @{$self->{fg}};
1843     glBegin GL_LINES;
1844 root 1.122 glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy};
1845     glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy} + $self->{cur_h};
1846 root 1.68 glEnd;
1847     }
1848     }
1849    
1850 root 1.121 package CFClient::UI::Entry;
1851 elmex 1.99
1852 root 1.121 our @ISA = CFClient::UI::EntryBase::;
1853 elmex 1.99
1854 root 1.138 use CFClient::OpenGL;
1855 elmex 1.99
1856     sub key_down {
1857     my ($self, $ev) = @_;
1858    
1859 root 1.137 my $sym = $ev->{sym};
1860 elmex 1.99
1861 root 1.136 if ($sym == 13) {
1862 elmex 1.167 unshift @{$self->{history}},
1863     my $txt = $self->get_text;
1864     $self->{history_pointer} = -1;
1865 elmex 1.169 $self->{history_saveback} = '';
1866 root 1.231 $self->_emit (activate => $txt);
1867 elmex 1.99 $self->update;
1868    
1869 elmex 1.167 } elsif ($sym == CFClient::SDLK_UP) {
1870     if ($self->{history_pointer} < 0) {
1871     $self->{history_saveback} = $self->get_text;
1872     }
1873 elmex 1.169 if (@{$self->{history} || []} > 0) {
1874     $self->{history_pointer}++;
1875     if ($self->{history_pointer} >= @{$self->{history} || []}) {
1876     $self->{history_pointer} = @{$self->{history} || []} - 1;
1877     }
1878     $self->set_text ($self->{history}->[$self->{history_pointer}]);
1879 elmex 1.167 }
1880    
1881     } elsif ($sym == CFClient::SDLK_DOWN) {
1882     $self->{history_pointer}--;
1883     $self->{history_pointer} = -1 if $self->{history_pointer} < 0;
1884    
1885     if ($self->{history_pointer} >= 0) {
1886     $self->set_text ($self->{history}->[$self->{history_pointer}]);
1887     } else {
1888     $self->set_text ($self->{history_saveback});
1889     }
1890    
1891 elmex 1.99 } else {
1892 root 1.271 return $self->SUPER::key_down ($ev)
1893 elmex 1.99 }
1894    
1895 root 1.271 1
1896 elmex 1.99 }
1897    
1898 root 1.68 #############################################################################
1899    
1900 root 1.79 package CFClient::UI::Button;
1901    
1902     our @ISA = CFClient::UI::Label::;
1903    
1904 root 1.138 use CFClient::OpenGL;
1905 root 1.79
1906 elmex 1.85 my @tex =
1907 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1908 elmex 1.85 qw(b1_button_active.png);
1909    
1910 root 1.79 sub new {
1911     my $class = shift;
1912    
1913     $class->SUPER::new (
1914 root 1.258 padding_x => 4,
1915     padding_y => 4,
1916 root 1.164 fg => [1, 1, 1],
1917     active_fg => [0, 0, 1],
1918     can_hover => 1,
1919     align => 0,
1920     valign => 0,
1921 elmex 1.150 can_events => 1,
1922 root 1.79 @_
1923     )
1924     }
1925    
1926 root 1.231 sub activate { }
1927    
1928 root 1.79 sub button_up {
1929     my ($self, $ev, $x, $y) = @_;
1930    
1931 root 1.231 $self->emit ("activate")
1932     if $x >= 0 && $x < $self->{w}
1933     && $y >= 0 && $y < $self->{h};
1934 root 1.271
1935     1
1936 root 1.79 }
1937    
1938     sub _draw {
1939     my ($self) = @_;
1940    
1941 root 1.279 local $self->{fg} = $GRAB == $self ? $self->{active_fg} : $self->{fg};
1942 root 1.79
1943 root 1.119 glEnable GL_TEXTURE_2D;
1944 elmex 1.85 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1945 root 1.119 glColor 0, 0, 0, 1;
1946 elmex 1.85
1947 root 1.195 $tex[0]->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
1948 elmex 1.85
1949     glDisable GL_TEXTURE_2D;
1950 root 1.79
1951     $self->SUPER::_draw;
1952     }
1953    
1954     #############################################################################
1955    
1956 root 1.86 package CFClient::UI::CheckBox;
1957    
1958     our @ISA = CFClient::UI::DrawBG::;
1959    
1960 elmex 1.102 my @tex =
1961 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1962 elmex 1.102 qw(c1_checkbox_bg.png c1_checkbox_active.png);
1963    
1964 root 1.138 use CFClient::OpenGL;
1965 root 1.86
1966     sub new {
1967     my $class = shift;
1968    
1969     $class->SUPER::new (
1970 root 1.258 padding_x => 2,
1971     padding_y => 2,
1972 root 1.86 fg => [1, 1, 1],
1973     active_fg => [1, 1, 0],
1974 root 1.209 bg => [0, 0, 0, 0.2],
1975     active_bg => [1, 1, 1, 0.5],
1976 root 1.86 state => 0,
1977 root 1.97 can_hover => 1,
1978 root 1.86 @_
1979     )
1980     }
1981    
1982 root 1.87 sub size_request {
1983     my ($self) = @_;
1984    
1985 root 1.258 (6) x 2
1986 root 1.87 }
1987    
1988 root 1.86 sub button_down {
1989     my ($self, $ev, $x, $y) = @_;
1990    
1991 root 1.258 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
1992     && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
1993 root 1.86 $self->{state} = !$self->{state};
1994 root 1.231 $self->_emit (changed => $self->{state});
1995 root 1.271 } else {
1996     return 0
1997 root 1.86 }
1998 root 1.271
1999     1
2000 root 1.86 }
2001    
2002     sub _draw {
2003     my ($self) = @_;
2004    
2005 root 1.87 $self->SUPER::_draw;
2006 root 1.86
2007 root 1.258 glTranslate $self->{padding_x} + 0.375, $self->{padding_y} + 0.375, 0;
2008 root 1.86
2009 root 1.258 my ($w, $h) = @$self{qw(w h)};
2010    
2011     my $s = List::Util::min $w - $self->{padding_x} * 2, $h - $self->{padding_y} * 2;
2012 elmex 1.102
2013 root 1.87 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };
2014 root 1.86
2015 elmex 1.102 my $tex = $self->{state} ? $tex[1] : $tex[0];
2016    
2017 root 1.197 glEnable GL_TEXTURE_2D;
2018 root 1.195 $tex->draw_quad_alpha (0, 0, $s, $s);
2019 elmex 1.102 glDisable GL_TEXTURE_2D;
2020 root 1.86 }
2021    
2022     #############################################################################
2023    
2024 elmex 1.145 package CFClient::UI::Image;
2025    
2026     our @ISA = CFClient::UI::Base::;
2027    
2028     use CFClient::OpenGL;
2029     use Carp qw/confess/;
2030    
2031     our %loaded_images;
2032    
2033     sub new {
2034     my $class = shift;
2035    
2036 elmex 1.150 my $self = $class->SUPER::new (can_events => 0, @_);
2037 elmex 1.145
2038     $self->{image} or confess "Image has 'image' not set. This is a fatal error!";
2039    
2040     $loaded_images{$self->{image}} ||=
2041     new_from_file CFClient::Texture CFClient::find_rcfile $self->{image}, mipmap => 1;
2042    
2043     my $tex = $self->{tex} = $loaded_images{$self->{image}};
2044    
2045 root 1.147 Scalar::Util::weaken $loaded_images{$self->{image}};
2046    
2047 elmex 1.145 $self->{aspect} = $tex->{w} / $tex->{h};
2048    
2049     $self
2050     }
2051    
2052     sub size_request {
2053     my ($self) = @_;
2054    
2055     ($self->{tex}->{w}, $self->{tex}->{h})
2056     }
2057    
2058     sub _draw {
2059     my ($self) = @_;
2060    
2061     my $tex = $self->{tex};
2062    
2063     my ($w, $h) = ($self->{w}, $self->{h});
2064    
2065     if ($self->{rot90}) {
2066     glRotate 90, 0, 0, 1;
2067     glTranslate 0, -$self->{w}, 0;
2068    
2069     ($w, $h) = ($h, $w);
2070     }
2071    
2072     glEnable GL_TEXTURE_2D;
2073     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2074    
2075 root 1.195 $tex->draw_quad_alpha (0, 0, $w, $h);
2076 elmex 1.145
2077     glDisable GL_TEXTURE_2D;
2078     }
2079    
2080     #############################################################################
2081    
2082 elmex 1.124 package CFClient::UI::VGauge;
2083    
2084     our @ISA = CFClient::UI::Base::;
2085    
2086 root 1.158 use List::Util qw(min max);
2087    
2088 root 1.138 use CFClient::OpenGL;
2089 elmex 1.124
2090     my %tex = (
2091     food => [
2092 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2093 elmex 1.124 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/
2094     ],
2095     grace => [
2096 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2097 root 1.158 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/
2098 elmex 1.124 ],
2099     hp => [
2100 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2101 elmex 1.124 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/
2102     ],
2103     mana => [
2104 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2105 root 1.158 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/
2106 elmex 1.124 ],
2107     );
2108    
2109     # eg. VGauge->new (gauge => 'food'), default gauge: food
2110     sub new {
2111     my $class = shift;
2112    
2113 root 1.140 my $self = $class->SUPER::new (
2114 root 1.141 type => 'food',
2115 root 1.140 @_
2116     );
2117    
2118 root 1.141 $self->{aspect} = $tex{$self->{type}}[0]{w} / $tex{$self->{type}}[0]{h};
2119 elmex 1.124
2120     $self
2121     }
2122    
2123     sub size_request {
2124     my ($self) = @_;
2125    
2126 root 1.143 #my $tex = $tex{$self->{type}}[0];
2127     #@$tex{qw(w h)}
2128     (0, 0)
2129 elmex 1.124 }
2130    
2131     sub set_max {
2132     my ($self, $max) = @_;
2133 root 1.127
2134 root 1.173 return if $self->{max_val} == $max;
2135    
2136 elmex 1.124 $self->{max_val} = $max;
2137 root 1.173 $self->update;
2138 elmex 1.124 }
2139    
2140     sub set_value {
2141     my ($self, $val, $max) = @_;
2142    
2143     $self->set_max ($max)
2144     if defined $max;
2145    
2146 root 1.173 return if $self->{val} == $val;
2147    
2148 elmex 1.124 $self->{val} = $val;
2149     $self->update;
2150     }
2151    
2152     sub _draw {
2153     my ($self) = @_;
2154    
2155 root 1.141 my $tex = $tex{$self->{type}};
2156 root 1.158 my ($t1, $t2, $t3) = @$tex;
2157 elmex 1.124
2158     my ($w, $h) = ($self->{w}, $self->{h});
2159    
2160 elmex 1.142 if ($self->{vertical}) {
2161     glRotate 90, 0, 0, 1;
2162     glTranslate 0, -$self->{w}, 0;
2163    
2164     ($w, $h) = ($h, $w);
2165     }
2166    
2167 elmex 1.124 my $ycut = $self->{val} / ($self->{max_val} || 1);
2168    
2169 root 1.158 my $ycut1 = max 0, min 1, $ycut;
2170     my $ycut2 = max 0, min 1, $ycut - 1;
2171    
2172     my $h1 = $self->{h} * (1 - $ycut1);
2173     my $h2 = $self->{h} * (1 - $ycut2);
2174 elmex 1.124
2175     glEnable GL_BLEND;
2176 root 1.278 glBlendFuncSeparate GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
2177     GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2178 elmex 1.124 glEnable GL_TEXTURE_2D;
2179     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2180    
2181 root 1.131 glBindTexture GL_TEXTURE_2D, $t1->{name};
2182     glBegin GL_QUADS;
2183 root 1.158 glTexCoord 0 , 0; glVertex 0 , 0;
2184     glTexCoord 0 , $t1->{t} * (1 - $ycut1); glVertex 0 , $h1;
2185     glTexCoord $t1->{s}, $t1->{t} * (1 - $ycut1); glVertex $w, $h1;
2186     glTexCoord $t1->{s}, 0; glVertex $w, 0;
2187 root 1.131 glEnd;
2188 elmex 1.124
2189 root 1.158 my $ycut1 = List::Util::min 1, $ycut;
2190 root 1.131 glBindTexture GL_TEXTURE_2D, $t2->{name};
2191     glBegin GL_QUADS;
2192 root 1.158 glTexCoord 0 , $t2->{t} * (1 - $ycut1); glVertex 0 , $h1;
2193     glTexCoord 0 , $t2->{t} * (1 - $ycut2); glVertex 0 , $h2;
2194     glTexCoord $t2->{s}, $t2->{t} * (1 - $ycut2); glVertex $w, $h2;
2195     glTexCoord $t2->{s}, $t2->{t} * (1 - $ycut1); glVertex $w, $h1;
2196 root 1.131 glEnd;
2197 elmex 1.124
2198 root 1.158 if ($t3) {
2199     glBindTexture GL_TEXTURE_2D, $t3->{name};
2200     glBegin GL_QUADS;
2201     glTexCoord 0 , $t3->{t} * (1 - $ycut2); glVertex 0 , $h2;
2202     glTexCoord 0 , $t3->{t}; glVertex 0 , $self->{h};
2203     glTexCoord $t3->{s}, $t3->{t}; glVertex $w, $self->{h};
2204     glTexCoord $t3->{s}, $t3->{t} * (1 - $ycut2); glVertex $w, $h2;
2205     glEnd;
2206     }
2207    
2208 elmex 1.124 glDisable GL_BLEND;
2209     glDisable GL_TEXTURE_2D;
2210     }
2211    
2212     #############################################################################
2213    
2214 root 1.141 package CFClient::UI::Gauge;
2215    
2216     our @ISA = CFClient::UI::VBox::;
2217    
2218     sub new {
2219 root 1.151 my ($class, %arg) = @_;
2220 root 1.141
2221     my $self = $class->SUPER::new (
2222 root 1.171 tooltip => $arg{type},
2223     can_hover => 1,
2224     can_events => 1,
2225 root 1.151 %arg,
2226 root 1.141 );
2227    
2228 root 1.161 $self->add ($self->{value} = new CFClient::UI::Label valign => +1, align => 0, template => "999");
2229     $self->add ($self->{gauge} = new CFClient::UI::VGauge type => $self->{type}, expand => 1, can_hover => 1);
2230     $self->add ($self->{max} = new CFClient::UI::Label valign => -1, align => 0, template => "999");
2231 root 1.141
2232     $self
2233     }
2234    
2235 elmex 1.146 sub set_fontsize {
2236     my ($self, $fsize) = @_;
2237    
2238     $self->{value}->set_fontsize ($fsize);
2239     $self->{max} ->set_fontsize ($fsize);
2240     }
2241    
2242 root 1.173 sub set_max {
2243     my ($self, $max) = @_;
2244    
2245     $self->{gauge}->set_max ($max);
2246     $self->{max}->set_text ($max);
2247     }
2248    
2249 root 1.141 sub set_value {
2250     my ($self, $val, $max) = @_;
2251    
2252     $self->set_max ($max)
2253     if defined $max;
2254    
2255     $self->{gauge}->set_value ($val, $max);
2256     $self->{value}->set_text ($val);
2257     }
2258    
2259     #############################################################################
2260    
2261 root 1.73 package CFClient::UI::Slider;
2262 root 1.68
2263     use strict;
2264    
2265 root 1.138 use CFClient::OpenGL;
2266 root 1.68
2267 root 1.73 our @ISA = CFClient::UI::DrawBG::;
2268 root 1.68
2269 elmex 1.99 my @tex =
2270     map { new_from_file CFClient::Texture CFClient::find_rcfile $_ }
2271     qw(s1_slider.png s1_slider_bg.png);
2272    
2273 root 1.68 sub new {
2274     my $class = shift;
2275    
2276 root 1.206 # range [value, low, high, page, unit]
2277 root 1.68
2278 root 1.97 # TODO: 0-width page
2279     # TODO: req_w/h are wrong with vertical
2280     # TODO: calculations are off
2281 root 1.76 my $self = $class->SUPER::new (
2282 root 1.68 fg => [1, 1, 1],
2283     active_fg => [0, 0, 0],
2284 root 1.209 bg => [0, 0, 0, 0.2],
2285     active_bg => [1, 1, 1, 0.5],
2286 root 1.227 range => [0, 0, 100, 10, 0],
2287 root 1.257 min_w => $::WIDTH / 80,
2288     min_h => $::WIDTH / 80,
2289 root 1.76 vertical => 0,
2290 root 1.97 can_hover => 1,
2291 root 1.217 inner_pad => 0.02,
2292 root 1.68 @_
2293 root 1.76 );
2294    
2295 root 1.206 $self->set_value ($self->{range}[0]);
2296     $self->update;
2297    
2298 root 1.76 $self
2299     }
2300    
2301 root 1.251 sub changed { }
2302    
2303 root 1.225 sub set_range {
2304     my ($self, $range) = @_;
2305    
2306 root 1.239 ($range, $self->{range}) = ($self->{range}, $range);
2307 root 1.225
2308 root 1.239 $self->update
2309     if "@$range" ne "@{$self->{range}}";
2310 root 1.225 }
2311    
2312 root 1.206 sub set_value {
2313     my ($self, $value) = @_;
2314    
2315     my ($old_value, $lo, $hi, $page, $unit) = @{$self->{range}};
2316    
2317     $hi = $lo + 1 if $hi <= $lo;
2318    
2319 root 1.227 $page = $hi - $lo if $page > $hi - $lo;
2320    
2321     $value = $lo if $value < $lo;
2322     $value = $hi - $page if $value > $hi - $page;
2323 root 1.206
2324     $value = $lo + $unit * int +($value - $lo + $unit * 0.5) / $unit
2325     if $unit;
2326    
2327     @{$self->{range}} = ($value, $lo, $hi, $page, $unit);
2328    
2329     if ($value != $old_value) {
2330 root 1.231 $self->_emit (changed => $value);
2331 root 1.206 $self->update;
2332     }
2333     }
2334    
2335 root 1.76 sub size_request {
2336     my ($self) = @_;
2337    
2338 root 1.257 ($self->{req_w}, $self->{req_h})
2339 root 1.68 }
2340    
2341 root 1.69 sub button_down {
2342     my ($self, $ev, $x, $y) = @_;
2343    
2344     $self->SUPER::button_down ($ev, $x, $y);
2345 root 1.227
2346     $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x];
2347    
2348 root 1.271 $self->mouse_motion ($ev, $x, $y)
2349 root 1.69 }
2350    
2351     sub mouse_motion {
2352     my ($self, $ev, $x, $y) = @_;
2353    
2354     if ($GRAB == $self) {
2355 root 1.71 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w});
2356    
2357 root 1.206 my (undef, $lo, $hi, $page) = @{$self->{range}};
2358 elmex 1.103
2359 root 1.227 $x = ($x - $self->{click}[1]) / ($w * $self->{scale});
2360 root 1.69
2361 root 1.227 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo));
2362 root 1.271 } else {
2363     return 0;
2364 root 1.69 }
2365 root 1.271
2366     1
2367 root 1.69 }
2368    
2369 root 1.206 sub update {
2370     my ($self) = @_;
2371    
2372 root 1.275 delete $self->{knob_w};
2373     $self->SUPER::update;
2374     }
2375    
2376     sub _draw {
2377     my ($self) = @_;
2378    
2379     unless ($self->{knob_w}) {
2380 root 1.206 $self->set_value ($self->{range}[0]);
2381    
2382     my ($value, $lo, $hi, $page) = @{$self->{range}};
2383 root 1.227 my $range = ($hi - $page - $lo) || 1e-100;
2384 root 1.206
2385 root 1.227 my $knob_w = List::Util::min 1, $page / ($hi - $lo) || 0.1;
2386 root 1.206
2387 root 1.227 $self->{offset} = List::Util::max $self->{inner_pad}, $knob_w * 0.5;
2388     $self->{scale} = 1 - 2 * $self->{offset} || 1e-100;
2389 root 1.206
2390 root 1.227 $value = ($value - $lo) / $range;
2391     $value = $value * $self->{scale} + $self->{offset};
2392 root 1.206
2393 root 1.227 $self->{knob_x} = $value - $knob_w * 0.5;
2394     $self->{knob_w} = $knob_w;
2395 root 1.275 }
2396 root 1.68
2397     $self->SUPER::_draw ();
2398    
2399 root 1.206 glScale $self->{w}, $self->{h};
2400 root 1.68
2401     if ($self->{vertical}) {
2402     # draw a vertical slider like a rotated horizontal slider
2403    
2404 root 1.214 glTranslate 1, 0, 0;
2405 root 1.68 glRotate 90, 0, 0, 1;
2406     }
2407    
2408     my $fg = $FOCUS == $self ? $self->{active_fg} : $self->{fg};
2409     my $bg = $FOCUS == $self ? $self->{active_bg} : $self->{bg};
2410    
2411 elmex 1.99 glEnable GL_TEXTURE_2D;
2412     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2413    
2414     # draw background
2415 root 1.206 $tex[1]->draw_quad_alpha (0, 0, 1, 1);
2416 root 1.69
2417 elmex 1.99 # draw handle
2418 root 1.206 $tex[0]->draw_quad_alpha ($self->{knob_x}, 0, $self->{knob_w}, 1);
2419 root 1.69
2420 elmex 1.99 glDisable GL_TEXTURE_2D;
2421 root 1.51 }
2422    
2423 root 1.39 #############################################################################
2424    
2425 root 1.225 package CFClient::UI::ValSlider;
2426    
2427     our @ISA = CFClient::UI::HBox::;
2428    
2429     sub new {
2430     my ($class, %arg) = @_;
2431    
2432     my $range = delete $arg{range};
2433    
2434     my $self = $class->SUPER::new (
2435     slider => (new CFClient::UI::Slider expand => 1, range => $range),
2436     entry => (new CFClient::UI::Label text => "", template => delete $arg{template}),
2437     to_value => sub { shift },
2438     from_value => sub { shift },
2439     %arg,
2440     );
2441    
2442     $self->{slider}->connect (changed => sub {
2443     my ($self, $value) = @_;
2444     $self->{parent}{entry}->set_text ($self->{parent}{to_value}->($value));
2445     $self->{parent}->emit (changed => $value);
2446     });
2447    
2448     # $self->{entry}->connect (changed => sub {
2449     # my ($self, $value) = @_;
2450     # $self->{parent}{slider}->set_value ($self->{parent}{from_value}->($value));
2451     # $self->{parent}->emit (changed => $value);
2452     # });
2453    
2454     $self->add ($self->{slider}, $self->{entry});
2455    
2456     $self->{slider}->emit (changed => $self->{slider}{range}[0]);
2457    
2458     $self
2459     }
2460    
2461     sub set_range { shift->{slider}->set_range (@_) }
2462     sub set_value { shift->{slider}->set_value (@_) }
2463    
2464     #############################################################################
2465    
2466 root 1.97 package CFClient::UI::TextView;
2467    
2468     our @ISA = CFClient::UI::HBox::;
2469    
2470 root 1.138 use CFClient::OpenGL;
2471 root 1.97
2472     sub new {
2473     my $class = shift;
2474    
2475     my $self = $class->SUPER::new (
2476 root 1.164 fontsize => 1,
2477     can_events => 0,
2478     #font => default_font
2479 root 1.105 @_,
2480 root 1.164
2481 root 1.195 layout => (new CFClient::Layout 1),
2482 root 1.164 par => [],
2483     height => 0,
2484     children => [
2485 root 1.97 (new CFClient::UI::Empty expand => 1),
2486     (new CFClient::UI::Slider vertical => 1),
2487     ],
2488     );
2489    
2490 root 1.176 $self->{children}[1]->connect (changed => sub { $self->update });
2491 root 1.107
2492 root 1.97 $self
2493     }
2494    
2495 root 1.107 sub set_fontsize {
2496     my ($self, $fontsize) = @_;
2497    
2498     $self->{fontsize} = $fontsize;
2499     $self->reflow;
2500     }
2501    
2502 root 1.220 sub size_allocate {
2503 root 1.259 my ($self, $w, $h) = @_;
2504 root 1.220
2505 root 1.259 $self->SUPER::size_allocate ($w, $h);
2506 root 1.220
2507     $self->{layout}->set_font ($self->{font}) if $self->{font};
2508     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
2509     $self->{layout}->set_width ($self->{children}[0]{w});
2510    
2511     $self->reflow;
2512     }
2513    
2514 root 1.228 sub text_size {
2515 root 1.220 my ($self, $text, $indent) = @_;
2516 root 1.105
2517     my $layout = $self->{layout};
2518    
2519 root 1.134 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2520 root 1.220 $layout->set_width ($self->{children}[0]{w} - $indent);
2521 root 1.195 $layout->set_markup ($text);
2522 root 1.105
2523 root 1.228 $layout->size
2524 root 1.105 }
2525    
2526     sub reflow {
2527     my ($self) = @_;
2528    
2529 root 1.107 $self->{need_reflow}++;
2530     $self->update;
2531 root 1.105 }
2532    
2533 root 1.227 sub set_offset {
2534     my ($self, $offset) = @_;
2535    
2536     # todo: base offset on lines or so, not on pixels
2537     $self->{children}[1]->set_value ($offset);
2538     }
2539    
2540 root 1.226 sub clear {
2541     my ($self) = @_;
2542    
2543     $self->{par} = [];
2544     $self->{height} = 0;
2545 root 1.227 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]);
2546 root 1.226 }
2547    
2548 root 1.97 sub add_paragraph {
2549 root 1.220 my ($self, $color, $text, $indent) = @_;
2550 root 1.97
2551 root 1.220 for my $line (split /\n/, $text) {
2552 root 1.228 my ($w, $h) = $self->text_size ($line);
2553     $self->{height} += $h;
2554     push @{$self->{par}}, [$w + $indent, $h, $color, $indent, $line];
2555 root 1.220 }
2556 root 1.105
2557 root 1.227 $self->{children}[1]->set_range ([$self->{height}, 0, $self->{height}, $self->{h}, 1]);
2558 root 1.97 }
2559    
2560 root 1.105 sub update {
2561 root 1.97 my ($self) = @_;
2562    
2563 root 1.105 $self->SUPER::update;
2564    
2565     return unless $self->{h} > 0;
2566    
2567 root 1.107 delete $self->{texture};
2568    
2569 root 1.198 $ROOT->on_post_alloc ($self, sub {
2570 root 1.228 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
2571    
2572 root 1.107 if (delete $self->{need_reflow}) {
2573     my $height = 0;
2574    
2575 root 1.228 my $layout = $self->{layout};
2576    
2577     $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2578    
2579     for (@{$self->{par}}) {
2580     if (1 || $_->[0] >= $W) { # TODO: works,but needs reconfigure etc. support
2581     $layout->set_width ($W - $_->[3]);
2582     $layout->set_markup ($_->[4]);
2583     my ($w, $h) = $layout->size;
2584     $_->[0] = $w + $_->[3];
2585     $_->[1] = $h;
2586     }
2587    
2588     $height += $_->[1];
2589     }
2590 root 1.107
2591     $self->{height} = $height;
2592    
2593 root 1.228 $self->{children}[1]->set_range ([$height, 0, $height, $H, 1]);
2594 root 1.107
2595     delete $self->{texture};
2596     }
2597    
2598 root 1.228 $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub {
2599 root 1.279 glClearColor 0, 0, 0, 0;
2600 root 1.107 glClear GL_COLOR_BUFFER_BIT;
2601    
2602     my $top = int $self->{children}[1]{range}[0];
2603 root 1.105
2604 root 1.107 my $y0 = $top;
2605 root 1.228 my $y1 = $top + $H;
2606 root 1.105
2607 root 1.107 my $y = 0;
2608 root 1.97
2609 root 1.107 my $layout = $self->{layout};
2610 root 1.97
2611 root 1.157 $layout->set_font ($self->{font}) if $self->{font};
2612    
2613 root 1.220 glEnable GL_BLEND;
2614 root 1.228 #TODO# not correct in windows where rgba is forced off
2615 root 1.220 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2616    
2617 root 1.107 for my $par (@{$self->{par}}) {
2618 root 1.228 my $h = $par->[1];
2619 root 1.97
2620 root 1.107 if ($y0 < $y + $h && $y < $y1) {
2621 root 1.228 $layout->set_foreground (@{ $par->[2] });
2622     $layout->set_width ($W - $par->[3]);
2623     $layout->set_markup ($par->[4]);
2624 root 1.220
2625     my ($w, $h, $data, $format, $internalformat) = $layout->render;
2626 root 1.105
2627 root 1.228 glRasterPos $par->[3], $y - $y0;
2628 root 1.220 glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data;
2629 root 1.107 }
2630    
2631     $y += $h;
2632 root 1.105 }
2633    
2634 root 1.220 glDisable GL_BLEND;
2635 root 1.107 };
2636     });
2637 root 1.105 }
2638 root 1.97
2639 root 1.105 sub _draw {
2640     my ($self) = @_;
2641 root 1.97
2642 root 1.176 glEnable GL_TEXTURE_2D;
2643     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2644 root 1.279 glColor 0, 0, 0, 1;
2645     $self->{texture}->draw_quad_alpha_premultiplied (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h});
2646 root 1.176 glDisable GL_TEXTURE_2D;
2647 root 1.97
2648 root 1.106 $self->{children}[1]->draw;
2649    
2650 root 1.97 }
2651    
2652     #############################################################################
2653    
2654 root 1.73 package CFClient::UI::Animator;
2655 root 1.35
2656 root 1.138 use CFClient::OpenGL;
2657 root 1.35
2658 root 1.73 our @ISA = CFClient::UI::Bin::;
2659 root 1.35
2660     sub moveto {
2661     my ($self, $x, $y) = @_;
2662    
2663     $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
2664 root 1.56 $self->{speed} = 0.001;
2665 root 1.35 $self->{time} = 1;
2666    
2667     ::animation_start $self;
2668     }
2669    
2670     sub animate {
2671     my ($self, $interval) = @_;
2672    
2673     $self->{time} -= $interval * $self->{speed};
2674     if ($self->{time} <= 0) {
2675     $self->{time} = 0;
2676     ::animation_stop $self;
2677     }
2678    
2679     my ($x0, $y0, $x1, $y1) = @{$self->{moveto}};
2680    
2681     $self->{x} = $x0 * $self->{time} + $x1 * (1 - $self->{time});
2682     $self->{y} = $y0 * $self->{time} + $y1 * (1 - $self->{time});
2683     }
2684    
2685     sub _draw {
2686     my ($self) = @_;
2687    
2688     glPushMatrix;
2689 root 1.51 glRotate $self->{time} * 1000, 0, 1, 0;
2690 root 1.38 $self->{children}[0]->draw;
2691 root 1.35 glPopMatrix;
2692     }
2693    
2694 root 1.51 #############################################################################
2695    
2696 root 1.96 package CFClient::UI::Flopper;
2697    
2698     our @ISA = CFClient::UI::Button::;
2699    
2700     sub new {
2701     my $class = shift;
2702    
2703     my $self = $class->SUPER::new (
2704 root 1.243 state => 0,
2705     on_activate => \&toggle_flopper,
2706 root 1.96 @_
2707     );
2708    
2709     $self
2710     }
2711    
2712     sub toggle_flopper {
2713     my ($self) = @_;
2714    
2715 elmex 1.245 $self->{other}->toggle_visibility;
2716 root 1.96 }
2717    
2718     #############################################################################
2719    
2720 root 1.153 package CFClient::UI::Tooltip;
2721    
2722     our @ISA = CFClient::UI::Bin::;
2723    
2724     use CFClient::OpenGL;
2725    
2726     sub new {
2727     my $class = shift;
2728    
2729     $class->SUPER::new (
2730     @_,
2731     can_events => 0,
2732     )
2733     }
2734    
2735 root 1.196 sub set_tooltip_from {
2736     my ($self, $widget) = @_;
2737 root 1.195
2738 root 1.259 my $tooltip = $widget->{tooltip};
2739    
2740     if ($ENV{CFPLUS_DEBUG} & 2) {
2741     $tooltip .= "\n\n" . (ref $widget) . "\n"
2742     . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n"
2743     . "req $widget->{req_w} $widget->{req_h}\n"
2744     . "visible $widget->{visible}";
2745     }
2746    
2747 root 1.197 $self->add (new CFClient::UI::Label
2748 root 1.259 markup => $tooltip,
2749 root 1.213 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH,
2750     fontsize => 0.8,
2751     fg => [0, 0, 0, 1],
2752     ellipsise => 0,
2753     font => ($widget->{tooltip_font} || $::FONT_PROP),
2754 root 1.197 );
2755 root 1.153 }
2756    
2757     sub size_request {
2758     my ($self) = @_;
2759    
2760     my ($w, $h) = @{$self->child}{qw(req_w req_h)};
2761    
2762 root 1.154 ($w + 4, $h + 4)
2763     }
2764    
2765 root 1.162 sub size_allocate {
2766 root 1.259 my ($self, $w, $h) = @_;
2767 root 1.162
2768 root 1.259 $self->SUPER::size_allocate ($w - 4, $h - 4);
2769 root 1.162 }
2770    
2771 root 1.253 sub visibility_change {
2772     my ($self, $visible) = @_;
2773    
2774     return unless $visible;
2775    
2776     $self->{root}->on_post_alloc ("move_$self" => sub {
2777 root 1.254 my $widget = $self->{owner}
2778     or return;
2779 root 1.253
2780     my ($x, $y) = $widget->coord2global ($widget->{w}, 0);
2781    
2782     ($x, $y) = $widget->coord2global (-$self->{w}, 0)
2783     if $x + $self->{w} > $::WIDTH;
2784    
2785 root 1.256 $self->move_abs ($x, $y);
2786 root 1.253 });
2787     }
2788    
2789 root 1.154 sub _draw {
2790     my ($self) = @_;
2791    
2792     glTranslate 0.375, 0.375;
2793    
2794     my ($w, $h) = @$self{qw(w h)};
2795    
2796     glColor 1, 0.8, 0.4;
2797     glBegin GL_QUADS;
2798     glVertex 0 , 0;
2799     glVertex 0 , $h;
2800     glVertex $w, $h;
2801     glVertex $w, 0;
2802     glEnd;
2803    
2804     glColor 0, 0, 0;
2805     glBegin GL_LINE_LOOP;
2806     glVertex 0 , 0;
2807     glVertex 0 , $h;
2808     glVertex $w, $h;
2809     glVertex $w, 0;
2810     glEnd;
2811    
2812 root 1.197 glTranslate 2 - 0.375, 2 - 0.375;
2813 root 1.252
2814 root 1.154 $self->SUPER::_draw;
2815 root 1.153 }
2816    
2817     #############################################################################
2818    
2819 root 1.162 package CFClient::UI::Face;
2820    
2821     our @ISA = CFClient::UI::Base::;
2822    
2823     use CFClient::OpenGL;
2824    
2825     sub new {
2826     my $class = shift;
2827    
2828 root 1.217 my $self = $class->SUPER::new (
2829 root 1.234 aspect => 1,
2830     can_events => 0,
2831 root 1.162 @_,
2832 root 1.217 );
2833    
2834     if ($self->{anim} && $self->{animspeed}) {
2835     Scalar::Util::weaken (my $widget = $self);
2836    
2837     $self->{timer} = Event->timer (
2838     at => $self->{animspeed} * int $::NOW / $self->{animspeed},
2839     hard => 1,
2840     interval => $self->{animspeed},
2841     cb => sub {
2842     ++$widget->{frame};
2843     $widget->update;
2844     },
2845     );
2846     }
2847    
2848     $self
2849 root 1.162 }
2850    
2851     sub size_request {
2852     (32, 8)
2853     }
2854    
2855 root 1.222 sub update {
2856     my ($self) = @_;
2857    
2858     return unless $self->{visible};
2859    
2860     $self->SUPER::update;
2861     }
2862    
2863 elmex 1.179 sub _draw {
2864 root 1.162 my ($self) = @_;
2865    
2866 root 1.227 return unless $::CONN;
2867 root 1.162
2868 root 1.217 my $face;
2869    
2870     if ($self->{frame}) {
2871     my $anim = $::CONN->{anim}[$self->{anim}];
2872    
2873     $face = $anim->[ $self->{frame} % @$anim ]
2874     if $anim && @$anim;
2875     }
2876    
2877     my $tex = $::CONN->{texture}[$::CONN->{faceid}[$face || $self->{face}]];
2878    
2879 root 1.162 if ($tex) {
2880     glEnable GL_TEXTURE_2D;
2881     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2882 root 1.279 glColor 0, 0, 0, 1;
2883 root 1.195 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2884 root 1.162 glDisable GL_TEXTURE_2D;
2885     }
2886     }
2887    
2888 root 1.217 sub DESTROY {
2889     my ($self) = @_;
2890    
2891     $self->{timer}->cancel
2892     if $self->{timer};
2893    
2894     $self->SUPER::DESTROY;
2895     }
2896    
2897 root 1.162 #############################################################################
2898    
2899 root 1.272 package CFClient::UI::Buttonbar;
2900    
2901     our @ISA = CFClient::UI::HBox::;
2902    
2903     # TODO: should actualyl wrap buttons and other goodies.
2904    
2905     #############################################################################
2906    
2907 root 1.178 package CFClient::UI::Menu;
2908    
2909     our @ISA = CFClient::UI::FancyFrame::;
2910    
2911     use CFClient::OpenGL;
2912    
2913     sub new {
2914     my $class = shift;
2915    
2916     my $self = $class->SUPER::new (
2917     items => [],
2918     z => 100,
2919     @_,
2920     );
2921    
2922     $self->add ($self->{vbox} = new CFClient::UI::VBox);
2923    
2924     for my $item (@{ $self->{items} }) {
2925     my ($widget, $cb) = @$item;
2926    
2927     # handle various types of items, only text for now
2928     if (!ref $widget) {
2929     $widget = new CFClient::UI::Label
2930     can_hover => 1,
2931     can_events => 1,
2932     text => $widget;
2933     }
2934    
2935     $self->{item}{$widget} = $item;
2936    
2937     $self->{vbox}->add ($widget);
2938     }
2939    
2940     $self
2941     }
2942    
2943     # popup given the event (must be a mouse button down event currently)
2944     sub popup {
2945     my ($self, $ev) = @_;
2946    
2947 root 1.231 $self->_emit ("popdown");
2948 root 1.178
2949     # maybe save $GRAB? must be careful about events...
2950     $GRAB = $self;
2951     $self->{button} = $ev->{button};
2952    
2953     $self->show;
2954 root 1.258 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5);
2955 root 1.178 }
2956    
2957     sub mouse_motion {
2958     my ($self, $ev, $x, $y) = @_;
2959    
2960 root 1.182 # TODO: should use vbox->find_widget or so
2961 root 1.178 $HOVER = $ROOT->find_widget ($ev->{x}, $ev->{y});
2962     $self->{hover} = $self->{item}{$HOVER};
2963 root 1.271
2964     0
2965 root 1.178 }
2966    
2967     sub button_up {
2968     my ($self, $ev, $x, $y) = @_;
2969    
2970     if ($ev->{button} == $self->{button}) {
2971     undef $GRAB;
2972     $self->hide;
2973    
2974 root 1.231 $self->_emit ("popdown");
2975 root 1.178 $self->{hover}[1]->() if $self->{hover};
2976 root 1.271 } else {
2977     return 0
2978 root 1.178 }
2979 root 1.271
2980     1
2981 root 1.178 }
2982    
2983     #############################################################################
2984    
2985 root 1.272 package CFClient::UI::Multiplexer;
2986    
2987     our @ISA = CFClient::UI::Container::;
2988    
2989     sub new {
2990     my $class = shift;
2991    
2992     my $self = $class->SUPER::new (
2993     @_,
2994     );
2995    
2996     $self->{current} = $self->{children}[0]
2997     if @{ $self->{children} };
2998    
2999     $self
3000     }
3001    
3002     sub add {
3003     my ($self, @widgets) = @_;
3004    
3005     $self->SUPER::add (@widgets);
3006    
3007     $self->{current} = $self->{children}[0]
3008     if @{ $self->{children} };
3009     }
3010    
3011     sub set_current_page {
3012     my ($self, $page_or_widget) = @_;
3013    
3014     my $widget = ref $page_or_widget
3015     ? $page_or_widget
3016     : $self->{children}[$page_or_widget];
3017    
3018     $self->{current} = $widget;
3019     $self->{current}->configure (0, 0, $self->{w}, $self->{h});
3020    
3021     $self->_emit (page_changed => $self->{current});
3022    
3023     $self->realloc;
3024     }
3025    
3026     sub visible_children {
3027     $_[0]{current}
3028     }
3029    
3030     sub size_request {
3031     my ($self) = @_;
3032    
3033     $self->{current}->size_request
3034     }
3035    
3036     sub size_allocate {
3037     my ($self, $w, $h) = @_;
3038    
3039     $self->{current}->configure (0, 0, $w, $h);
3040     }
3041    
3042     sub _draw {
3043     my ($self) = @_;
3044    
3045     $self->{current}->draw;
3046     }
3047    
3048     #############################################################################
3049    
3050     package CFClient::UI::Notebook;
3051    
3052     our @ISA = CFClient::UI::VBox::;
3053    
3054     sub new {
3055     my $class = shift;
3056    
3057     my $self = $class->SUPER::new (
3058     buttonbar => (new CFClient::UI::Buttonbar),
3059     multiplexer => (new CFClient::UI::Multiplexer expand => 1),
3060 root 1.273 # filter => # will be put between multiplexer and $self
3061 root 1.272 @_,
3062     );
3063 root 1.273
3064     $self->{filter}->add ($self->{multiplexer}) if $self->{filter};
3065     $self->SUPER::add ($self->{buttonbar}, $self->{filter} || $self->{multiplexer});
3066 root 1.272
3067     $self
3068     }
3069    
3070     sub add {
3071     my ($self, $title, $widget, $tooltip) = @_;
3072    
3073     Scalar::Util::weaken $self;
3074    
3075     $self->{buttonbar}->add (new CFClient::UI::Button
3076     markup => $title,
3077     tooltip => $tooltip,
3078     on_activate => sub { $self->set_current_page ($widget) },
3079     );
3080    
3081     $self->{multiplexer}->add ($widget);
3082     }
3083    
3084     sub set_current_page {
3085     my ($self, $page) = @_;
3086    
3087     $self->{multiplexer}->set_current_page ($page);
3088     $self->_emit (page_changed => $self->{multiplexer}{current});
3089     }
3090    
3091     #############################################################################
3092    
3093 root 1.194 package CFClient::UI::Statusbox;
3094    
3095     our @ISA = CFClient::UI::VBox::;
3096    
3097 root 1.210 sub new {
3098     my $class = shift;
3099    
3100 root 1.280 my $self = $class->SUPER::new (
3101 root 1.210 fontsize => 0.8,
3102     @_,
3103 root 1.280 );
3104    
3105     Scalar::Util::weaken (my $this = $self);
3106    
3107 root 1.281 $self->{timer} = Event->timer (after => 1, interval => 1, cb => sub { $this->reorder });
3108 root 1.280
3109     $self
3110 root 1.210 }
3111    
3112 root 1.194 sub reorder {
3113     my ($self) = @_;
3114 root 1.280 my $NOW = Time::HiRes::time;
3115 root 1.194
3116 root 1.281 # freeze display when hovering over any label
3117     return if $CFClient::UI::TOOLTIP->{owner}
3118     && grep $CFClient::UI::TOOLTIP->{owner} == $_->{label},
3119     values %{ $self->{item} };
3120    
3121 root 1.194 while (my ($k, $v) = each %{ $self->{item} }) {
3122     delete $self->{item}{$k} if $v->{timeout} < $NOW;
3123     }
3124    
3125     my @widgets;
3126 root 1.197
3127     my @items = sort {
3128     $a->{pri} <=> $b->{pri}
3129     or $b->{id} <=> $a->{id}
3130     } values %{ $self->{item} };
3131    
3132 root 1.280 $self->{timer}->interval (1);
3133    
3134 root 1.194 my $count = 10 + 1;
3135     for my $item (@items) {
3136     last unless --$count;
3137    
3138 root 1.281 my $label = $item->{label} ||= do {
3139 root 1.194 # TODO: doesn't handle markup well (read as: at all)
3140 root 1.197 my $short = $item->{count} > 1
3141     ? "<b>$item->{count} ×</b> $item->{text}"
3142     : $item->{text};
3143    
3144 root 1.194 for ($short) {
3145     s/^\s+//;
3146 root 1.205 s/\s+/ /g;
3147 root 1.194 }
3148    
3149     new CFClient::UI::Label
3150 root 1.196 markup => $short,
3151 root 1.197 tooltip => $item->{tooltip},
3152 root 1.196 tooltip_font => $::FONT_PROP,
3153 root 1.197 tooltip_width => 0.67,
3154 root 1.213 fontsize => $item->{fontsize} || $self->{fontsize},
3155     max_w => $::WIDTH * 0.44,
3156 root 1.281 fg => [@{ $item->{fg} }],
3157 root 1.196 can_events => 1,
3158 root 1.197 can_hover => 1
3159 root 1.194 };
3160 root 1.280
3161     if ((my $diff = $item->{timeout} - $NOW) < 2) {
3162 root 1.281 $label->{fg}[3] = ($item->{fg}[3] || 1) * $diff / 2;
3163     $label->update;
3164     $label->set_max_size (undef, $label->{req_h} * $diff)
3165     if $diff < 1;
3166 root 1.280 $self->{timer}->interval (1/30);
3167 root 1.281 } else {
3168     $label->{fg}[3] = $item->{fg}[3] || 1;
3169 root 1.280 }
3170 root 1.281
3171     push @widgets, $label;
3172 root 1.194 }
3173    
3174     $self->clear;
3175 root 1.197 $self->SUPER::add (reverse @widgets);
3176 root 1.194 }
3177    
3178     sub add {
3179     my ($self, $text, %arg) = @_;
3180    
3181 root 1.198 $text =~ s/^\s+//;
3182     $text =~ s/\s+$//;
3183    
3184 root 1.233 return unless $text;
3185    
3186 root 1.280 my $timeout = (int time) + ((delete $arg{timeout}) || 60);
3187 root 1.194
3188 root 1.197 my $group = exists $arg{group} ? $arg{group} : ++$self->{id};
3189 root 1.194
3190 root 1.197 if (my $item = $self->{item}{$group}) {
3191     if ($item->{text} eq $text) {
3192     $item->{count}++;
3193     } else {
3194     $item->{count} = 1;
3195     $item->{text} = $item->{tooltip} = $text;
3196     }
3197 root 1.198 $item->{id} = ++$self->{id};
3198 root 1.197 $item->{timeout} = $timeout;
3199     delete $item->{label};
3200     } else {
3201     $self->{item}{$group} = {
3202     id => ++$self->{id},
3203     text => $text,
3204     timeout => $timeout,
3205     tooltip => $text,
3206 root 1.205 fg => [0.8, 0.8, 0.8, 0.8],
3207 root 1.197 pri => 0,
3208     count => 1,
3209     %arg,
3210     };
3211     }
3212 root 1.194
3213     $self->reorder;
3214     }
3215    
3216 root 1.213 sub reconfigure {
3217     my ($self) = @_;
3218    
3219     delete $_->{label}
3220     for values %{ $self->{item} || {} };
3221    
3222     $self->reorder;
3223     $self->SUPER::reconfigure;
3224     }
3225    
3226 root 1.280 sub DESTROY {
3227     my ($self) = @_;
3228    
3229     $self->{timer}->cancel;
3230    
3231     $self->SUPER::DESTROY;
3232     }
3233    
3234 root 1.194 #############################################################################
3235    
3236 root 1.265 package CFClient::UI::Inventory;
3237 root 1.51
3238 root 1.265 our @ISA = CFClient::UI::ScrolledWindow::;
3239 root 1.107
3240 root 1.191 sub new {
3241     my $class = shift;
3242    
3243 root 1.251 my $self = $class->SUPER::new (
3244 root 1.273 child => (new CFClient::UI::Table col_expand => [0, 1, 0]),
3245 root 1.191 @_,
3246 root 1.251 );
3247    
3248     $self
3249 root 1.191 }
3250    
3251 root 1.265 sub set_items {
3252     my ($self, $items) = @_;
3253    
3254 root 1.273 $self->{child}->clear;
3255 root 1.265 return unless $items;
3256 root 1.186
3257 root 1.265 my @items = sort {
3258     ($a->{type} <=> $b->{type})
3259     or ($a->{name} cmp $b->{name})
3260     } @$items;
3261 root 1.186
3262 root 1.265 $self->{real_items} = \@items;
3263 root 1.256
3264 root 1.265 my $row = 0;
3265     for my $item (@items) {
3266     CFClient::Item::update_widgets $item;
3267 root 1.256
3268 root 1.273 $self->{child}->add (0, $row, $item->{face_widget});
3269     $self->{child}->add (1, $row, $item->{desc_widget});
3270     $self->{child}->add (2, $row, $item->{weight_widget});
3271 root 1.256
3272 root 1.265 $row++;
3273     }
3274 root 1.256 }
3275    
3276 root 1.265 #############################################################################
3277 root 1.186
3278 root 1.265 package CFClient::UI::BindEditor;
3279 root 1.149
3280 root 1.265 our @ISA = CFClient::UI::FancyFrame::;
3281 root 1.205
3282 root 1.265 sub new {
3283     my $class = shift;
3284 root 1.205
3285 root 1.265 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3286 root 1.191
3287 root 1.265 $self->add (my $vb = new CFClient::UI::VBox);
3288 root 1.191
3289 root 1.51
3290 root 1.265 $vb->add ($self->{rec_btn} = new CFClient::UI::Button
3291     text => "start recording",
3292     tooltip => "Start/Stops recording of actions."
3293     ."All subsequent actions after the recording started will be captured."
3294     ."The actions are displayed after the record was stopped."
3295     ."To bind the action you have to click on the 'Bind' button",
3296     on_activate => sub {
3297     unless ($self->{recording}) {
3298     $self->start;
3299     } else {
3300     $self->stop;
3301     }
3302     });
3303 root 1.58
3304 root 1.265 $vb->add (new CFClient::UI::Label text => "Actions:");
3305     $vb->add ($self->{cmdbox} = new CFClient::UI::VBox);
3306 root 1.58
3307 root 1.265 $vb->add (new CFClient::UI::Label text => "Bound to: ");
3308     $vb->add (my $hb = new CFClient::UI::HBox);
3309     $hb->add ($self->{keylbl} = new CFClient::UI::Label expand => 1);
3310     $hb->add (new CFClient::UI::Button
3311     text => "bind",
3312     tooltip => "This opens a query where you have to press the key combination to bind the recorded actions",
3313     on_activate => sub {
3314     $self->ask_for_bind;
3315     });
3316 root 1.51
3317 root 1.265 $vb->add (my $hb = new CFClient::UI::HBox);
3318     $hb->add (new CFClient::UI::Button
3319     text => "ok",
3320     expand => 1,
3321     tooltip => "This closes the binding editor and saves the binding",
3322     on_activate => sub {
3323     $self->hide;
3324     $self->commit;
3325     });
3326 root 1.51
3327 root 1.265 $hb->add (new CFClient::UI::Button
3328     text => "cancel",
3329     expand => 1,
3330     tooltip => "This closes the binding editor without saving",
3331     on_activate => sub {
3332     $self->hide;
3333     $self->{binding_cancel}->()
3334     if $self->{binding_cancel};
3335     });
3336 root 1.203
3337 root 1.265 $self->update_binding_widgets;
3338 elmex 1.146
3339 root 1.265 $self
3340 root 1.222 }
3341    
3342 root 1.265 sub commit {
3343     my ($self) = @_;
3344     my ($mod, $sym, $cmds) = $self->get_binding;
3345     if ($sym != 0 && @$cmds > 0) {
3346     $::STATUSBOX->add ("Bound actions to '".CFClient::Binder::keycombo_to_name ($mod, $sym)
3347     ."'. Don't forget 'Save Config'!");
3348     $self->{binding_change}->($mod, $sym, $cmds)
3349     if $self->{binding_change};
3350     } else {
3351     $::STATUSBOX->add ("No action bound, no key or action specified!");
3352     $self->{binding_cancel}->()
3353     if $self->{binding_cancel};
3354 root 1.222 }
3355 root 1.51 }
3356    
3357 root 1.265 sub start {
3358     my ($self) = @_;
3359 root 1.107
3360 root 1.265 $self->{rec_btn}->set_text ("stop recording");
3361     $self->{recording} = 1;
3362     $self->clear_command_list;
3363     $::CONN->start_record if $::CONN;
3364 root 1.107 }
3365    
3366 root 1.265 sub stop {
3367 root 1.51 my ($self) = @_;
3368    
3369 root 1.265 $self->{rec_btn}->set_text ("start recording");
3370     $self->{recording} = 0;
3371 root 1.198
3372 root 1.265 my $rec;
3373     $rec = $::CONN->stop_record if $::CONN;
3374     return unless ref $rec eq 'ARRAY';
3375     $self->set_command_list ($rec);
3376     }
3377 root 1.191
3378 elmex 1.270
3379     sub ask_for_bind_and_commit {
3380     my ($self) = @_;
3381     $self->ask_for_bind (1);
3382     }
3383    
3384 root 1.265 sub ask_for_bind {
3385     my ($self, $commit) = @_;
3386 root 1.243
3387 root 1.265 CFClient::Binder::open_binding_dialog (sub {
3388     my ($mod, $sym) = @_;
3389     $self->{binding} = [$mod, $sym]; # XXX: how to stop that memleak?
3390     $self->update_binding_widgets;
3391     $self->commit if $commit;
3392     });
3393     }
3394 root 1.259
3395 root 1.265 # $mod and $sym are the modifiers and key symbol
3396     # $cmds is a array ref of strings (the commands)
3397     # $cb is the callback that is executed on OK
3398     # $ccb is the callback that is executed on CANCEL and
3399     # when the binding was unsuccessful on OK
3400     sub set_binding {
3401     my ($self, $mod, $sym, $cmds, $cb, $ccb) = @_;
3402 root 1.191
3403 root 1.265 $self->clear_command_list;
3404     $self->{recording} = 0;
3405     $self->{rec_btn}->set_text ("start recording");
3406 root 1.243
3407 root 1.265 $self->{binding} = [$mod, $sym];
3408     $self->{commands} = $cmds;
3409 root 1.191
3410 root 1.265 $self->{binding_change} = $cb;
3411     $self->{binding_cancel} = $ccb;
3412 root 1.256
3413 root 1.265 $self->update_binding_widgets;
3414     }
3415 root 1.257
3416 root 1.265 # this is a shortcut method that asks for a binding
3417     # and then just binds it.
3418     sub do_quick_binding {
3419     my ($self, $cmds) = @_;
3420     $self->set_binding (undef, undef, $cmds, sub {
3421     $::CFG->{bindings}->{$_[0]}->{$_[1]} = $_[2];
3422     });
3423     $self->ask_for_bind (1);
3424     }
3425 root 1.191
3426 root 1.265 sub update_binding_widgets {
3427     my ($self) = @_;
3428     my ($mod, $sym, $cmds) = $self->get_binding;
3429     $self->{keylbl}->set_text (CFClient::Binder::keycombo_to_name ($mod, $sym));
3430     $self->set_command_list ($cmds);
3431     }
3432 root 1.259
3433 root 1.265 sub get_binding {
3434     my ($self) = @_;
3435     return (
3436     $self->{binding}->[0],
3437     $self->{binding}->[1],
3438     [ grep { defined $_ } @{$self->{commands}} ]
3439     );
3440     }
3441 root 1.259
3442 root 1.265 sub clear_command_list {
3443     my ($self) = @_;
3444     $self->{cmdbox}->clear ();
3445     }
3446 root 1.191
3447 root 1.265 sub set_command_list {
3448     my ($self, $cmds) = @_;
3449 root 1.191
3450 root 1.265 $self->{cmdbox}->clear ();
3451     $self->{commands} = $cmds;
3452 root 1.250
3453 root 1.265 my $idx = 0;
3454 root 1.191
3455 root 1.265 for (@$cmds) {
3456     $self->{cmdbox}->add (my $hb = new CFClient::UI::HBox);
3457 root 1.256
3458 root 1.265 my $i = $idx;
3459     $hb->add (new CFClient::UI::Label text => $_);
3460     $hb->add (new CFClient::UI::Button
3461     text => "delete",
3462     tooltip => "Deletes the action from the record",
3463     on_activate => sub {
3464     $self->{cmdbox}->remove ($hb);
3465     $cmds->[$i] = undef;
3466     });
3467 root 1.256
3468 root 1.252
3469 root 1.265 $idx++
3470 root 1.107 }
3471 root 1.51 }
3472    
3473     #############################################################################
3474    
3475 root 1.264 package CFClient::UI::SpellList;
3476    
3477 root 1.273 our @ISA = CFClient::UI::Table::;
3478 root 1.264
3479     sub new {
3480     my $class = shift;
3481    
3482 root 1.272 my $self = $class->SUPER::new (
3483     binding => [],
3484     commands => [],
3485     @_,
3486     )
3487 root 1.264 }
3488    
3489 root 1.290 my @TOOLTIP_LVL = (align => 1, can_events => 1, can_hover => 1, tooltip =>
3490     "<b>Level</b>. Minimum level the caster needs in the associated skill to be able to attempt casting this spell.");
3491     my @TOOLTIP_SP = (align => 1, can_events => 1, can_hover => 1, tooltip =>
3492     "<b>Spell points / Grace points</b>. Amount of spell or grace points used by each invocation.");
3493     my @TOOLTIP_DMG = (align => 1, can_events => 1, can_hover => 1, tooltip =>
3494     "<b>Damage</b>. The amount of damage the spell deals when it hits.");
3495    
3496     sub rebuild_spell_list {
3497     my ($self) = @_;
3498    
3499     $CFClient::UI::ROOT->on_refresh ($self => sub {
3500     $self->clear;
3501    
3502     $self->add (1, 0, new CFClient::UI::Label text => "Spell Name");
3503     $self->add (2, 0, new CFClient::UI::Label text => "Lvl" , @TOOLTIP_LVL);
3504     $self->add (3, 0, new CFClient::UI::Label text => "Sp/Gp", @TOOLTIP_SP);
3505     $self->add (4, 0, new CFClient::UI::Label text => "Dmg" , @TOOLTIP_DMG);
3506    
3507     my $row = 0;
3508    
3509     for (sort { $a cmp $b } keys %{ $self->{spell} }) {
3510     my $spell = $self->{spell}{$_};
3511    
3512     $row++;
3513    
3514     $self->add (0, $row, new CFClient::UI::Face
3515     face => $spell->{face},
3516     can_hover => 1,
3517     can_events => 1,
3518     tooltip => $spell->{message},
3519     );
3520    
3521     $self->add (1, $row, new CFClient::UI::Label
3522     expand => 1,
3523     text => $spell->{name},
3524     can_hover => 1,
3525     can_events => 1,
3526     tooltip => $spell->{message},
3527     );
3528    
3529     $self->add (2, $row, new CFClient::UI::Label text => $spell->{level}, @TOOLTIP_LVL);
3530     $self->add (3, $row, new CFClient::UI::Label text => $spell->{mana} || $spell->{grace}, @TOOLTIP_SP);
3531     $self->add (4, $row, new CFClient::UI::Label text => $spell->{damage}, @TOOLTIP_DMG);
3532    
3533     # TODO: should be done via popup
3534     $self->add (5, $row, new CFClient::UI::Button
3535     text => "bind",
3536     tooltip => "bind spell readying (cast command) to key",
3537     on_activate => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) },
3538     );
3539     }
3540     });
3541     }
3542    
3543 root 1.264 sub add_spell {
3544     my ($self, $spell) = @_;
3545    
3546 root 1.290 $self->{spell}->{$spell->{name}} = $spell;
3547     $self->rebuild_spell_list;
3548 root 1.264 }
3549    
3550     sub remove_spell {
3551     my ($self, $spell) = @_;
3552 root 1.290
3553     delete $self->{spell}->{$spell->{name}};
3554 root 1.264 $self->rebuild_spell_list;
3555     }
3556    
3557     #############################################################################
3558    
3559 root 1.265 package CFClient::UI::Root;
3560    
3561     our @ISA = CFClient::UI::Container::;
3562 elmex 1.260
3563 root 1.280 use List::Util qw(min max);
3564    
3565 root 1.265 use CFClient::OpenGL;
3566 elmex 1.260
3567     sub new {
3568     my $class = shift;
3569    
3570 root 1.265 my $self = $class->SUPER::new (
3571     visible => 1,
3572     @_,
3573     );
3574    
3575     Scalar::Util::weaken ($self->{root} = $self);
3576    
3577     $self
3578     }
3579    
3580     sub size_request {
3581     my ($self) = @_;
3582    
3583     ($self->{w}, $self->{h})
3584     }
3585 elmex 1.260
3586 root 1.265 sub _to_pixel {
3587     my ($coord, $size, $max) = @_;
3588 elmex 1.260
3589 root 1.265 $coord =
3590     $coord eq "center" ? ($max - $size) * 0.5
3591     : $coord eq "max" ? $max
3592     : $coord;
3593 elmex 1.260
3594 root 1.265 $coord = 0 if $coord < 0;
3595     $coord = $max - $size if $coord > $max - $size;
3596 elmex 1.260
3597 root 1.265 int $coord + 0.5
3598     }
3599 elmex 1.260
3600 root 1.265 sub size_allocate {
3601     my ($self, $w, $h) = @_;
3602 elmex 1.261
3603 root 1.265 for my $child ($self->children) {
3604     my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)};
3605 elmex 1.260
3606 root 1.265 $X = $child->{force_x} if exists $child->{force_x};
3607     $Y = $child->{force_y} if exists $child->{force_y};
3608 elmex 1.260
3609 root 1.265 $X = _to_pixel $X, $W, $self->{w};
3610     $Y = _to_pixel $Y, $H, $self->{h};
3611 elmex 1.260
3612 root 1.265 $child->configure ($X, $Y, $W, $H);
3613     }
3614 elmex 1.260 }
3615    
3616 root 1.265 sub coord2local {
3617     my ($self, $x, $y) = @_;
3618    
3619     ($x, $y)
3620 elmex 1.260 }
3621    
3622 root 1.265 sub coord2global {
3623     my ($self, $x, $y) = @_;
3624 elmex 1.260
3625 root 1.265 ($x, $y)
3626 elmex 1.260 }
3627    
3628 root 1.265 sub update {
3629 elmex 1.260 my ($self) = @_;
3630    
3631 root 1.265 $::WANT_REFRESH++;
3632     }
3633 elmex 1.260
3634 root 1.265 sub add {
3635     my ($self, @children) = @_;
3636 elmex 1.260
3637 root 1.265 $_->{is_toplevel} = 1
3638     for @children;
3639 elmex 1.260
3640 root 1.265 $self->SUPER::add (@children);
3641 elmex 1.260 }
3642    
3643 root 1.265 sub remove {
3644     my ($self, @children) = @_;
3645    
3646     $self->SUPER::remove (@children);
3647 elmex 1.260
3648 root 1.265 delete $self->{is_toplevel}
3649     for @children;
3650 elmex 1.260
3651 root 1.265 while (@children) {
3652     my $w = pop @children;
3653     push @children, $w->children;
3654     $w->set_invisible;
3655     }
3656     }
3657 elmex 1.260
3658 root 1.265 sub on_refresh {
3659     my ($self, $id, $cb) = @_;
3660 elmex 1.260
3661 root 1.265 $self->{refresh_hook}{$id} = $cb;
3662 elmex 1.260 }
3663    
3664 root 1.265 sub on_post_alloc {
3665     my ($self, $id, $cb) = @_;
3666    
3667     $self->{post_alloc_hook}{$id} = $cb;
3668 elmex 1.262 }
3669    
3670 root 1.265 sub draw {
3671 elmex 1.260 my ($self) = @_;
3672    
3673 root 1.265 while ($self->{refresh_hook}) {
3674     $_->()
3675     for values %{delete $self->{refresh_hook}};
3676     }
3677    
3678     if ($self->{realloc}) {
3679 root 1.266 my %queue;
3680 root 1.265 my @queue;
3681 root 1.266 my $widget;
3682 root 1.265
3683 root 1.266 outer:
3684 root 1.265 while () {
3685 root 1.266 if (my $realloc = delete $self->{realloc}) {
3686     for $widget (values %$realloc) {
3687     $widget->{visible} or next; # do not resize invisible widgets
3688 root 1.265
3689 root 1.266 $queue{$widget+0}++ and next; # duplicates are common
3690 root 1.265
3691 root 1.266 push @{ $queue[$widget->{visible}] }, $widget;
3692     }
3693 root 1.265 }
3694    
3695 root 1.266 while () {
3696     @queue or last outer;
3697    
3698     $widget = pop @{ $queue[-1] || [] }
3699     and last;
3700    
3701     pop @queue;
3702     }
3703 root 1.265
3704 root 1.266 delete $queue{$widget+0};
3705 root 1.265
3706     my ($w, $h) = $widget->size_request;
3707    
3708 root 1.280 $w = max $widget->{min_w}, $w + $widget->{padding_x} * 2;
3709     $h = max $widget->{min_h}, $h + $widget->{padding_y} * 2;
3710    
3711     $w = min $widget->{max_w}, $w if exists $widget->{max_w};
3712     $h = min $widget->{max_h}, $h if exists $widget->{max_h};
3713 root 1.265
3714     $w = $widget->{force_w} if exists $widget->{force_w};
3715     $h = $widget->{force_h} if exists $widget->{force_h};
3716    
3717     if ($widget->{req_w} != $w || $widget->{req_h} != $h
3718     || delete $widget->{force_realloc}) {
3719     $widget->{req_w} = $w;
3720     $widget->{req_h} = $h;
3721    
3722     $self->{size_alloc}{$widget+0} = $widget;
3723    
3724     if (my $parent = $widget->{parent}) {
3725 root 1.266 $self->{realloc}{$parent+0} = $parent
3726     unless $queue{$parent+0};
3727    
3728 root 1.265 $parent->{force_size_alloc} = 1;
3729     $self->{size_alloc}{$parent+0} = $parent;
3730     }
3731     }
3732    
3733     delete $self->{realloc}{$widget+0};
3734     }
3735     }
3736 elmex 1.260
3737 root 1.265 while (my $size_alloc = delete $self->{size_alloc}) {
3738     my @queue = sort { $b->{visible} <=> $a->{visible} }
3739     values %$size_alloc;
3740 elmex 1.260
3741 root 1.265 while () {
3742     my $widget = pop @queue || last;
3743 elmex 1.260
3744 root 1.265 my ($w, $h) = @$widget{qw(alloc_w alloc_h)};
3745 elmex 1.260
3746 root 1.265 $w = 0 if $w < 0;
3747     $h = 0 if $h < 0;
3748 elmex 1.260
3749 root 1.265 $w = int $w + 0.5;
3750     $h = int $h + 0.5;
3751 elmex 1.260
3752 root 1.265 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) {
3753 root 1.266 $widget->{old_w} = $widget->{w};
3754     $widget->{old_h} = $widget->{h};
3755    
3756 root 1.265 $widget->{w} = $w;
3757     $widget->{h} = $h;
3758 elmex 1.260
3759 root 1.265 $widget->emit (size_allocate => $w, $h);
3760     }
3761     }
3762     }
3763 elmex 1.260
3764 root 1.265 while ($self->{post_alloc_hook}) {
3765     $_->()
3766     for values %{delete $self->{post_alloc_hook}};
3767 elmex 1.260 }
3768 root 1.265
3769    
3770     glViewport 0, 0, $::WIDTH, $::HEIGHT;
3771     glClearColor +($::CFG->{fow_intensity}) x 3, 1;
3772     glClear GL_COLOR_BUFFER_BIT;
3773    
3774     glMatrixMode GL_PROJECTION;
3775     glLoadIdentity;
3776     glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000;
3777     glMatrixMode GL_MODELVIEW;
3778     glLoadIdentity;
3779    
3780 root 1.267 {
3781     package CFClient::UI::Base;
3782    
3783     ($draw_x, $draw_y, $draw_w, $draw_h) =
3784     (0, 0, $self->{w}, $self->{h});
3785     }
3786    
3787 root 1.265 $self->_draw;
3788 elmex 1.260 }
3789    
3790 elmex 1.262 #############################################################################
3791    
3792 root 1.73 package CFClient::UI;
3793 root 1.51
3794 root 1.113 $ROOT = new CFClient::UI::Root;
3795 root 1.213 $TOOLTIP = new CFClient::UI::Tooltip z => 900;
3796 root 1.51
3797     1
3798 root 1.5