ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.73
Committed: Tue Apr 11 18:06:53 2006 UTC (18 years, 2 months ago) by root
Branch: MAIN
Changes since 1.72: +44 -39 lines
Log Message:
CFClient::Widget => CFClient::UI

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