ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.266
Committed: Thu Jun 1 03:42:58 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.265: +27 -11 lines
Log Message:
optimised layout algorithm to skip layouting the same widget twice. use counting sort to speed up sorting. do not redraw labels unecessarily

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