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