ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.298
Committed: Wed Jun 7 07:00:30 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.297: +36 -13 lines
Log Message:
improve spell list

File Contents

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