ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.320
Committed: Tue Jul 4 23:23:32 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.319: +33 -38 lines
Log Message:
Get rid of cairo completely (yay!) and of ft2 factually (still need the
library as it included pangofc), by introducing a custom pango opengl
renderer.

Text rendering now no longer requires the distinction between rgba and
grayscale modes, requires much less texture space and memory, and is
faster on accelerated hardware (and possibly with software rendering, too).

All at the cost of only 1200 lines or so.

File Contents

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