ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.302
Committed: Mon Jun 12 13:26:14 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.301: +30 -23 lines
Log Message:
first round of npc dialog window, some ui bugfixes

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