ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.86
Committed: Wed Apr 12 15:35:54 2006 UTC (18 years, 1 month ago) by root
Branch: MAIN
Changes since 1.85: +62 -6 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.73 package CFClient::UI;
2 root 1.8
3 elmex 1.1 use strict;
4 root 1.18
5 root 1.74 use Scalar::Util ();
6     use List::Util ();
7 root 1.18
8 root 1.60 use CFClient;
9 root 1.41
10 root 1.51 our ($FOCUS, $HOVER, $GRAB); # various widgets
11    
12     our $TOPLEVEL;
13     our $BUTTON_STATE;
14    
15 elmex 1.1 # class methods for events
16 root 1.51 sub feed_sdl_key_down_event {
17     $FOCUS->key_down ($_[0]) if $FOCUS;
18     }
19    
20     sub feed_sdl_key_up_event {
21     $FOCUS->key_up ($_[0]) if $FOCUS;
22     }
23    
24     sub feed_sdl_button_down_event {
25     my ($ev) = @_;
26     my ($x, $y) = ($ev->motion_x, $ev->motion_y);
27    
28     if (!$BUTTON_STATE) {
29     my $widget = $TOPLEVEL->find_widget ($x, $y);
30    
31     $GRAB = $widget;
32     $GRAB->update if $GRAB;
33     }
34    
35     $BUTTON_STATE |= 1 << ($ev->button - 1);
36    
37 root 1.58 $GRAB->button_down ($ev, $GRAB->translate ($x, $y)) if $GRAB;
38 root 1.51 }
39    
40     sub feed_sdl_button_up_event {
41     my ($ev) = @_;
42     my ($x, $y) = ($ev->motion_x, $ev->motion_y);
43    
44     my $widget = $GRAB || $TOPLEVEL->find_widget ($x, $y);
45    
46     $BUTTON_STATE &= ~(1 << ($ev->button - 1));
47    
48 root 1.77 $GRAB->button_up ($ev, $GRAB->translate ($x, $y)) if $GRAB;
49 root 1.58
50 root 1.51 if (!$BUTTON_STATE) {
51     my $grab = $GRAB; undef $GRAB;
52     $grab->update if $grab;
53     $GRAB->update if $GRAB;
54     }
55     }
56    
57     sub feed_sdl_motion_event {
58     my ($ev) = @_;
59     my ($x, $y) = ($ev->motion_x, $ev->motion_y);
60    
61     my $widget = $GRAB || $TOPLEVEL->find_widget ($x, $y);
62    
63     if ($widget != $HOVER) {
64     my $hover = $HOVER; $HOVER = $widget;
65    
66     $hover->update if $hover;
67     $HOVER->update if $HOVER;
68     }
69    
70 root 1.58 $HOVER->mouse_motion ($ev, $HOVER->translate ($x, $y)) if $HOVER;
71 root 1.51 }
72 elmex 1.1
73 root 1.73 #############################################################################
74    
75     package CFClient::UI::Base;
76    
77     use strict;
78    
79     use SDL::OpenGL;
80    
81 elmex 1.1 sub new {
82     my $class = shift;
83 root 1.10
84 root 1.79 my $self = bless {
85 root 1.65 x => 0,
86     y => 0,
87     z => 0,
88 root 1.75 w => -1,
89     h => -1,
90 root 1.65 @_
91 root 1.79 }, $class;
92    
93     for (keys %$self) {
94     if (/^connect_(.*)$/) {
95     $self->connect ($1 => delete $self->{$_});
96     }
97     }
98    
99     $self
100 elmex 1.1 }
101    
102 root 1.18 sub move {
103     my ($self, $x, $y, $z) = @_;
104     $self->{x} = $x;
105     $self->{y} = $y;
106     $self->{z} = $z if defined $z;
107     }
108    
109 elmex 1.20 sub needs_redraw {
110     0
111     }
112    
113 root 1.14 sub size_request {
114 elmex 1.36 require Carp;
115     Carp::confess "size_request is abtract";
116     }
117    
118 root 1.75 sub _size_allocate {
119     my ($self, $x, $y, $w, $h) = @_;
120    
121     $self->{x} = $x;
122     $self->{y} = $y;
123    
124     return unless $self->{w} != $w || $self->{h} != $h;
125 root 1.40
126     $self->{w} = $w;
127     $self->{h} = $h;
128 root 1.75
129     1
130     }
131    
132     sub size_allocate {
133     my ($self, $x, $y, $w, $h) = @_;
134    
135     $self->_size_allocate ($x, $y, $w, $h);
136 root 1.14 }
137    
138 root 1.82 # translate global coordinates to local coordinate system
139 root 1.58 sub translate {
140     my ($self, $x, $y) = @_;
141    
142     $self->{parent}->translate ($x - $self->{x}, $y - $self->{y});
143     }
144    
145 elmex 1.1 sub focus_in {
146 root 1.51 my ($self) = @_;
147    
148 root 1.68 return if $FOCUS == $self;
149    
150 root 1.51 my $focus = $FOCUS; $FOCUS = $self;
151     $focus->update if $focus;
152     $FOCUS->update;
153 elmex 1.1 }
154 root 1.4
155 elmex 1.1 sub focus_out {
156 root 1.51 my ($self) = @_;
157 root 1.4
158 root 1.51 return unless $FOCUS == $self;
159 root 1.4
160 root 1.51 my $focus = $FOCUS; undef $FOCUS;
161     $focus->update if $focus; #?
162 elmex 1.1 }
163 root 1.4
164 root 1.51 sub mouse_motion { }
165     sub button_up { }
166     sub key_down { }
167     sub key_up { }
168    
169 root 1.68 sub button_down {
170     my ($self, $ev, $x, $y) = @_;
171    
172     $self->focus_in;
173     }
174    
175 root 1.51 sub w { $_[0]{w} = $_[1] if @_ > 1; $_[0]{w} }
176     sub h { $_[0]{h} = $_[1] if @_ > 1; $_[0]{h} }
177     sub x { $_[0]{x} = $_[1] if @_ > 1; $_[0]{x} }
178     sub y { $_[0]{y} = $_[1] if @_ > 1; $_[0]{y} }
179     sub z { $_[0]{z} = $_[1] if @_ > 1; $_[0]{z} }
180 elmex 1.11
181 elmex 1.1 sub draw {
182 elmex 1.11 my ($self) = @_;
183    
184 root 1.68 return unless $self->{h} && $self->{w};
185    
186 elmex 1.11 glPushMatrix;
187 root 1.12 glTranslate $self->{x}, $self->{y}, 0;
188 elmex 1.11 $self->_draw;
189 root 1.72 glPopMatrix;
190    
191 root 1.51 if ($self == $HOVER) {
192 root 1.73 my ($x, $y) = @$self{qw(x y)};
193 root 1.72
194 root 1.76 glColor 0, 0, 1, 0.2;
195 root 1.50 glEnable GL_BLEND;
196 root 1.74 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
197 root 1.48 glBegin GL_QUADS;
198 root 1.72 glVertex $x , $y;
199     glVertex $x + $self->{w}, $y;
200     glVertex $x + $self->{w}, $y + $self->{h};
201     glVertex $x , $y + $self->{h};
202 root 1.47 glEnd;
203 root 1.50 glDisable GL_BLEND;
204 root 1.47 }
205 elmex 1.11 }
206    
207     sub _draw {
208 root 1.38 my ($self) = @_;
209    
210     warn "no draw defined for $self\n";
211 elmex 1.1 }
212 root 1.4
213 root 1.38 sub find_widget {
214     my ($self, $x, $y) = @_;
215    
216     return $self
217     if $x >= $self->{x} && $x < $self->{x} + $self->{w}
218     && $y >= $self->{y} && $y < $self->{y} + $self->{h};
219    
220     ()
221     }
222    
223 elmex 1.32 sub set_parent {
224     my ($self, $par) = @_;
225    
226     $self->{parent} = $par;
227     Scalar::Util::weaken $self->{parent};
228     }
229    
230     sub get_parent {
231     $_[0]->{parent}
232     }
233    
234     sub update {
235     my ($self) = @_;
236    
237     $self->{parent}->update
238     if $self->{parent};
239 elmex 1.1 }
240 elmex 1.2
241 root 1.72 sub connect {
242     my ($self, $signal, $cb) = @_;
243    
244     push @{ $self->{cb}{$signal} }, $cb;
245     }
246    
247     sub emit {
248     my ($self, $signal, @args) = @_;
249    
250     $_->($self, @args)
251     for @{$self->{cb}{$signal} || []};
252     }
253    
254 root 1.18 sub DESTROY {
255     my ($self) = @_;
256    
257 elmex 1.32 #$self->deactivate;
258 root 1.18 }
259    
260 root 1.39 #############################################################################
261    
262 root 1.73 package CFClient::UI::DrawBG;
263 root 1.68
264 root 1.73 our @ISA = CFClient::UI::Base::;
265 root 1.68
266     use strict;
267     use SDL::OpenGL;
268    
269     sub new {
270     my $class = shift;
271    
272     # range [value, low, high, page]
273    
274     $class->SUPER::new (
275 root 1.76 bg => [0, 0, 0, 0.2],
276 root 1.79 active_bg => [1, 1, 1, 0.5],
277 root 1.68 @_
278     )
279     }
280    
281     sub _draw {
282     my ($self) = @_;
283    
284     my ($w, $h) = @$self{qw(w h)};
285    
286 root 1.76 glEnable GL_BLEND;
287     glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
288 root 1.68 glColor @{ $FOCUS == $self ? $self->{active_bg} : $self->{bg} };
289 root 1.76
290 root 1.68 glBegin GL_QUADS;
291     glVertex 0 , 0;
292     glVertex 0 , $h;
293     glVertex $w, $h;
294     glVertex $w, 0;
295     glEnd;
296 root 1.76
297     glDisable GL_BLEND;
298 root 1.68 }
299    
300     #############################################################################
301    
302 root 1.73 package CFClient::UI::Empty;
303 root 1.66
304 root 1.73 our @ISA = CFClient::UI::Base::;
305 root 1.66
306     sub size_request {
307     (0, 0)
308     }
309    
310 root 1.67 sub draw { }
311 root 1.66
312     #############################################################################
313    
314 root 1.73 package CFClient::UI::Container;
315 elmex 1.15
316 root 1.73 our @ISA = CFClient::UI::Base::;
317 elmex 1.15
318 root 1.38 sub new {
319 root 1.64 my ($class, %arg) = @_;
320    
321     my $children = delete $arg{children} || [];
322 root 1.38
323 root 1.65 my $self = $class->SUPER::new (children => [], %arg);
324 root 1.64 $self->add ($_) for @$children;
325 root 1.38
326     $self
327     }
328    
329     sub add {
330 root 1.77 my ($self, $chld) = @_;
331 root 1.38
332     $chld->set_parent ($self);
333    
334 root 1.66 $self->{children} = [
335 root 1.38 sort { $a->{z} <=> $b->{z} }
336 root 1.66 @{$self->{children}}, $chld
337     ];
338 root 1.38
339 root 1.75 $self->{w} = $self->{h} = -1;
340     $self->update;
341 root 1.38 }
342 root 1.35
343 elmex 1.32 sub remove {
344 root 1.38 my ($self, $widget) = @_;
345    
346     $self->{children} = [ grep $_ != $widget, @{ $self->{children} } ];
347    
348 root 1.75 $self->size_allocate (0, 0, $self->{w}, $self->{h});
349 root 1.38 }
350    
351     sub find_widget {
352     my ($self, $x, $y) = @_;
353    
354 root 1.45 $x -= $self->{x};
355     $y -= $self->{y};
356    
357 root 1.38 my $res;
358    
359 root 1.46 for (reverse @{ $self->{children} }) {
360 root 1.45 $res = $_->find_widget ($x, $y)
361 root 1.38 and return $res;
362     }
363    
364 root 1.46 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
365 elmex 1.32 }
366 elmex 1.15
367 root 1.35 sub _draw {
368     my ($self) = @_;
369    
370 root 1.38 $_->draw for @{$self->{children}};
371 root 1.35 }
372 elmex 1.15
373 root 1.39 #############################################################################
374    
375 root 1.73 package CFClient::UI::Bin;
376 elmex 1.32
377 root 1.73 our @ISA = CFClient::UI::Container::;
378 elmex 1.32
379 root 1.66 sub new {
380     my ($class, %arg) = @_;
381    
382 root 1.73 my $child = (delete $arg{child}) || new CFClient::UI::Empty::;
383 root 1.66
384     $class->SUPER::new (children => [$child], %arg)
385     }
386    
387     sub add {
388     my ($self, $widget) = @_;
389    
390     $self->{children} = [];
391    
392     $self->SUPER::add ($widget);
393     }
394    
395     sub remove {
396     my ($self, $widget) = @_;
397    
398     $self->SUPER::remove ($widget);
399    
400 root 1.73 $self->{children} = [new CFClient::UI::Empty]
401 root 1.66 unless @{$self->{children}};
402     }
403    
404 root 1.39 sub child { $_[0]->{children}[0] }
405 elmex 1.32
406 root 1.38 sub size_request {
407 root 1.68 $_[0]{children}[0]->size_request
408 root 1.38 }
409 elmex 1.32
410 root 1.38 sub size_allocate {
411 root 1.75 my ($self, $x, $y, $w, $h) = @_;
412 root 1.42
413 root 1.75 $self->_size_allocate ($x, $y, $w, $h) or return;
414 root 1.68
415 root 1.75 $self->{children}[0]->size_allocate (0, 0, $w, $h);
416 root 1.38 }
417 elmex 1.32
418 root 1.39 #############################################################################
419    
420 root 1.73 package CFClient::UI::Window;
421 elmex 1.20
422 root 1.73 our @ISA = CFClient::UI::Bin::;
423 elmex 1.20
424     use SDL::OpenGL;
425    
426 root 1.42 sub new {
427 root 1.64 my ($class, %arg) = @_;
428 elmex 1.32
429 root 1.64 my $self = $class->SUPER::new (%arg);
430 elmex 1.32 }
431    
432     sub update {
433     my ($self) = @_;
434 root 1.42
435 root 1.63 # we want to do this delayed...
436 elmex 1.32 $self->render_chld;
437 root 1.42 $self->SUPER::update;
438 elmex 1.20 }
439    
440     sub render_chld {
441     my ($self) = @_;
442    
443     $self->{texture} =
444 root 1.60 CFClient::Texture->new_from_opengl (
445 root 1.42 $self->{w}, $self->{h}, sub { $self->child->draw }
446 elmex 1.20 );
447 elmex 1.36 }
448    
449     sub size_allocate {
450 root 1.75 my ($self, $x, $y, $w, $h) = @_;
451 elmex 1.36
452 root 1.75 $self->_size_allocate ($x, $y, $w, $h) or return;
453 root 1.68
454 root 1.75 $self->child->size_allocate (0, 0, $w, $h);
455 elmex 1.36
456 root 1.42 $self->render_chld;
457 elmex 1.20 }
458    
459     sub _draw {
460     my ($self) = @_;
461    
462 elmex 1.36 my ($w, $h) = ($self->w, $self->h);
463 root 1.29
464 elmex 1.20 my $tex = $self->{texture}
465     or return;
466    
467     glEnable GL_BLEND;
468 root 1.74 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
469 elmex 1.20 glEnable GL_TEXTURE_2D;
470 root 1.35 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
471 elmex 1.20
472 root 1.56 $tex->draw_quad (0, 0, $w, $h);
473 elmex 1.20
474     glDisable GL_BLEND;
475     glDisable GL_TEXTURE_2D;
476     }
477    
478 root 1.39 #############################################################################
479    
480 root 1.73 package CFClient::UI::Frame;
481 elmex 1.15
482 root 1.73 our @ISA = CFClient::UI::Bin::;
483 elmex 1.15
484     use SDL::OpenGL;
485    
486     sub size_request {
487     my ($self) = @_;
488 root 1.39 my $chld = $self->child
489 elmex 1.15 or return (0, 0);
490 root 1.30
491     $chld->move (2, 2);
492    
493 elmex 1.15 map { $_ + 4 } $chld->size_request;
494     }
495    
496 elmex 1.36 sub size_allocate {
497 root 1.75 my ($self, $x, $y, $w, $h) = @_;
498 root 1.42
499 root 1.75 $self->_size_allocate ($x, $y, $w, $h) or return;
500 elmex 1.36
501 root 1.75 $self->child->size_allocate (2, 2, $w - 4, $h - 4);
502 elmex 1.36 }
503    
504 elmex 1.15 sub _draw {
505     my ($self) = @_;
506    
507 root 1.39 my $chld = $self->child;
508 elmex 1.15
509     my ($w, $h) = $chld->size_request;
510    
511     glBegin GL_QUADS;
512 root 1.30 glColor 0, 0, 0;
513 root 1.56 glVertex 0 , 0;
514     glVertex 0 , $h + 4;
515     glVertex $w + 4 , $h + 4;
516     glVertex $w + 4 , 0;
517 elmex 1.15 glEnd;
518    
519 root 1.23 $chld->draw;
520 elmex 1.15 }
521    
522 root 1.39 #############################################################################
523    
524 root 1.73 package CFClient::UI::FancyFrame;
525 elmex 1.31
526 root 1.73 our @ISA = CFClient::UI::Bin::;
527 elmex 1.31
528     use SDL::OpenGL;
529    
530 root 1.41 my @tex =
531 root 1.60 map { new_from_file CFClient::Texture CFClient::find_rcfile $_ }
532 root 1.41 qw(d1_bg.png d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png);
533 elmex 1.34
534     sub size_request {
535     my ($self) = @_;
536 root 1.39
537 root 1.78 return ($self->{user_w}, $self->{user_h}) if $self->{user_w} && $self->{user_h};
538    
539     my ($w, $h) = $self->SUPER::size_request;
540 elmex 1.34
541 root 1.72 $h += $tex[1]->{h};
542     $h += $tex[4]->{h};
543     $w += $tex[2]->{w};
544     $w += $tex[3]->{w};
545 elmex 1.34
546 elmex 1.36 ($w, $h)
547     }
548    
549     sub size_allocate {
550 root 1.75 my ($self, $x, $y, $w, $h) = @_;
551 root 1.68
552 root 1.75 $self->_size_allocate ($x, $y, $w, $h) or return;
553 root 1.40
554 root 1.77 $h -= $tex[1]{h};
555     $h -= $tex[4]{h};
556     $w -= $tex[2]{w};
557     $w -= $tex[3]{w};
558 elmex 1.36
559     $h = $h < 0 ? 0 : $h;
560     $w = $w < 0 ? 0 : $w;
561 root 1.43
562 root 1.66 my $child = $self->child;
563    
564 root 1.75 $child->size_allocate ($tex[3]->{w}, $tex[1]->{h}, $w, $h);
565 elmex 1.34 }
566    
567 root 1.77 sub button_down {
568     my ($self, $ev, $x, $y) = @_;
569    
570     if ($x < $self->{w} && $x >= $self->{w} - $tex[2]{w}
571     && $y < $self->{h} && $y >= $self->{h} - $tex[4]{h}) {
572    
573     my ($ox, $oy) = ($ev->button_x, $ev->button_y);
574     my ($bw, $bh) = ($self->{w}, $self->{h});
575    
576     $self->{motion} = sub {
577     my ($ev, $x, $y) = @_;
578    
579     ($x, $y) = ($ev->motion_x, $ev->motion_y);
580    
581     $self->{user_w} = $bw + $x - $ox;
582     $self->{user_h} = $bh + $y - $oy;
583     $self->update;
584     };
585    
586     } elsif ($x >= 0 && $x < $self->{w}
587     && $y >= 0 && $y < $tex[1]{h}) {
588    
589     my ($ox, $oy) = ($ev->button_x, $ev->button_y);
590     my ($bx, $by) = ($self->{x}, $self->{y});
591    
592     $self->{motion} = sub {
593     my ($ev, $x, $y) = @_;
594    
595     ($x, $y) = ($ev->motion_x, $ev->motion_y);
596    
597     $self->move ($bx + $x - $ox, $by + $y - $oy);
598     $self->update;
599     };
600     }
601     }
602    
603     sub button_up {
604     my ($self, $ev, $x, $y) = @_;
605    
606     delete $self->{motion};
607     }
608    
609     sub mouse_motion {
610     my ($self, $ev, $x, $y) = @_;
611    
612     $self->{motion}->($ev, $x, $y) if $self->{motion};
613     }
614    
615 elmex 1.34 sub _draw {
616     my ($self) = @_;
617    
618 root 1.43 my ($w, $h) = ($self->{w}, $self->{h});
619     my ($cw, $ch) = ($self->child->{w}, $self->child->{h});
620 elmex 1.34
621     glEnable GL_BLEND;
622 root 1.74 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
623 elmex 1.34 glEnable GL_TEXTURE_2D;
624 elmex 1.36 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
625 elmex 1.34
626 root 1.41 my $top = $tex[1];
627 root 1.72 $top->draw_quad (0, 0, $w, $top->{h});
628 elmex 1.34
629 root 1.41 my $left = $tex[3];
630 root 1.72 $left->draw_quad (0, $top->{h}, $left->{w}, $ch);
631 elmex 1.34
632 root 1.41 my $right = $tex[2];
633 root 1.72 $right->draw_quad ($w - $right->{w}, $top->{h}, $right->{w}, $ch);
634 elmex 1.34
635 root 1.41 my $bottom = $tex[4];
636 root 1.72 $bottom->draw_quad (0, $h - $bottom->{h}, $w, $bottom->{h});
637 elmex 1.34
638 root 1.41 my $bg = $tex[0];
639 root 1.76
640 elmex 1.36 glBindTexture GL_TEXTURE_2D, $bg->{name};
641     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
642 elmex 1.34
643 root 1.72 my $rep_x = $cw / $bg->{w};
644     my $rep_y = $ch / $bg->{h};
645 elmex 1.34
646 root 1.72 $bg->draw_quad ($left->{w}, $top->{h}, $cw, $ch);
647 elmex 1.34
648 root 1.76 glDisable GL_TEXTURE_2D;
649 elmex 1.34 glDisable GL_BLEND;
650 elmex 1.36
651 root 1.39 $self->child->draw;
652 elmex 1.36
653 elmex 1.34 }
654 elmex 1.31
655 root 1.39 #############################################################################
656    
657 root 1.73 package CFClient::UI::Table;
658 elmex 1.15
659 root 1.73 our @ISA = CFClient::UI::Base::;
660 elmex 1.15
661 root 1.75 use List::Util qw(max sum);
662    
663 elmex 1.15 use SDL::OpenGL;
664    
665 root 1.78 sub new {
666     my $class = shift;
667    
668     $class->SUPER::new (
669     col_expand => [],
670     @_
671     )
672     }
673    
674 elmex 1.15 sub add {
675     my ($self, $x, $y, $chld) = @_;
676 elmex 1.32
677 root 1.38 $self->{children}[$y][$x] = $chld;
678 elmex 1.32 $chld->set_parent ($self);
679 root 1.75
680     $self->{w} = $self->{h} = -1;
681 elmex 1.32 $self->update;
682 elmex 1.15 }
683    
684 root 1.75 sub get_wh {
685     my ($self) = @_;
686    
687     my (@w, @h);
688 elmex 1.15
689 root 1.75 for my $y (0 .. $#{$self->{children}}) {
690     my $row = $self->{children}[$y]
691     or next;
692 elmex 1.15
693 root 1.75 for my $x (0 .. $#$row) {
694     my $widget = $row->[$x]
695     or next;
696     my ($w, $h) = $widget->size_request;
697 elmex 1.15
698 root 1.75 $w[$x] = max $w[$x], $w;
699     $h[$y] = max $h[$y], $h;
700 elmex 1.17 }
701 elmex 1.15 }
702 root 1.75
703     (\@w, \@h)
704 elmex 1.15 }
705    
706     sub size_request {
707     my ($self) = @_;
708    
709 root 1.75 my ($ws, $hs) = $self->get_wh;
710 elmex 1.15
711 root 1.75 (
712 root 1.78 (sum @$ws),
713     (sum @$hs),
714 root 1.75 )
715     }
716    
717     sub size_allocate {
718     my ($self, $x, $y, $w, $h) = @_;
719    
720     $self->_size_allocate ($x, $y, $w, $h) or return;
721    
722     my ($ws, $hs) = $self->get_wh;
723    
724 root 1.78 my $req_w = sum @$ws;
725     my $req_h = sum @$hs;
726    
727     # TODO: nicer code && do row_expand
728     my @col_expand = @{$self->{col_expand}};
729     @col_expand = (1) x @$ws unless @col_expand;
730     my $col_expand = (sum @col_expand) || 1;
731 elmex 1.15
732 root 1.75 # linearly scale sizes
733 root 1.78 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws;
734     $hs->[$_] *= 1 * $h / $req_h for 0 .. $#$hs;
735 elmex 1.15
736 root 1.75 my $y;
737 elmex 1.15
738 root 1.75 for my $r (0 .. $#{$self->{children}}) {
739     my $row = $self->{children}[$r]
740     or next;
741 elmex 1.15
742     my $x = 0;
743 root 1.75 my $row_h = $hs->[$r];
744    
745     for my $c (0 .. $#$row) {
746     my $col_w = $ws->[$c];
747 elmex 1.15
748 root 1.83 if (my $widget = $row->[$c]) {
749     $widget->size_allocate ($x, $y, $col_w, $row_h);
750     }
751 elmex 1.15
752 root 1.75 $x += $col_w;
753 elmex 1.15 }
754    
755 root 1.75 $y += $row_h;
756     }
757    
758     }
759    
760 root 1.76 sub find_widget {
761     my ($self, $x, $y) = @_;
762    
763     $x -= $self->{x};
764     $y -= $self->{y};
765    
766     my $res;
767    
768     for (grep $_, map @$_, grep $_, @{ $self->{children} }) {
769     $res = $_->find_widget ($x, $y)
770     and return $res;
771     }
772    
773     $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
774     }
775    
776 root 1.75 sub _draw {
777     my ($self) = @_;
778    
779     for (grep $_, @{$self->{children}}) {
780     $_->draw for grep $_, @$_;
781 elmex 1.15 }
782     }
783    
784 root 1.39 #############################################################################
785    
786 root 1.76 package CFClient::UI::HBox;
787    
788     # TODO: wrap into common Box base class
789    
790     our @ISA = CFClient::UI::Container::;
791    
792     sub size_request {
793     my ($self) = @_;
794    
795     my @alloc = map [$_->size_request], @{$self->{children}};
796    
797     (
798     (List::Util::sum map $_->[0], @alloc),
799     (List::Util::max map $_->[1], @alloc),
800     )
801     }
802    
803     sub size_allocate {
804     my ($self, $x, $y, $w, $h) = @_;
805    
806 root 1.86 $self->_size_allocate ($x, $y, $w, $h);
807 root 1.76
808     ($h, $w) = ($w, $h);
809    
810     my $children = $self->{children};
811    
812     my @h = map +($_->size_request)[0], @$children;
813    
814     my $req_h = List::Util::sum @h;
815    
816     if ($req_h > $h) {
817     # ah well, not enough space
818 root 1.78 $_ *= $h / $req_h for @h;
819 root 1.76 } else {
820 root 1.77 my $exp = List::Util::sum map $_->{expand}, @$children;
821     $exp ||= 1;
822 root 1.76
823     for (0 .. $#$children) {
824     my $child = $children->[$_];
825    
826     my $alloc_h = $h[$_];
827 root 1.77 $alloc_h += ($h - $req_h) * $child->{expand} / $exp;
828 root 1.76 $h[$_] = $alloc_h;
829     }
830     }
831    
832     my $y = 0;
833     for (0 .. $#$children) {
834     my $child = $children->[$_];
835     my $h = $h[$_];
836     $child->size_allocate ($y, 0, $h, $w);
837    
838     $y += $h;
839     }
840     }
841    
842     #############################################################################
843    
844 root 1.73 package CFClient::UI::VBox;
845 elmex 1.15
846 root 1.76 # TODO: wrap into common Box base class
847    
848 root 1.73 our @ISA = CFClient::UI::Container::;
849 elmex 1.15
850 root 1.43 sub size_request {
851     my ($self) = @_;
852    
853     my @alloc = map [$_->size_request], @{$self->{children}};
854    
855     (
856     (List::Util::max map $_->[0], @alloc),
857     (List::Util::sum map $_->[1], @alloc),
858     )
859     }
860    
861 elmex 1.36 sub size_allocate {
862 root 1.75 my ($self, $x, $y, $w, $h) = @_;
863 elmex 1.36
864 root 1.86 $self->_size_allocate ($x, $y, $w, $h);
865 root 1.68
866     my $children = $self->{children};
867    
868     my @h = map +($_->size_request)[1], @$children;
869    
870     my $req_h = List::Util::sum @h;
871    
872     if ($req_h > $h) {
873     # ah well, not enough space
874 root 1.78 $_ *= $h / $req_h for @h;
875 root 1.68 } else {
876 root 1.77 my $exp = List::Util::sum map $_->{expand}, @$children;
877     $exp ||= 1;
878 root 1.68
879     for (0 .. $#$children) {
880     my $child = $children->[$_];
881 elmex 1.36
882 root 1.77 $h[$_] += ($h - $req_h) * $child->{expand} / $exp;
883 root 1.68 }
884 elmex 1.36 }
885    
886     my $y = 0;
887 root 1.68 for (0 .. $#$children) {
888     my $child = $children->[$_];
889     my $h = $h[$_];
890 root 1.75 $child->size_allocate (0, $y, $w, $h);
891 elmex 1.36
892 root 1.68 $y += $h;
893 elmex 1.36 }
894     }
895    
896 root 1.39 #############################################################################
897    
898 root 1.73 package CFClient::UI::Label;
899 root 1.10
900 root 1.73 our @ISA = CFClient::UI::Base::;
901 root 1.12
902 root 1.10 use SDL::OpenGL;
903    
904     sub new {
905 root 1.64 my ($class, %arg) = @_;
906 root 1.51
907 root 1.59 my $self = $class->SUPER::new (
908 root 1.76 fg => [1, 1, 1],
909     height => $::FONTSIZE,
910     text => "",
911     align => -1,
912     padding => 2,
913     layout => new CFClient::Layout,
914 root 1.64 %arg
915 root 1.59 );
916 root 1.10
917 root 1.64 $self->set_text ($self->{text});
918 root 1.10
919     $self
920     }
921    
922 root 1.68 sub escape_text {
923     local $_ = $_[1];
924    
925     s/&/&amp;/g;
926     s/>/&gt;/g;
927     s/</&lt;/g;
928    
929     $_[1]
930     }
931    
932 elmex 1.15 sub set_text {
933     my ($self, $text) = @_;
934 root 1.28
935     $self->{text} = $text;
936 root 1.59 $self->{layout}->set_markup ($text);
937 root 1.28
938 root 1.59 delete $self->{texture};
939 root 1.81 # $self->{w} = $self->{h} = -1;
940 root 1.68 $self->update;
941 elmex 1.15 }
942    
943     sub get_text {
944     my ($self, $text) = @_;
945 root 1.28
946 elmex 1.15 $self->{text}
947     }
948    
949 root 1.14 sub size_request {
950     my ($self) = @_;
951    
952 root 1.59 $self->{layout}->set_width;
953 root 1.64 $self->{layout}->set_height ($self->{height});
954 root 1.76 my ($w, $h) = $self->{layout}->size;
955    
956     (
957     $w + $self->{padding} * 2,
958     $h + $self->{padding} * 2,
959     )
960 root 1.59 }
961 root 1.51
962 root 1.59 sub size_allocate {
963 root 1.75 my ($self, $x, $y, $w, $h) = @_;
964 root 1.51
965 root 1.75 $self->_size_allocate ($x, $y, $w, $h) or return;
966 root 1.68
967 root 1.59 delete $self->{texture};
968 root 1.14 }
969    
970 root 1.68 sub update {
971     my ($self) = @_;
972    
973     delete $self->{texture};
974     $self->SUPER::update;
975     }
976    
977 elmex 1.11 sub _draw {
978 root 1.10 my ($self) = @_;
979    
980 root 1.59 my $tex = $self->{texture} ||= do {
981     $self->{layout}->set_width ($self->{w});
982 root 1.81 $self->{layout}->set_height (List::Util::min $self->{h} - $self->{padding} * 2, $self->{height});
983 root 1.74 new_from_layout CFClient::Texture $self->{layout}
984 root 1.59 };
985 root 1.10
986 root 1.12 glEnable GL_BLEND;
987 root 1.74 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
988 root 1.10 glEnable GL_TEXTURE_2D;
989 root 1.28 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
990 root 1.10
991 root 1.68 glColor @{$self->{fg}};
992 root 1.12
993 root 1.72 my $x =
994 root 1.76 $self->{align} < 0 ? $self->{padding}
995     : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding}
996 root 1.74 : ($self->{w} - $tex->{w}) * 0.5;
997 root 1.72
998 root 1.84 glTranslate $x, ($self->{h} - $tex->{h}) * 0.5, 0;
999 root 1.82 $tex->draw_quad (0, 0);
1000 root 1.10
1001 root 1.74 glDisable GL_TEXTURE_2D;
1002 root 1.12 glDisable GL_BLEND;
1003 root 1.10 }
1004    
1005 root 1.39 #############################################################################
1006    
1007 root 1.73 package CFClient::UI::Entry;
1008 elmex 1.31
1009 root 1.73 our @ISA = CFClient::UI::Label::;
1010 elmex 1.31
1011     use SDL;
1012     use SDL::OpenGL;
1013    
1014 root 1.68 sub new {
1015     my $class = shift;
1016    
1017     $class->SUPER::new (
1018     fg => [1, 1, 1],
1019 root 1.76 bg => [0, 0, 0, 0.2],
1020 root 1.79 active_bg => [1, 1, 1, 0.5],
1021 root 1.68 active_fg => [0, 0, 0],
1022     @_
1023     )
1024     }
1025    
1026     sub _set_text {
1027     my ($self, $text) = @_;
1028    
1029     $self->{last_activity} = $::NOW;
1030    
1031     $self->{text} = $text;
1032     $self->{layout}->set_width ($self->{w});
1033 root 1.82 $self->{layout}->set_height (List::Util::min $self->{h} - $self->{padding} * 2, $self->{height});
1034 root 1.72
1035     $text =~ s/./*/g if $self->{hidden};
1036    
1037 root 1.76 $self->{layout}->set_markup ($self->escape_text ($text) . " ");
1038 root 1.68
1039     $text = substr $text, 0, $self->{cursor};
1040     utf8::encode $text;
1041    
1042     @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text);
1043     }
1044    
1045     sub size_request {
1046     my ($self) = @_;
1047    
1048     my ($w, $h) = $self->SUPER::size_request;
1049    
1050     ($w + 1, $h) # add 1 for cursor
1051     }
1052    
1053     sub size_allocate {
1054 root 1.75 my ($self, $x, $y, $w, $h) = @_;
1055 root 1.68
1056 root 1.75 $self->SUPER::size_allocate ($x, $y, $w, $h);
1057 root 1.68
1058     $self->_set_text ($self->{text});
1059     }
1060    
1061     sub set_text {
1062     my ($self, $text) = @_;
1063    
1064     $self->{cursor} = length $text;
1065     $self->_set_text ($text);
1066     $self->update;
1067     }
1068    
1069 elmex 1.31 sub key_down {
1070     my ($self, $ev) = @_;
1071    
1072     my $mod = $ev->key_mod;
1073     my $sym = $ev->key_sym;
1074    
1075     my $uni = $ev->key_unicode;
1076    
1077     my $text = $self->get_text;
1078    
1079     if ($sym == SDLK_BACKSPACE) {
1080 root 1.68 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1081     } elsif ($sym == SDLK_DELETE) {
1082     substr $text, $self->{cursor}, 1, "";
1083     } elsif ($sym == SDLK_LEFT) {
1084     --$self->{cursor} if $self->{cursor};
1085     } elsif ($sym == SDLK_RIGHT) {
1086     ++$self->{cursor} if $self->{cursor} < length $self->{text};
1087 root 1.76 } elsif ($sym == SDLK_HOME) {
1088     $self->{cursor} = 0;
1089     } elsif ($sym == SDLK_END) {
1090     $self->{cursor} = length $text;
1091 elmex 1.31 } elsif ($uni) {
1092 root 1.68 substr $text, $self->{cursor}++, 0, chr $uni;
1093 elmex 1.31 }
1094 root 1.51
1095 root 1.68 $self->_set_text ($text);
1096     $self->update;
1097     }
1098    
1099     sub focus_in {
1100     my ($self) = @_;
1101    
1102     $self->{last_activity} = $::NOW;
1103    
1104     $self->SUPER::focus_in;
1105 elmex 1.31 }
1106    
1107 root 1.51 sub button_down {
1108 root 1.68 my ($self, $ev, $x, $y) = @_;
1109    
1110     $self->SUPER::button_down ($ev, $x, $y);
1111    
1112     my $idx = $self->{layout}->xy_to_index ($x, $y);
1113    
1114     # byte-index to char-index
1115 root 1.76 my $text = $self->{text};
1116 root 1.68 utf8::encode $text;
1117     $self->{cursor} = length substr $text, 0, $idx;
1118 root 1.51
1119 root 1.68 $self->_set_text ($self->{text});
1120     $self->update;
1121 root 1.51 }
1122    
1123 root 1.58 sub mouse_motion {
1124     my ($self, $ev, $x, $y) = @_;
1125 root 1.68 # printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d#
1126 root 1.58 }
1127    
1128 root 1.51 sub _draw {
1129     my ($self) = @_;
1130    
1131 root 1.68 local $self->{fg} = $self->{fg};
1132    
1133 root 1.51 if ($FOCUS == $self) {
1134 root 1.68 glColor @{$self->{active_bg}};
1135     $self->{fg} = $self->{active_fg};
1136 root 1.51 } else {
1137 root 1.68 glColor @{$self->{bg}};
1138 root 1.51 }
1139    
1140 root 1.76 glEnable GL_BLEND;
1141     glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1142 root 1.51 glBegin GL_QUADS;
1143 root 1.68 glVertex 0 , 0;
1144     glVertex 0 , $self->{h};
1145     glVertex $self->{w}, $self->{h};
1146     glVertex $self->{w}, 0;
1147 root 1.51 glEnd;
1148 root 1.76 glDisable GL_BLEND;
1149 root 1.51
1150     $self->SUPER::_draw;
1151 root 1.68
1152     #TODO: force update every cursor change :(
1153     if ($FOCUS == $self && (($::NOW - $self->{last_activity}) & 1023) < 600) {
1154     glColor @{$self->{fg}};
1155     glBegin GL_LINES;
1156     glVertex $self->{cur_x}, $self->{cur_y};
1157     glVertex $self->{cur_x}, $self->{cur_y} + $self->{cur_h};
1158     glEnd;
1159     }
1160     }
1161    
1162     #############################################################################
1163    
1164 root 1.79 package CFClient::UI::Button;
1165    
1166     our @ISA = CFClient::UI::Label::;
1167    
1168     use SDL;
1169     use SDL::OpenGL;
1170    
1171 elmex 1.85 my @tex =
1172     map { new_from_file CFClient::Texture CFClient::find_rcfile $_ }
1173     qw(b1_button_active.png);
1174    
1175 root 1.79 sub new {
1176     my $class = shift;
1177    
1178     $class->SUPER::new (
1179     padding => 4,
1180     fg => [1, 1, 1],
1181     bg => [1, 1, 1, 0.2],
1182     active_fg => [1, 1, 0],
1183 elmex 1.85 # active_bg => [0, 0, 0, 0.5],
1184     # border_fg => [1, 1, 0],
1185 root 1.79 @_
1186     )
1187     }
1188    
1189     sub button_up {
1190     my ($self, $ev, $x, $y) = @_;
1191    
1192     if ($x >= 0 && $x < $self->{w}
1193     && $y >= 0 && $y < $self->{h}) {
1194     $self->emit ("activate");
1195     }
1196     }
1197    
1198     sub _draw {
1199     my ($self) = @_;
1200    
1201     local $self->{fg} = $self->{fg};
1202 elmex 1.85 my $tex = $tex[0];
1203 root 1.79
1204     glEnable GL_BLEND;
1205 elmex 1.85 glEnable GL_TEXTURE_2D;
1206 root 1.79 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1207    
1208     if ($GRAB == $self) {
1209     $self->{fg} = $self->{active_fg};
1210     }
1211    
1212 elmex 1.85 glBindTexture GL_TEXTURE_2D, $tex->{name};
1213     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1214    
1215     $tex->draw_quad (0, 0, $self->{w}, $self->{h});
1216    
1217     glDisable GL_TEXTURE_2D;
1218 root 1.79 glDisable GL_BLEND;
1219    
1220     $self->SUPER::_draw;
1221     }
1222    
1223     #############################################################################
1224    
1225 root 1.86 package CFClient::UI::CheckBox;
1226    
1227     our @ISA = CFClient::UI::DrawBG::;
1228    
1229     use SDL;
1230     use SDL::OpenGL;
1231    
1232     my @tex =
1233     map { new_from_file CFClient::Texture CFClient::find_rcfile $_ }
1234     qw(b1_button_active.png);
1235    
1236     sub new {
1237     my $class = shift;
1238    
1239     $class->SUPER::new (
1240     padding => 4,
1241     fg => [1, 1, 1],
1242     active_fg => [1, 1, 0],
1243     state => 0,
1244     @_
1245     )
1246     }
1247    
1248     sub button_down {
1249     my ($self, $ev, $x, $y) = @_;
1250    
1251     if ($x >= $self->{padding} && $x < $self->{w} - $self->{padding}
1252     && $y >= $self->{padding} && $y < $self->{h} - $self->{padding}) {
1253     $self->{state} = !$self->{state};
1254     $self->emit ("changed");
1255     }
1256     }
1257    
1258     sub _draw {
1259     my ($self) = @_;
1260    
1261     local $self->{fg} = $self->{fg};
1262     my $tex = $tex[0];
1263    
1264     glEnable GL_BLEND;
1265     glEnable GL_TEXTURE_2D;
1266     glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1267    
1268     if ($GRAB == $self) {
1269     $self->{fg} = $self->{active_fg};
1270     }
1271    
1272     glBindTexture GL_TEXTURE_2D, $tex->{name};
1273     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1274    
1275     $tex->draw_quad (0, 0, $self->{w}, $self->{h});
1276    
1277     glDisable GL_TEXTURE_2D;
1278     glDisable GL_BLEND;
1279    
1280     $self->SUPER::_draw;
1281     }
1282    
1283     #############################################################################
1284    
1285 root 1.73 package CFClient::UI::Slider;
1286 root 1.68
1287     use strict;
1288    
1289     use SDL::OpenGL;
1290    
1291 root 1.73 our @ISA = CFClient::UI::DrawBG::;
1292 root 1.68
1293     sub new {
1294     my $class = shift;
1295    
1296     # range [value, low, high, page]
1297    
1298 root 1.76 my $self = $class->SUPER::new (
1299 root 1.68 fg => [1, 1, 1],
1300     active_fg => [0, 0, 0],
1301     range => [0, 0, 100, 10],
1302 root 1.76 req_w => 40,
1303     req_h => 10,
1304     vertical => 0,
1305 root 1.68 @_
1306 root 1.76 );
1307    
1308     $self
1309     }
1310    
1311     sub size_request {
1312     my ($self) = @_;
1313    
1314     my $w = $self->{req_w};
1315     my $h = $self->{req_h};
1316    
1317     $self->{vertical} ? ($h, $w) : ($w, $h)
1318 root 1.68 }
1319    
1320 root 1.69 sub button_down {
1321     my ($self, $ev, $x, $y) = @_;
1322    
1323     $self->SUPER::button_down ($ev, $x, $y);
1324     $self->mouse_motion ($ev, $x, $y);
1325     }
1326    
1327     sub mouse_motion {
1328     my ($self, $ev, $x, $y) = @_;
1329    
1330     if ($GRAB == $self) {
1331     my ($value, $lo, $hi, $page) = @{$self->{range}};
1332    
1333 root 1.71 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w});
1334    
1335     $x = $x * ($hi - $lo) / $w + $lo;
1336 root 1.69 $x = $lo if $x < $lo;
1337     $x = $hi - $page if $x > $hi - $page;
1338     $self->{range}[0] = $x;
1339    
1340 root 1.72 $self->emit (changed => $x);
1341 root 1.69 $self->update;
1342     }
1343     }
1344    
1345 root 1.68 sub _draw {
1346     my ($self) = @_;
1347    
1348     $self->SUPER::_draw ();
1349    
1350     my ($w, $h) = @$self{qw(w h)};
1351    
1352     if ($self->{vertical}) {
1353     # draw a vertical slider like a rotated horizontal slider
1354    
1355     glRotate 90, 0, 0, 1;
1356 root 1.71 glTranslate 0, -$self->{w}, 0;
1357 root 1.68
1358     ($w, $h) = ($h, $w);
1359     }
1360    
1361     my $fg = $FOCUS == $self ? $self->{active_fg} : $self->{fg};
1362     my $bg = $FOCUS == $self ? $self->{active_bg} : $self->{bg};
1363    
1364 root 1.69 my ($value, $lo, $hi, $page) = @{$self->{range}};
1365    
1366     $page = int $page * $w / ($hi - $lo);
1367     $value = int +($value - $lo) * $w / ($hi - $lo);
1368    
1369     $w -= $page;
1370     $page &= ~1;
1371     glTranslate $page * 0.5, 0, 0;
1372 root 1.80 $page ||= 2;
1373 root 1.69
1374 root 1.68 glColor @$fg;
1375     glBegin GL_LINES;
1376     glVertex 0, 0; glVertex 0, $h;
1377     glVertex $w - 1, 0; glVertex $w - 1, $h;
1378     glVertex 0, $h * 0.5; glVertex $w, $h * 0.5;
1379     glEnd;
1380 root 1.69
1381     my $knob_a = $value - $page * 0.5;
1382     my $knob_b = $value + $page * 0.5;
1383    
1384     glBegin GL_QUADS;
1385     glColor @$fg;
1386     glVertex $knob_a, 0;
1387     glVertex $knob_a, $h;
1388     glVertex $knob_b, $h;
1389     glVertex $knob_b, 0;
1390    
1391     if ($knob_a < $knob_b - 2) {
1392     glColor @$bg;
1393     glVertex $knob_a + 1, 1;
1394     glVertex $knob_a + 1, $h - 1;
1395     glVertex $knob_b - 1, $h - 1;
1396     glVertex $knob_b - 1, 1;
1397     }
1398     glEnd;
1399 root 1.51 }
1400    
1401 root 1.39 #############################################################################
1402    
1403 root 1.73 package CFClient::UI::MapWidget;
1404 root 1.4
1405 elmex 1.2 use strict;
1406 elmex 1.7
1407 root 1.25 use List::Util qw(min max);
1408 elmex 1.2
1409 root 1.16 use SDL;
1410 elmex 1.2 use SDL::OpenGL;
1411    
1412 root 1.73 our @ISA = CFClient::UI::Base::;
1413 root 1.25
1414 root 1.64 sub new {
1415     my $class = shift;
1416    
1417 root 1.65 $class->SUPER::new (
1418     z => -1,
1419     list => (glGenLists 1),
1420     @_
1421     )
1422 root 1.64 }
1423    
1424 elmex 1.2 sub key_down {
1425     print "MAPKEYDOWN\n";
1426     }
1427    
1428     sub key_up {
1429     }
1430    
1431 elmex 1.36 sub size_request {
1432 root 1.52 (
1433 root 1.77 1 + 32 * int $::WIDTH / 32,
1434     1 + 32 * int $::HEIGHT / 32,
1435 root 1.52 )
1436 elmex 1.36 }
1437    
1438 root 1.65 sub update {
1439     my ($self) = @_;
1440    
1441     $self->{need_update} = 1;
1442 root 1.74 $self->SUPER::update;
1443 root 1.65 }
1444    
1445 root 1.77 sub draw {
1446 root 1.21 my ($self) = @_;
1447    
1448 root 1.65 if (delete $self->{need_update}) {
1449     glNewList $self->{list}, GL_COMPILE;
1450 root 1.25
1451 root 1.65 my $mx = $::CONN->{mapx};
1452     my $my = $::CONN->{mapy};
1453 root 1.25
1454 root 1.65 my $map = $::CONN->{map};
1455 root 1.25
1456 root 1.65 my ($xofs, $yofs);
1457 root 1.25
1458 root 1.65 my $sw = 1 + int $::WIDTH / 32;
1459     my $sh = 1 + int $::HEIGHT / 32;
1460 root 1.25
1461 root 1.65 if ($::CONN->{mapw} > $sw) {
1462     $xofs = $mx + ($::CONN->{mapw} - $sw) * 0.5;
1463     } else {
1464     $xofs = $self->{xofs} = min $mx, max $mx + $::CONN->{mapw} - $sw + 1, $self->{xofs};
1465     }
1466 root 1.25
1467 root 1.65 if ($::CONN->{maph} > $sh) {
1468     $yofs = $my + ($::CONN->{maph} - $sh) * 0.5;
1469     } else {
1470     $yofs = $self->{yofs} = min $my, max $my + $::CONN->{maph} - $sh + 1, $self->{yofs};
1471     }
1472 root 1.35
1473 root 1.65 glEnable GL_TEXTURE_2D;
1474     glEnable GL_BLEND;
1475 root 1.74 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1476 root 1.65 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1477 elmex 1.2
1478 root 1.65 my $sw4 = ($sw + 3) & ~3;
1479     my $darkness = "\x00" x ($sw4 * $sh);
1480 elmex 1.2
1481 root 1.65 for my $x (0 .. $sw - 1) {
1482     my $row = $map->[$x + $xofs];
1483     for my $y (0 .. $sh - 1) {
1484    
1485     my $cell = $row->[$y + $yofs]
1486     or next;
1487    
1488     my $dark = $cell->[0];
1489     if ($dark < 0) {
1490     substr $darkness, $y * $sw4 + $x, 1, chr 224;
1491     } else {
1492     substr $darkness, $y * $sw4 + $x, 1, chr 255 - $dark;
1493     }
1494    
1495     for my $num (grep $_, @$cell[1,2,3]) {
1496     my $tex = $::CONN->{face}[$num]{texture} || next;
1497    
1498 root 1.72 my ($w, $h) = @$tex{qw(w h)};
1499 root 1.19
1500 root 1.65 $tex->draw_quad (($x + 1) * 32 - $w, ($y + 1) * 32 - $h, $w, $h);
1501     }
1502 elmex 1.2 }
1503     }
1504    
1505 root 1.35 # if (1) { # higher quality darkness
1506     # $lighting =~ s/(.)/$1$1$1/gs;
1507     # my $pb = new_from_data Gtk2::Gdk::Pixbuf $lighting, "rgb", 0, 8, $sw4, $sh, $sw4 * 3;
1508     #
1509     # $pb = $pb->scale_simple ($sw4 * 0.5, $sh * 0.5, "bilinear");
1510     #
1511     # $lighting = $pb->get_pixels;
1512     # $lighting =~ s/(.)../$1/gs;
1513     # }
1514    
1515 root 1.65 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
1516    
1517     $darkness = new CFClient::Texture
1518 root 1.74 w => $sw4,
1519     h => $sh,
1520 root 1.65 data => $darkness,
1521     internalformat => GL_ALPHA,
1522     format => GL_ALPHA;
1523 root 1.57
1524 root 1.65 glColor 0.45, 0.45, 0.45, 1;
1525     $darkness->draw_quad (0, 0, $sw4 * 32, $sh * 32);
1526 root 1.35
1527 root 1.65 glDisable GL_TEXTURE_2D;
1528     glDisable GL_BLEND;
1529 root 1.35
1530 root 1.65 glEndList;
1531     }
1532    
1533     glCallList $self->{list};
1534 elmex 1.2 }
1535    
1536 root 1.16 my %DIR = (
1537     SDLK_KP8, [1, "north"],
1538 root 1.18 SDLK_KP9, [2, "northeast"],
1539 root 1.16 SDLK_KP6, [3, "east"],
1540     SDLK_KP3, [4, "southeast"],
1541     SDLK_KP2, [5, "south"],
1542     SDLK_KP1, [6, "southwest"],
1543     SDLK_KP4, [7, "west"],
1544     SDLK_KP7, [8, "northwest"],
1545 root 1.18
1546     SDLK_UP, [1, "north"],
1547     SDLK_RIGHT, [3, "east"],
1548     SDLK_DOWN, [5, "south"],
1549     SDLK_LEFT, [7, "west"],
1550 root 1.16 );
1551    
1552     sub key_down {
1553     my ($self, $ev) = @_;
1554    
1555     my $mod = $ev->key_mod;
1556     my $sym = $ev->key_sym;
1557    
1558     if ($sym == SDLK_KP5) {
1559     $::CONN->send ("command stay fire");
1560 elmex 1.85 } elsif ($sym == SDLK_a) {
1561     $::CONN->send ("command apply");
1562 root 1.16 } elsif (exists $DIR{$sym}) {
1563     if ($mod & KMOD_SHIFT) {
1564 root 1.18 $self->{shft}++;
1565 root 1.16 $::CONN->send ("command fire $DIR{$sym}[0]");
1566     } elsif ($mod & KMOD_CTRL) {
1567 root 1.18 $self->{ctrl}++;
1568 root 1.16 $::CONN->send ("command run $DIR{$sym}[0]");
1569     } else {
1570 root 1.18 $::CONN->send ("command $DIR{$sym}[1]");
1571 root 1.16 }
1572     }
1573     }
1574    
1575     sub key_up {
1576     my ($self, $ev) = @_;
1577    
1578     my $mod = $ev->key_mod;
1579     my $sym = $ev->key_sym;
1580    
1581 root 1.18 if (!($mod & KMOD_SHIFT) && delete $self->{shft}) {
1582     $::CONN->send ("command fire_stop");
1583     }
1584     if (!($mod & KMOD_CTRL ) && delete $self->{ctrl}) {
1585     $::CONN->send ("command run_stop");
1586 root 1.16 }
1587     }
1588    
1589 root 1.39 #############################################################################
1590    
1591 root 1.73 package CFClient::UI::Animator;
1592 root 1.35
1593     use SDL::OpenGL;
1594    
1595 root 1.73 our @ISA = CFClient::UI::Bin::;
1596 root 1.35
1597     sub moveto {
1598     my ($self, $x, $y) = @_;
1599    
1600     $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
1601 root 1.56 $self->{speed} = 0.001;
1602 root 1.35 $self->{time} = 1;
1603    
1604     ::animation_start $self;
1605     }
1606    
1607     sub animate {
1608     my ($self, $interval) = @_;
1609    
1610     $self->{time} -= $interval * $self->{speed};
1611     if ($self->{time} <= 0) {
1612     $self->{time} = 0;
1613     ::animation_stop $self;
1614     }
1615    
1616     my ($x0, $y0, $x1, $y1) = @{$self->{moveto}};
1617    
1618     $self->{x} = $x0 * $self->{time} + $x1 * (1 - $self->{time});
1619     $self->{y} = $y0 * $self->{time} + $y1 * (1 - $self->{time});
1620     }
1621    
1622     sub _draw {
1623     my ($self) = @_;
1624    
1625     glPushMatrix;
1626 root 1.51 glRotate $self->{time} * 1000, 0, 1, 0;
1627 root 1.38 $self->{children}[0]->draw;
1628 root 1.35 glPopMatrix;
1629     }
1630    
1631 root 1.51 #############################################################################
1632    
1633 root 1.73 package CFClient::UI::Toplevel;
1634 root 1.51
1635 root 1.73 our @ISA = CFClient::UI::Container::;
1636 root 1.51
1637     sub size_request {
1638     ($::WIDTH, $::HEIGHT)
1639     }
1640    
1641     sub size_allocate {
1642 root 1.75 my ($self, $x, $y, $w, $h) = @_;
1643 root 1.51
1644 root 1.75 $self->_size_allocate ($x, $y, $w, $h);
1645 root 1.51
1646 root 1.75 $_->size_allocate ($_->{x}, $_->{y}, $_->size_request)
1647 root 1.51 for @{$self->{children}};
1648     }
1649    
1650 root 1.58 sub translate {
1651     my ($self, $x, $y) = @_;
1652    
1653     ($x, $y)
1654     }
1655    
1656 root 1.51 sub update {
1657     my ($self) = @_;
1658    
1659 root 1.75 $self->size_allocate (0, 0, $::WIDTH, $::HEIGHT);
1660 root 1.51 ::refresh ();
1661     }
1662    
1663     sub add {
1664     my ($self, $widget) = @_;
1665    
1666     $self->SUPER::add ($widget);
1667    
1668 root 1.75 $widget->size_allocate ($widget->{x}, $widget->{y}, $widget->size_request);
1669 root 1.51 }
1670    
1671     sub draw {
1672     my ($self) = @_;
1673    
1674     $self->_draw;
1675     }
1676    
1677     #############################################################################
1678    
1679 root 1.73 package CFClient::UI;
1680 root 1.51
1681 root 1.73 $TOPLEVEL = new CFClient::UI::Toplevel;
1682 root 1.51
1683     1
1684 root 1.5