ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.72
Committed: Tue Apr 11 18:00:45 2006 UTC (18 years, 1 month ago) by root
Branch: MAIN
Changes since 1.71: +56 -35 lines
Log Message:
*** empty log message ***

File Contents

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