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, 1 month ago) by root
Branch: MAIN
Changes since 1.72: +44 -39 lines
Log Message:
CFClient::Widget => CFClient::UI

File Contents

# Content
1 package CFClient::UI;
2
3 use strict;
4
5 use Scalar::Util;
6
7 use CFClient;
8
9 our ($FOCUS, $HOVER, $GRAB); # various widgets
10
11 our $TOPLEVEL;
12 our $BUTTON_STATE;
13
14 # class methods for events
15 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 $GRAB->button_down ($ev, $GRAB->translate ($x, $y)) if $GRAB;
37 }
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 $GRAB->button_down ($ev, $GRAB->translate ($x, $y)) if $GRAB;
48
49 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 $HOVER->mouse_motion ($ev, $HOVER->translate ($x, $y)) if $HOVER;
70 }
71
72 #############################################################################
73
74 package CFClient::UI::Base;
75
76 use strict;
77
78 use SDL::OpenGL;
79
80 sub new {
81 my $class = shift;
82
83 bless {
84 x => 0,
85 y => 0,
86 z => 0,
87 @_
88 }, $class
89 }
90
91 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 sub needs_redraw {
99 0
100 }
101
102 sub size_request {
103 require Carp;
104 Carp::confess "size_request is abtract";
105 }
106
107 sub size_allocate {
108 my ($self, $w, $h) = @_;
109
110 $self->{w} = $w;
111 $self->{h} = $h;
112 }
113
114 # 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 sub focus_in {
122 my ($self) = @_;
123
124 return if $FOCUS == $self;
125
126 my $focus = $FOCUS; $FOCUS = $self;
127 $focus->update if $focus;
128 $FOCUS->update;
129 }
130
131 sub focus_out {
132 my ($self) = @_;
133
134 return unless $FOCUS == $self;
135
136 my $focus = $FOCUS; undef $FOCUS;
137 $focus->update if $focus; #?
138 }
139
140 sub mouse_motion { }
141 sub button_up { }
142 sub key_down { }
143 sub key_up { }
144
145 sub button_down {
146 my ($self, $ev, $x, $y) = @_;
147
148 $self->focus_in;
149 }
150
151 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
157 sub draw {
158 my ($self) = @_;
159
160 return unless $self->{h} && $self->{w};
161
162 glPushMatrix;
163 glTranslate $self->{x}, $self->{y}, 0;
164 $self->_draw;
165 glPopMatrix;
166
167 if ($self == $HOVER) {
168 my ($x, $y) = @$self{qw(x y)};
169
170 glColor 1, 1, 1, 0.1;
171 glEnable GL_BLEND;
172 glBegin GL_QUADS;
173 glVertex $x , $y;
174 glVertex $x + $self->{w}, $y;
175 glVertex $x + $self->{w}, $y + $self->{h};
176 glVertex $x , $y + $self->{h};
177 glEnd;
178 glDisable GL_BLEND;
179 }
180 }
181
182 sub _draw {
183 my ($self) = @_;
184
185 warn "no draw defined for $self\n";
186 }
187
188 sub bbox {
189 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 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 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 }
228
229 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 sub DESTROY {
243 my ($self) = @_;
244
245 #$self->deactivate;
246 }
247
248 #############################################################################
249
250 package CFClient::UI::DrawBG;
251
252 our @ISA = CFClient::UI::Base::;
253
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 package CFClient::UI::Empty;
286
287 our @ISA = CFClient::UI::Base::;
288
289 sub size_request {
290 (0, 0)
291 }
292
293 sub draw { }
294
295 #############################################################################
296
297 package CFClient::UI::Container;
298
299 our @ISA = CFClient::UI::Base::;
300
301 sub new {
302 my ($class, %arg) = @_;
303
304 my $children = delete $arg{children} || [];
305
306 my $self = $class->SUPER::new (children => [], %arg);
307 $self->add ($_) for @$children;
308
309 $self
310 }
311
312 sub add {
313 my ($self, $chld, $expand) = @_;
314
315 $chld->{expand} = $expand;
316 $chld->set_parent ($self);
317
318 $self->{children} = [
319 sort { $a->{z} <=> $b->{z} }
320 @{$self->{children}}, $chld
321 ];
322
323 $self->size_allocate ($self->{w}, $self->{h})
324 if $self->{w}; #TODO: check for "realised state"
325 }
326
327 sub remove {
328 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 $x -= $self->{x};
339 $y -= $self->{y};
340
341 my $res;
342
343 for (reverse @{ $self->{children} }) {
344 $res = $_->find_widget ($x, $y)
345 and return $res;
346 }
347
348 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
349 }
350
351 sub _draw {
352 my ($self) = @_;
353
354 $_->draw for @{$self->{children}};
355 }
356
357 #############################################################################
358
359 package CFClient::UI::Bin;
360
361 our @ISA = CFClient::UI::Container::;
362
363 sub new {
364 my ($class, %arg) = @_;
365
366 my $child = (delete $arg{child}) || new CFClient::UI::Empty::;
367
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 $self->{children} = [new CFClient::UI::Empty]
385 unless @{$self->{children}};
386 }
387
388 sub child { $_[0]->{children}[0] }
389
390 sub size_request {
391 $_[0]{children}[0]->size_request
392 }
393
394 sub size_allocate {
395 my ($self, $w, $h) = @_;
396
397 return unless $self->{w} != $w || $self->{h} != $h;
398
399 $self->SUPER::size_allocate ($w, $h);
400 $self->{children}[0]->size_allocate ($w, $h);
401 }
402
403 #############################################################################
404
405 package CFClient::UI::Window;
406
407 our @ISA = CFClient::UI::Bin::;
408
409 use SDL::OpenGL;
410
411 sub new {
412 my ($class, %arg) = @_;
413
414 my $self = $class->SUPER::new (%arg);
415 }
416
417 sub update {
418 my ($self) = @_;
419
420 # we want to do this delayed...
421 $self->render_chld;
422 $self->SUPER::update;
423 }
424
425 sub render_chld {
426 my ($self) = @_;
427
428 $self->{texture} =
429 CFClient::Texture->new_from_opengl (
430 $self->{w}, $self->{h}, sub { $self->child->draw }
431 );
432 }
433
434 sub size_allocate {
435 my ($self, $w, $h) = @_;
436
437 return unless $self->{w} != $w || $self->{h} != $h;
438
439 $self->{w} = $w;
440 $self->{h} = $h;
441
442 $self->child->size_allocate ($w, $h);
443
444 $self->render_chld;
445 }
446
447 sub _draw {
448 my ($self) = @_;
449
450 my ($w, $h) = ($self->w, $self->h);
451
452 my $tex = $self->{texture}
453 or return;
454
455 glEnable GL_BLEND;
456 glEnable GL_TEXTURE_2D;
457 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
458
459 $tex->draw_quad (0, 0, $w, $h);
460
461 glDisable GL_BLEND;
462 glDisable GL_TEXTURE_2D;
463 }
464
465 #############################################################################
466
467 package CFClient::UI::Frame;
468
469 our @ISA = CFClient::UI::Bin::;
470
471 use SDL::OpenGL;
472
473 sub size_request {
474 my ($self) = @_;
475 my $chld = $self->child
476 or return (0, 0);
477
478 $chld->move (2, 2);
479
480 map { $_ + 4 } $chld->size_request;
481 }
482
483 sub size_allocate {
484 my ($self, $w, $h) = @_;
485
486 return unless $self->{w} != $w || $self->{h} != $h;
487
488 $self->{w} = $w;
489 $self->{h} = $h;
490
491 $self->child->size_allocate ($w - 4, $h - 4);
492 $self->child->move (2, 2);
493 }
494
495 sub _draw {
496 my ($self) = @_;
497
498 my $chld = $self->child;
499
500 my ($w, $h) = $chld->size_request;
501
502 glBegin GL_QUADS;
503 glColor 0, 0, 0;
504 glVertex 0 , 0;
505 glVertex 0 , $h + 4;
506 glVertex $w + 4 , $h + 4;
507 glVertex $w + 4 , 0;
508 glEnd;
509
510 $chld->draw;
511 }
512
513 #############################################################################
514
515 package CFClient::UI::FancyFrame;
516
517 our @ISA = CFClient::UI::Bin::;
518
519 use SDL::OpenGL;
520
521 my @tex =
522 map { new_from_file CFClient::Texture CFClient::find_rcfile $_ }
523 qw(d1_bg.png d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png);
524
525 sub size_request {
526 my ($self) = @_;
527
528 my ($w, $h) = $self->SUPER::size_request;
529
530 $h += $tex[1]->{h};
531 $h += $tex[4]->{h};
532 $w += $tex[2]->{w};
533 $w += $tex[3]->{w};
534
535 ($w, $h)
536 }
537
538 sub size_allocate {
539 my ($self, $w, $h) = @_;
540
541 return unless $self->{w} != $w || $self->{h} != $h;
542
543 $self->SUPER::size_allocate ($w, $h);
544
545 $h -= $tex[1]->{h};
546 $h -= $tex[4]->{h};
547 $w -= $tex[2]->{w};
548 $w -= $tex[3]->{w};
549
550 $h = $h < 0 ? 0 : $h;
551 $w = $w < 0 ? 0 : $w;
552
553 my $child = $self->child;
554
555 $child->size_allocate ($w, $h);
556 $child->move ($tex[3]->{w}, $tex[1]->{h});
557 }
558
559 sub _draw {
560 my ($self) = @_;
561
562 my ($w, $h) = ($self->{w}, $self->{h});
563 my ($cw, $ch) = ($self->child->{w}, $self->child->{h});
564
565 glEnable GL_BLEND;
566 glEnable GL_TEXTURE_2D;
567 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
568 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
569
570 my $top = $tex[1];
571 $top->draw_quad (0, 0, $w, $top->{h});
572
573 my $left = $tex[3];
574 $left->draw_quad (0, $top->{h}, $left->{w}, $ch);
575
576 my $right = $tex[2];
577 $right->draw_quad ($w - $right->{w}, $top->{h}, $right->{w}, $ch);
578
579 my $bottom = $tex[4];
580 $bottom->draw_quad (0, $h - $bottom->{h}, $w, $bottom->{h});
581
582 my $bg = $tex[0];
583 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
588 my $rep_x = $cw / $bg->{w};
589 my $rep_y = $ch / $bg->{h};
590
591 $bg->draw_quad ($left->{w}, $top->{h}, $cw, $ch);
592
593 glDisable GL_BLEND;
594 glDisable GL_TEXTURE_2D;
595
596 $self->child->draw;
597
598 }
599
600 #############################################################################
601
602 package CFClient::UI::Table;
603
604 our @ISA = CFClient::UI::Base::;
605
606 use SDL::OpenGL;
607
608 sub add {
609 my ($self, $x, $y, $chld) = @_;
610 my $old_chld = $self->{children}[$y][$x];
611
612 $self->{children}[$y][$x] = $chld;
613 $chld->set_parent ($self);
614 $self->update;
615 }
616
617 sub max_row_height {
618 my ($self, $row) = @_;
619
620 my $hs = 0;
621 for (my $xi = 0; $xi <= $#{$self->{children}->[$row] || []}; $xi++) {
622 my $c = $self->{children}->[$row]->[$xi];
623 if ($c) {
624 my ($w, $h) = $c->size_request;
625 if ($hs < $h) { $hs = $h }
626 }
627 }
628 return $hs;
629 }
630
631 sub max_col_width {
632 my ($self, $col) = @_;
633
634 my $ws = 0;
635 for (my $yi = 0; $yi <= $#{$self->{children} || []}; $yi++) {
636 my $c = ($self->{children}->[$yi] || [])->[$col];
637 if ($c) {
638 my ($w, $h) = $c->size_request;
639 if ($ws < $w) { $ws = $w }
640 }
641 }
642 return $ws;
643 }
644
645 sub size_request {
646 my ($self) = @_;
647
648 my ($hs, $ws) = (0, 0);
649
650 for (my $yi = 0; $yi <= $#{$self->{children}}; $yi++) {
651 $hs += $self->max_row_height ($yi);
652 }
653
654 for (my $yi = 0; $yi <= $#{$self->{children}}; $yi++) {
655 my $wm = 0;
656 for (my $xi = 0; $xi <= $#{$self->{children}->[$yi]}; $xi++) {
657 $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 for (my $yi = 0; $yi <= $#{$self->{children}}; $yi++) {
670 my $x = 0;
671
672 for (my $xi = 0; $xi <= $#{$self->{children}->[$yi]}; $xi++) {
673
674 my $c = $self->{children}->[$yi]->[$xi];
675 if ($c) {
676 $c->move ($x, $y, 0); #TODO: Move to size_request
677 $c->draw if $c;
678 }
679
680 $x += $self->max_col_width ($xi);
681 }
682
683 $y += $self->max_row_height ($yi);
684 }
685 }
686
687 #############################################################################
688
689 package CFClient::UI::VBox;
690
691 our @ISA = CFClient::UI::Container::;
692
693 use SDL::OpenGL;
694
695 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 sub size_allocate {
707 my ($self, $w, $h) = @_;
708
709 return unless $self->{w} != $w || $self->{h} != $h;
710
711 $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
731 for (0 .. $#$children) {
732 my $child = $children->[$_];
733
734 my $alloc_h = $h[$_];
735 $alloc_h += ($h - $req_h) / @exp if $exp{$child};
736 $h[$_] = $alloc_h;
737 }
738 }
739
740 my $y = 0;
741 for (0 .. $#$children) {
742 my $child = $children->[$_];
743 my $h = $h[$_];
744 $child->move (0, $y);
745 $child->size_allocate ($w, $h);
746
747 $y += $h;
748 }
749 }
750
751 #############################################################################
752
753 package CFClient::UI::Label;
754
755 our @ISA = CFClient::UI::Base::;
756
757 use SDL::OpenGL;
758
759 sub new {
760 my ($class, %arg) = @_;
761
762 my $self = $class->SUPER::new (
763 fg => [1, 1, 1],
764 height => $::FONTSIZE,
765 text => "",
766 align => -1,
767 layout => new CFClient::Layout,
768 %arg
769 );
770
771 $self->set_text ($self->{text});
772
773 $self
774 }
775
776 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 sub set_text {
787 my ($self, $text) = @_;
788
789 $self->{text} = $text;
790 $self->{layout}->set_markup ($text);
791
792 delete $self->{texture};
793 $self->update;
794 }
795
796 sub get_text {
797 my ($self, $text) = @_;
798
799 $self->{text}
800 }
801
802 sub size_request {
803 my ($self) = @_;
804
805 $self->{layout}->set_width;
806 $self->{layout}->set_height ($self->{height});
807 $self->{layout}->size
808 # if ($self->{texture}{w} > 1 && $self->{texture}{height} > 1) { #TODO: hack
809 # (
810 # $self->{texture}{w},
811 # $self->{texture}{h},
812 # )
813 # } else {
814 # my ($w, $h, $data) = CFClient::font_render "Yy", $self->{h};
815 #
816 # ($w, $h)
817 # }
818 }
819
820 sub size_allocate {
821 my ($self, $w, $h) = @_;
822
823 return unless $self->{w} != $w || $self->{h} != $h;
824
825 $self->SUPER::size_allocate ($w, $h);
826 delete $self->{texture};
827 }
828
829 sub update {
830 my ($self) = @_;
831
832 delete $self->{texture};
833 $self->SUPER::update;
834 }
835
836 sub _draw {
837 my ($self) = @_;
838
839 my $tex = $self->{texture} ||= do {
840 $self->{layout}->set_width ($self->{w});
841 new_from_layout CFClient::Texture $self->{layout};
842 };
843
844 glEnable GL_BLEND;
845 glEnable GL_TEXTURE_2D;
846 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
847 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
848
849 glColor @{$self->{fg}};
850
851 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
858 glDisable GL_BLEND;
859 glDisable GL_TEXTURE_2D;
860 }
861
862 #############################################################################
863
864 package CFClient::UI::Entry;
865
866 our @ISA = CFClient::UI::Label::;
867
868 use SDL;
869 use SDL::OpenGL;
870
871 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
891 $text =~ s/./*/g if $self->{hidden};
892
893
894 $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 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 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 } elsif ($uni) {
947 substr $text, $self->{cursor}++, 0, chr $uni;
948 }
949
950 $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 }
961
962 sub button_down {
963 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
974 $self->_set_text ($self->{text});
975 $self->update;
976 }
977
978 sub mouse_motion {
979 my ($self, $ev, $x, $y) = @_;
980 # printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d#
981 }
982
983 sub _draw {
984 my ($self) = @_;
985
986 local $self->{fg} = $self->{fg};
987
988 if ($FOCUS == $self) {
989 glColor @{$self->{active_bg}};
990 $self->{fg} = $self->{active_fg};
991 } else {
992 glColor @{$self->{bg}};
993 }
994
995 glBegin GL_QUADS;
996 glVertex 0 , 0;
997 glVertex 0 , $self->{h};
998 glVertex $self->{w}, $self->{h};
999 glVertex $self->{w}, 0;
1000 glEnd;
1001
1002 $self->SUPER::_draw;
1003
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 package CFClient::UI::Slider;
1017
1018 use strict;
1019
1020 use SDL::OpenGL;
1021 use SDL::OpenGL::Constants;
1022
1023 our @ISA = CFClient::UI::DrawBG::;
1024
1025 sub size_request {
1026 my ($self) = @_;
1027
1028 my $w = 50;
1029 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 vertical => 1,
1044 @_
1045 )
1046 }
1047
1048 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 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w});
1062
1063 $x = $x * ($hi - $lo) / $w + $lo;
1064 $x = $lo if $x < $lo;
1065 $x = $hi - $page if $x > $hi - $page;
1066 $self->{range}[0] = $x;
1067
1068 $self->emit (changed => $x);
1069 $self->update;
1070 }
1071 }
1072
1073 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 glTranslate 0, -$self->{w}, 0;
1085
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 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 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
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 }
1127
1128 #############################################################################
1129
1130 package CFClient::UI::MapWidget;
1131
1132 use strict;
1133
1134 use List::Util qw(min max);
1135
1136 use SDL;
1137 use SDL::OpenGL;
1138 use SDL::OpenGL::Constants;
1139
1140 our @ISA = CFClient::UI::Base::;
1141
1142 sub new {
1143 my $class = shift;
1144
1145 $class->SUPER::new (
1146 z => -1,
1147 list => (glGenLists 1),
1148 @_
1149 )
1150 }
1151
1152 sub key_down {
1153 print "MAPKEYDOWN\n";
1154 }
1155
1156 sub key_up {
1157 }
1158
1159 sub size_request {
1160 (
1161 1 + int $::WIDTH / 32,
1162 1 + int $::HEIGHT / 32,
1163 )
1164 }
1165
1166 sub update {
1167 my ($self) = @_;
1168
1169 $self->{need_update} = 1;
1170 }
1171
1172 sub _draw {
1173 my ($self) = @_;
1174
1175 if (delete $self->{need_update}) {
1176 glNewList $self->{list}, GL_COMPILE;
1177
1178 my $mx = $::CONN->{mapx};
1179 my $my = $::CONN->{mapy};
1180
1181 my $map = $::CONN->{map};
1182
1183 my ($xofs, $yofs);
1184
1185 my $sw = 1 + int $::WIDTH / 32;
1186 my $sh = 1 + int $::HEIGHT / 32;
1187
1188 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
1194 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
1200 glEnable GL_TEXTURE_2D;
1201 glEnable GL_BLEND;
1202 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1203
1204 my $sw4 = ($sw + 3) & ~3;
1205 my $darkness = "\x00" x ($sw4 * $sh);
1206
1207 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 my ($w, $h) = @$tex{qw(w h)};
1225
1226 $tex->draw_quad (($x + 1) * 32 - $w, ($y + 1) * 32 - $h, $w, $h);
1227 }
1228 }
1229 }
1230
1231 # 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 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
1251 glColor 0.45, 0.45, 0.45, 1;
1252 $darkness->draw_quad (0, 0, $sw4 * 32, $sh * 32);
1253
1254 glDisable GL_TEXTURE_2D;
1255 glDisable GL_BLEND;
1256
1257 glEndList;
1258 }
1259
1260 glCallList $self->{list};
1261 }
1262
1263 my %DIR = (
1264 SDLK_KP8, [1, "north"],
1265 SDLK_KP9, [2, "northeast"],
1266 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
1273 SDLK_UP, [1, "north"],
1274 SDLK_RIGHT, [3, "east"],
1275 SDLK_DOWN, [5, "south"],
1276 SDLK_LEFT, [7, "west"],
1277 );
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 $self->{shft}++;
1290 $::CONN->send ("command fire $DIR{$sym}[0]");
1291 } elsif ($mod & KMOD_CTRL) {
1292 $self->{ctrl}++;
1293 $::CONN->send ("command run $DIR{$sym}[0]");
1294 } else {
1295 $::CONN->send ("command $DIR{$sym}[1]");
1296 }
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 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 }
1312 }
1313
1314 #############################################################################
1315
1316 package CFClient::UI::Animator;
1317
1318 use SDL::OpenGL;
1319
1320 our @ISA = CFClient::UI::Bin::;
1321
1322 sub moveto {
1323 my ($self, $x, $y) = @_;
1324
1325 $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
1326 $self->{speed} = 0.001;
1327 $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 glRotate $self->{time} * 1000, 0, 1, 0;
1352 $self->{children}[0]->draw;
1353 glPopMatrix;
1354 }
1355
1356 #############################################################################
1357
1358 package CFClient::UI::Toplevel;
1359
1360 our @ISA = CFClient::UI::Container::;
1361
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 sub translate {
1376 my ($self, $x, $y) = @_;
1377
1378 ($x, $y)
1379 }
1380
1381 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 package CFClient::UI;
1405
1406 $TOPLEVEL = new CFClient::UI::Toplevel;
1407
1408 1
1409