ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.303
Committed: Tue Jun 13 10:28:38 2006 UTC (17 years, 11 months ago) by elmex
Branch: MAIN
Changes since 1.302: +63 -1 lines
Log Message:
implemented close button for frames

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