ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.265
Committed: Thu Jun 1 02:59:46 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.264: +323 -323 lines
Log Message:
*** empty log message ***

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