ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
(Generate patch)

Comparing deliantra/Deliantra-Client/DC/UI.pm (file contents):
Revision 1.337 by root, Mon Jul 24 04:24:43 2006 UTC vs.
Revision 1.384 by root, Fri Jul 20 16:32:11 2007 UTC

1package CFClient::UI; 1package CFPlus::UI;
2 2
3use utf8; 3use utf8;
4use strict; 4use strict;
5 5
6use Scalar::Util ();
7use List::Util (); 6use List::Util ();
8use Event; 7use Event;
9 8
10use CFClient; 9use CFPlus;
10use CFPlus::Pod;
11use CFClient::Texture; 11use CFPlus::Texture;
12 12
13our ($FOCUS, $HOVER, $GRAB); # various widgets 13our ($FOCUS, $HOVER, $GRAB); # various widgets
14 14
15our $LAYOUT; 15our $LAYOUT;
16our $ROOT; 16our $ROOT;
22our $TOOLTIP_WATCHER = Event->idle (min => 1/60, cb => sub { 22our $TOOLTIP_WATCHER = Event->idle (min => 1/60, cb => sub {
23 if (!$GRAB) { 23 if (!$GRAB) {
24 for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) { 24 for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) {
25 if (length $widget->{tooltip}) { 25 if (length $widget->{tooltip}) {
26 if ($TOOLTIP->{owner} != $widget) { 26 if ($TOOLTIP->{owner} != $widget) {
27 $TOOLTIP->{owner}->emit ("tooltip_hide") if $TOOLTIP->{owner};
27 $TOOLTIP->hide; 28 $TOOLTIP->hide;
28 29
29 $TOOLTIP->{owner} = $widget; 30 $TOOLTIP->{owner} = $widget;
31 $TOOLTIP->{owner}->emit ("tooltip_show") if $TOOLTIP->{owner};
30 32
31 return if $ENV{CFPLUS_DEBUG} & 8; 33 return if $ENV{CFPLUS_DEBUG} & 8;
32 34
33 my $tip = $widget->{tooltip}; 35 my $tip = $widget->{tooltip};
34 36
42 } 44 }
43 } 45 }
44 } 46 }
45 47
46 $TOOLTIP->hide; 48 $TOOLTIP->hide;
49 $TOOLTIP->{owner}->emit ("tooltip_hide") if $TOOLTIP->{owner};
47 delete $TOOLTIP->{owner}; 50 delete $TOOLTIP->{owner};
48}); 51});
49 52
50sub get_layout { 53sub get_layout {
51 my $layout; 54 my $layout;
96 99
97sub feed_sdl_button_down_event { 100sub feed_sdl_button_down_event {
98 my ($ev) = @_; 101 my ($ev) = @_;
99 my ($x, $y) = ($ev->{x}, $ev->{y}); 102 my ($x, $y) = ($ev->{x}, $ev->{y});
100 103
101 unless ($BUTTON_STATE) { 104 $BUTTON_STATE |= 1 << ($ev->{button} - 1);
105
106 unless ($GRAB) {
102 my $widget = $ROOT->find_widget ($x, $y); 107 my $widget = $ROOT->find_widget ($x, $y);
103 108
104 $GRAB = $widget; 109 $GRAB = $widget;
105 $GRAB->update if $GRAB; 110 $GRAB->update if $GRAB;
106 111
107 $TOOLTIP_WATCHER->cb->(); 112 $TOOLTIP_WATCHER->cb->();
108 } 113 }
109
110 $BUTTON_STATE |= 1 << ($ev->{button} - 1);
111 114
112 if ($GRAB) { 115 if ($GRAB) {
113 if ($ev->{button} == 4 || $ev->{button} == 5) { 116 if ($ev->{button} == 4 || $ev->{button} == 5) {
114 # mousewheel 117 # mousewheel
115 $ev->{dx} = 0; 118 $ev->{dx} = 0;
207 reconfigure_widgets; 210 reconfigure_widgets;
208} 211}
209 212
210############################################################################# 213#############################################################################
211 214
212package CFClient::UI::Event; 215package CFPlus::UI::Event;
213 216
214sub xy { 217sub xy {
215 $_[1]->coord2local ($_[0]{x}, $_[0]{y}) 218 $_[1]->coord2local ($_[0]{x}, $_[0]{y})
216} 219}
217 220
218############################################################################# 221#############################################################################
219 222
220package CFClient::UI::Base; 223package CFPlus::UI::Base;
221 224
222use strict; 225use strict;
223 226
224use CFClient::OpenGL; 227use CFPlus::OpenGL;
225 228
226sub new { 229sub new {
227 my $class = shift; 230 my $class = shift;
228 231
229 my $self = bless { 232 my $self = bless {
234 h => undef, 237 h => undef,
235 can_events => 1, 238 can_events => 1,
236 @_ 239 @_
237 }, $class; 240 }, $class;
238 241
239 Scalar::Util::weaken ($CFClient::UI::WIDGET{$self+0} = $self); 242 CFPlus::weaken ($CFPlus::UI::WIDGET{$self+0} = $self);
240 243
241 for (keys %$self) { 244 for (keys %$self) {
242 if (/^on_(.*)$/) { 245 if (/^on_(.*)$/) {
243 $self->connect ($1 => delete $self->{$_}); 246 $self->connect ($1 => delete $self->{$_});
244 } 247 }
245 } 248 }
246 249
247 if (my $layout = $CFClient::UI::LAYOUT->{$self->{name}}) { 250 if (my $layout = $CFPlus::UI::LAYOUT->{$self->{name}}) {
248 $self->{x} = $layout->{x} * $CFClient::UI::ROOT->{alloc_w} if exists $layout->{x}; 251 $self->{x} = $layout->{x} * $CFPlus::UI::ROOT->{alloc_w} if exists $layout->{x};
249 $self->{y} = $layout->{y} * $CFClient::UI::ROOT->{alloc_h} if exists $layout->{y}; 252 $self->{y} = $layout->{y} * $CFPlus::UI::ROOT->{alloc_h} if exists $layout->{y};
250 $self->{force_w} = $layout->{w} * $CFClient::UI::ROOT->{alloc_w} if exists $layout->{w}; 253 $self->{force_w} = $layout->{w} * $CFPlus::UI::ROOT->{alloc_w} if exists $layout->{w};
251 $self->{force_h} = $layout->{h} * $CFClient::UI::ROOT->{alloc_h} if exists $layout->{h}; 254 $self->{force_h} = $layout->{h} * $CFPlus::UI::ROOT->{alloc_h} if exists $layout->{h};
252 255
253 $self->{x} -= $self->{force_w} * 0.5 if exists $layout->{x}; 256 $self->{x} -= $self->{force_w} * 0.5 if exists $layout->{x};
254 $self->{y} -= $self->{force_h} * 0.5 if exists $layout->{y}; 257 $self->{y} -= $self->{force_h} * 0.5 if exists $layout->{y};
255 258
256 $self->show if $layout->{show}; 259 $self->show if $layout->{show};
261 264
262sub destroy { 265sub destroy {
263 my ($self) = @_; 266 my ($self) = @_;
264 267
265 $self->hide; 268 $self->hide;
269 $self->emit ("destroy");
266 %$self = (); 270 %$self = ();
267} 271}
268 272
273sub TO_JSON {
274 { __widget_ref__ => $_[0]{s_id} }
275}
276
269sub show { 277sub show {
270 my ($self) = @_; 278 my ($self) = @_;
271 279
272 return if $self->{parent}; 280 return if $self->{parent};
273 281
274 $CFClient::UI::ROOT->add ($self); 282 $CFPlus::UI::ROOT->add ($self);
275} 283}
276 284
277sub set_visible { 285sub set_visible {
278 my ($self) = @_; 286 my ($self) = @_;
279 287
300 delete $self->{root}; 308 delete $self->{root};
301 309
302 undef $GRAB if $GRAB == $self; 310 undef $GRAB if $GRAB == $self;
303 undef $HOVER if $HOVER == $self; 311 undef $HOVER if $HOVER == $self;
304 312
305 $CFClient::UI::TOOLTIP_WATCHER->cb->() 313 $CFPlus::UI::TOOLTIP_WATCHER->cb->()
306 if $TOOLTIP->{owner} == $self; 314 if $TOOLTIP->{owner} == $self;
307 315
308 $self->emit ("focus_out"); 316 $self->emit ("focus_out");
309 $self->emit (visibility_change => 0); 317 $self->emit (visibility_change => 0);
310} 318}
312sub set_visibility { 320sub set_visibility {
313 my ($self, $visible) = @_; 321 my ($self, $visible) = @_;
314 322
315 return if $self->{visible} == $visible; 323 return if $self->{visible} == $visible;
316 324
317 $visible ? $self->hide 325 $visible ? $self->show
318 : $self->show; 326 : $self->hide;
319} 327}
320 328
321sub toggle_visibility { 329sub toggle_visibility {
322 my ($self) = @_; 330 my ($self) = @_;
323 331
367 my ($self, $x, $y, $w, $h) = @_; 375 my ($self, $x, $y, $w, $h) = @_;
368 376
369 if ($self->{aspect}) { 377 if ($self->{aspect}) {
370 my ($ow, $oh) = ($w, $h); 378 my ($ow, $oh) = ($w, $h);
371 379
372 $w = List::Util::min $w, CFClient::ceil $h * $self->{aspect}; 380 $w = List::Util::min $w, CFPlus::ceil $h * $self->{aspect};
373 $h = List::Util::min $h, CFClient::ceil $w / $self->{aspect}; 381 $h = List::Util::min $h, CFPlus::ceil $w / $self->{aspect};
374 382
375 # use alignment to adjust x, y 383 # use alignment to adjust x, y
376 384
377 $x += int 0.5 * ($ow - $w); 385 $x += int 0.5 * ($ow - $w);
378 $y += int 0.5 * ($oh - $h); 386 $y += int 0.5 * ($oh - $h);
419 427
420 return if $self->{tooltip} eq $tooltip; 428 return if $self->{tooltip} eq $tooltip;
421 429
422 $self->{tooltip} = $tooltip; 430 $self->{tooltip} = $tooltip;
423 431
424 if ($CFClient::UI::TOOLTIP->{owner} == $self) { 432 if ($CFPlus::UI::TOOLTIP->{owner} == $self) {
425 delete $CFClient::UI::TOOLTIP->{owner}; 433 delete $CFPlus::UI::TOOLTIP->{owner};
426 $CFClient::UI::TOOLTIP_WATCHER->cb->(); 434 $CFPlus::UI::TOOLTIP_WATCHER->cb->();
427 } 435 }
428} 436}
429 437
430# translate global coordinates to local coordinate system 438# translate global coordinates to local coordinate system
431sub coord2local { 439sub coord2local {
497sub connect { 505sub connect {
498 my ($self, $signal, $cb) = @_; 506 my ($self, $signal, $cb) = @_;
499 507
500 push @{ $self->{signal_cb}{$signal} }, $cb; 508 push @{ $self->{signal_cb}{$signal} }, $cb;
501 509
502 defined wantarray and CFClient::guard { 510 defined wantarray and CFPlus::guard {
503 @{ $self->{signal_cb}{$signal} } = grep $_ != $cb, 511 @{ $self->{signal_cb}{$signal} } = grep $_ != $cb,
504 @{ $self->{signal_cb}{$signal} }; 512 @{ $self->{signal_cb}{$signal} };
505 } 513 }
514}
515
516sub disconnect_all {
517 my ($self, $signal) = @_;
518
519 delete $self->{signal_cb}{$signal};
506} 520}
507 521
508my %has_coords = ( 522my %has_coords = (
509 button_down => 1, 523 button_down => 1,
510 button_up => 1, 524 button_up => 1,
513); 527);
514 528
515sub emit { 529sub emit {
516 my ($self, $signal, @args) = @_; 530 my ($self, $signal, @args) = @_;
517 531
518 # I do not really like this solution, but I dislike duplication 532 # I do not really like this solution, but I do not like duplication
519 # and needlessly verbose code, too. 533 # and needlessly verbose code, either.
520 my @append 534 my @append
521 = $has_coords{$signal} 535 = $has_coords{$signal}
522 ? $args[0]->xy ($self) 536 ? $args[0]->xy ($self)
523 : (); 537 : ();
524 538
525 #warn +(caller(1))[3] . "emit $signal on $self (parent $self->{parent})\n";#d# 539 #warn +(caller(1))[3] . "emit $signal on $self (parent $self->{parent})\n";#d#
526 540
527 #d##TODO# stop propagating at first true, do not use sum 541 for my $cb (
528 (List::Util::sum map $_->($self, @args, @append), @{$self->{signal_cb}{$signal} || []}) # before 542 @{$self->{signal_cb}{$signal} || []}, # before
529 || ($self->can ("invoke_$signal") || sub { 1 })->($self, @args, @append) # closure 543 ($self->can ("invoke_$signal") || sub { 1 }), # closure
544 ) {
545 return $cb->($self, @args, @append) || next;
546 }
547
548 # parent
530 || ($self->{parent} && $self->{parent}->emit ($signal, @args)) # parent 549 $self->{parent} && $self->{parent}->emit ($signal, @args)
531} 550}
532 551
533sub find_widget { 552sub find_widget {
534 my ($self, $x, $y) = @_; 553 my ($self, $x, $y) = @_;
535 554
543} 562}
544 563
545sub set_parent { 564sub set_parent {
546 my ($self, $parent) = @_; 565 my ($self, $parent) = @_;
547 566
548 Scalar::Util::weaken ($self->{parent} = $parent); 567 CFPlus::weaken ($self->{parent} = $parent);
549 $self->set_visible if $parent->{visible}; 568 $self->set_visible if $parent->{visible};
550} 569}
551 570
552sub realloc { 571sub realloc {
553 my ($self) = @_; 572 my ($self) = @_;
619 glVertex $self->{w} - 1, 0; 638 glVertex $self->{w} - 1, 0;
620 glVertex $self->{w} - 1, $self->{h} - 1; 639 glVertex $self->{w} - 1, $self->{h} - 1;
621 glVertex 0 , $self->{h} - 1; 640 glVertex 0 , $self->{h} - 1;
622 glEnd; 641 glEnd;
623 glPopMatrix; 642 glPopMatrix;
624 #CFClient::UI::Label->new (w => $self->{w}, h => $self->{h}, text => $self, fontsize => 0)->_draw; 643 #CFPlus::UI::Label->new (w => $self->{w}, h => $self->{h}, text => $self, fontsize => 0)->_draw;
625 } 644 }
626 645
627 $self->_draw; 646 $self->_draw;
628 glPopMatrix; 647 glPopMatrix;
629} 648}
632 my ($self) = @_; 651 my ($self) = @_;
633 652
634 warn "no draw defined for $self\n"; 653 warn "no draw defined for $self\n";
635} 654}
636 655
656my $cntx;#d#
637sub DESTROY { 657sub DESTROY {
638 my ($self) = @_; 658 my ($self) = @_;
639 659
640 return if CFClient::in_destruct; 660 return if CFPlus::in_destruct;
641
642 delete $WIDGET{$self+0};
643 661
644 eval { $self->destroy }; 662 eval { $self->destroy };
645 warn "exception during widget destruction: $@" if $@ & $@ != /during global destruction/; 663 warn "exception during widget destruction: $@" if $@ & $@ != /during global destruction/;
664
665 delete $WIDGET{$self+0};
646} 666}
647 667
648############################################################################# 668#############################################################################
649 669
650package CFClient::UI::DrawBG; 670package CFPlus::UI::DrawBG;
651 671
652our @ISA = CFClient::UI::Base::; 672our @ISA = CFPlus::UI::Base::;
653 673
654use strict; 674use strict;
655use CFClient::OpenGL; 675use CFPlus::OpenGL;
656 676
657sub new { 677sub new {
658 my $class = shift; 678 my $class = shift;
659 679
660 # range [value, low, high, page] 680 # range [value, low, high, page]
691 } 711 }
692} 712}
693 713
694############################################################################# 714#############################################################################
695 715
696package CFClient::UI::Empty; 716package CFPlus::UI::Empty;
697 717
698our @ISA = CFClient::UI::Base::; 718our @ISA = CFPlus::UI::Base::;
699 719
700sub new { 720sub new {
701 my ($class, %arg) = @_; 721 my ($class, %arg) = @_;
702 $class->SUPER::new (can_events => 0, %arg); 722 $class->SUPER::new (can_events => 0, %arg);
703} 723}
710 730
711sub draw { } 731sub draw { }
712 732
713############################################################################# 733#############################################################################
714 734
715package CFClient::UI::Container; 735package CFPlus::UI::Container;
716 736
717our @ISA = CFClient::UI::Base::; 737our @ISA = CFPlus::UI::Base::;
718 738
719sub new { 739sub new {
720 my ($class, %arg) = @_; 740 my ($class, %arg) = @_;
721 741
722 my $children = delete $arg{children}; 742 my $children = delete $arg{children};
808 $_->draw for @{$self->{children}}; 828 $_->draw for @{$self->{children}};
809} 829}
810 830
811############################################################################# 831#############################################################################
812 832
813package CFClient::UI::Bin; 833package CFPlus::UI::Bin;
814 834
815our @ISA = CFClient::UI::Container::; 835our @ISA = CFPlus::UI::Container::;
816 836
817sub new { 837sub new {
818 my ($class, %arg) = @_; 838 my ($class, %arg) = @_;
819 839
820 my $child = (delete $arg{child}) || new CFClient::UI::Empty::; 840 my $child = (delete $arg{child}) || new CFPlus::UI::Empty::;
821 841
822 $class->SUPER::new (children => [$child], %arg) 842 $class->SUPER::new (children => [$child], %arg)
823} 843}
824 844
825sub add { 845sub add {
832sub remove { 852sub remove {
833 my ($self, $widget) = @_; 853 my ($self, $widget) = @_;
834 854
835 $self->SUPER::remove ($widget); 855 $self->SUPER::remove ($widget);
836 856
837 $self->{children} = [new CFClient::UI::Empty] 857 $self->{children} = [new CFPlus::UI::Empty]
838 unless @{$self->{children}}; 858 unless @{$self->{children}};
839} 859}
840 860
841sub child { $_[0]->{children}[0] } 861sub child { $_[0]->{children}[0] }
842 862
854 874
855############################################################################# 875#############################################################################
856 876
857# back-buffered drawing area 877# back-buffered drawing area
858 878
859package CFClient::UI::Window; 879package CFPlus::UI::Window;
860 880
861our @ISA = CFClient::UI::Bin::; 881our @ISA = CFPlus::UI::Bin::;
862 882
863use CFClient::OpenGL; 883use CFPlus::OpenGL;
864 884
865sub new { 885sub new {
866 my ($class, %arg) = @_; 886 my ($class, %arg) = @_;
867 887
868 my $self = $class->SUPER::new (%arg); 888 my $self = $class->SUPER::new (%arg);
890} 910}
891 911
892sub render_child { 912sub render_child {
893 my ($self) = @_; 913 my ($self) = @_;
894 914
895 $self->{texture} = new_from_opengl CFClient::Texture $self->{w}, $self->{h}, sub { 915 $self->{texture} = new_from_opengl CFPlus::Texture $self->{w}, $self->{h}, sub {
896 glClearColor 0, 0, 0, 0; 916 glClearColor 0, 0, 0, 0;
897 glClear GL_COLOR_BUFFER_BIT; 917 glClear GL_COLOR_BUFFER_BIT;
898 918
899 { 919 {
900 package CFClient::UI::Base; 920 package CFPlus::UI::Base;
901 921
902 ($draw_x, $draw_y, $draw_w, $draw_h) = 922 local ($draw_x, $draw_y, $draw_w, $draw_h) =
903 (0, 0, $self->{w}, $self->{h}); 923 (0, 0, $self->{w}, $self->{h});
924
925 $self->_render;
904 } 926 }
905
906 $self->_render;
907 }; 927 };
908} 928}
909 929
910sub _draw { 930sub _draw {
911 my ($self) = @_; 931 my ($self) = @_;
912
913 my ($w, $h) = @$self{qw(w h)};
914 932
915 my $tex = $self->{texture} 933 my $tex = $self->{texture}
916 or return; 934 or return;
917 935
918 glEnable GL_TEXTURE_2D; 936 glEnable GL_TEXTURE_2D;
919 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 937 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
920 glColor 0, 0, 0, 1; 938 glColor 0, 0, 0, 1;
921 939
922 $tex->draw_quad_alpha_premultiplied (0, 0, $w, $h); 940 $tex->draw_quad_alpha_premultiplied (0, 0);
923 941
924 glDisable GL_TEXTURE_2D; 942 glDisable GL_TEXTURE_2D;
925} 943}
926 944
927############################################################################# 945#############################################################################
928 946
929package CFClient::UI::ViewPort; 947package CFPlus::UI::ViewPort;
930 948
949use List::Util qw(min max);
950
931our @ISA = CFClient::UI::Window::; 951our @ISA = CFPlus::UI::Window::;
932 952
933sub new { 953sub new {
934 my $class = shift; 954 my $class = shift;
935 955
936 $class->SUPER::new ( 956 $class->SUPER::new (
966} 986}
967 987
968sub set_offset { 988sub set_offset {
969 my ($self, $x, $y) = @_; 989 my ($self, $x, $y) = @_;
970 990
991 my $x = max 0, min $self->child->{w} - $self->{w}, int $x;
992 my $y = max 0, min $self->child->{h} - $self->{h}, int $y;
993
994 if ($x != $self->{view_x} or $y != $self->{view_y}) {
971 $self->{view_x} = int $x; 995 $self->{view_x} = $x;
972 $self->{view_y} = int $y; 996 $self->{view_y} = $y;
973 997
998 $self->emit (changed => $x, $y);
974 $self->update; 999 $self->update;
1000 }
975} 1001}
976 1002
977# hmm, this does not work for topleft of $self... but we should not ask for that 1003# hmm, this does not work for topleft of $self... but we should not ask for that
978sub coord2local { 1004sub coord2local {
979 my ($self, $x, $y) = @_; 1005 my ($self, $x, $y) = @_;
996 if ( $x >= $self->{x} && $x < $self->{x} + $self->{w} 1022 if ( $x >= $self->{x} && $x < $self->{x} + $self->{w}
997 && $y >= $self->{y} && $y < $self->{y} + $self->{h} 1023 && $y >= $self->{y} && $y < $self->{y} + $self->{h}
998 ) { 1024 ) {
999 $self->child->find_widget ($x + $self->{view_x}, $y + $self->{view_y}) 1025 $self->child->find_widget ($x + $self->{view_x}, $y + $self->{view_y})
1000 } else { 1026 } else {
1001 $self->CFClient::UI::Base::find_widget ($x, $y) 1027 $self->CFPlus::UI::Base::find_widget ($x, $y)
1002 } 1028 }
1003} 1029}
1004 1030
1005sub _render { 1031sub _render {
1006 my ($self) = @_; 1032 my ($self) = @_;
1007 1033
1008 local $CFClient::UI::Base::draw_x = $CFClient::UI::Base::draw_x - $self->{view_x}; 1034 local $CFPlus::UI::Base::draw_x = $CFPlus::UI::Base::draw_x - $self->{view_x};
1009 local $CFClient::UI::Base::draw_y = $CFClient::UI::Base::draw_y - $self->{view_y}; 1035 local $CFPlus::UI::Base::draw_y = $CFPlus::UI::Base::draw_y - $self->{view_y};
1010 1036
1011 CFClient::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y}; 1037 CFPlus::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y};
1012 1038
1013 $self->SUPER::_render; 1039 $self->SUPER::_render;
1014} 1040}
1015 1041
1016############################################################################# 1042#############################################################################
1017 1043
1018package CFClient::UI::ScrolledWindow; 1044package CFPlus::UI::ScrolledWindow;
1019 1045
1020our @ISA = CFClient::UI::HBox::; 1046our @ISA = CFPlus::UI::Table::;
1021 1047
1022sub new { 1048sub new {
1023 my ($class, %arg) = @_; 1049 my ($class, %arg) = @_;
1024 1050
1025 my $child = delete $arg{child}; 1051 my $child = delete $arg{child};
1026 1052
1027 my $self; 1053 my $self;
1028 1054
1029 my $slider = new CFClient::UI::Slider 1055 my $hslider = new CFPlus::UI::Slider
1056 vertical => 0,
1057 range => [0, 0, 1, 0.01], # HACK fix
1058 on_changed => sub {
1059 $self->{hpos} = $_[1];
1060 $self->{vp}->set_offset ($self->{hpos}, $self->{vpos});
1061 },
1062 ;
1063
1064 my $vslider = new CFPlus::UI::Slider
1030 vertical => 1, 1065 vertical => 1,
1031 range => [0, 0, 1, 0.01], # HACK fix 1066 range => [0, 0, 1, 0.01], # HACK fix
1032 on_changed => sub { 1067 on_changed => sub {
1033 $self->{vp}->set_offset (0, $_[1]); 1068 $self->{vpos} = $_[1];
1069 $self->{vp}->set_offset ($self->{hpos}, $self->{vpos});
1034 }, 1070 },
1035 ; 1071 ;
1036 1072
1037 $self = $class->SUPER::new ( 1073 $self = $class->SUPER::new (
1038 vp => (new CFClient::UI::ViewPort expand => 1), 1074 scroll_x => 0,
1075 scroll_y => 1,
1039 can_events => 1, 1076 can_events => 1,
1040 slider => $slider, 1077 hslider => $hslider,
1078 vslider => $vslider,
1079 col_expand => [1, 0],
1080 row_expand => [1, 0],
1041 %arg, 1081 %arg,
1042 ); 1082 );
1043 1083
1044 $self->SUPER::add ($self->{vp}, $self->{slider}); 1084 $self->{vp} = new CFPlus::UI::ViewPort
1085 expand => 1,
1086 scroll_x => $self->{scroll_x},
1087 scroll_y => $self->{scroll_y},
1088 on_changed => sub {
1089 my ($vp, $x, $y) = @_;
1090
1091 $vp->{parent}{hslider}->set_value ($x);
1092 $vp->{parent}{vslider}->set_value ($y);
1093
1094 0
1095 },
1096 ;
1097
1098 $self->SUPER::add_at (0, 0, $self->{vp});
1099
1045 $self->add ($child) if $child; 1100 $self->add ($child) if $child;
1046 1101
1047 $self 1102 $self
1048} 1103}
1049 1104
1053 my ($self, $widget) = @_; 1108 my ($self, $widget) = @_;
1054 1109
1055 $self->{vp}->add ($self->{child} = $widget); 1110 $self->{vp}->add ($self->{child} = $widget);
1056} 1111}
1057 1112
1113sub update_slider {
1114 my ($self) = @_;
1115
1116 my $child = ($self->{vp} or return)->child;
1117
1118 my ($w1, $w2) = ($child->{w}, $self->{vp}{w});
1119 $self->{hslider}->set_range ([$self->{hslider}{range}[0], 0, $w1, $w2, 1]);
1120
1121 my $visible = $w1 > $w2;
1122 if ($visible != $self->{hslider}{visible}) {
1123 $visible ? $self->SUPER::add_at (0, 1, $self->{hslider})
1124 : $self->{hslider}->hide;
1125 }
1126
1127 my ($h1, $h2) = ($child->{h}, $self->{vp}{h});
1128 $self->{vslider}->set_range ([$self->{vslider}{range}[0], 0, $h1, $h2, 1]);
1129
1130 my $visible = $h1 > $h2;
1131 if ($visible != $self->{vslider}{visible}) {
1132 $visible ? $self->SUPER::add_at (1, 0, $self->{vslider})
1133#!/opt/bin/perl
1134
1135my $startup_done = sub { };
1136our $PANGO = "1.5.0";
1137
1138# do splash-screen thingy on win32
1139BEGIN {
1140 if (%PAR::LibCache && $^O eq "MSWin32") {
1141 while (my ($filename, $zip) = each %PAR::LibCache) {
1142 $zip->extractMember ("SPLASH.bmp", "$ENV{PAR_TEMP}/SPLASH.bmp");
1143 }
1144
1145 require Win32::GUI::SplashScreen;
1146
1147 Win32::GUI::SplashScreen::Show (
1148 -file => "$ENV{PAR_TEMP}/SPLASH.bmp",
1149 );
1150
1151 $startup_done = sub {
1152 Win32::GUI::SplashScreen::Done (1);
1153 };
1154 }
1155}
1156
1157use strict;
1158use utf8;
1159
1160use Carp 'verbose';
1161
1162# do things only needed for single-binary version (par)
1163BEGIN {
1164 if (%PAR::LibCache) {
1165 @INC = grep ref, @INC; # weed out all paths except pars loader refs
1166
1167 my $tmp = $ENV{PAR_TEMP};
1168
1169 while (my ($filename, $zip) = each %PAR::LibCache) {
1170 for ($zip->memberNames) {
1171 next unless /^root\/(.*)/;
1172 $zip->extractMember ($_, "$tmp/$1")
1173 unless -e "$tmp/$1";
1174 }
1175 }
1176
1177 if ($^O eq "MSWin32") {
1178 # relocatable
1179 } else {
1180 # unix, need to patch pango rc file
1181 open my $fh, "<:perlio", "$tmp/usr/lib/pango/$PANGO/module-files.d/libpango1.0-0.modules"
1182 or die "$tmp/usr/lib/$PANGO/module-files.d/libpango1.0-0.modules: $!";
1183 local $/;
1184 my $rc = <$fh>;
1185 $rc =~ s/^\//$tmp\//gm; # replace abs paths by relative ones
1186
1187 mkdir "$tmp/pango-modules";
1188 open my $fh, ">:perlio", "$tmp/pango-modules/pango.modules"
1189 or die "$tmp/pango-modules/pango.modules: $!";
1190 print $fh $rc;
1191
1192 $ENV{PANGO_RC_FILE} = "$tmp/pango.rc";
1193 open my $fh, ">:perlio", $ENV{PANGO_RC_FILE}
1194 or die "$ENV{PANGO_RC_FILE}: $!";
1195 print $fh "[Pango]\nModuleFiles = $tmp/pango-modules\n";
1196 }
1197
1198 unshift @INC, $tmp;
1199 }
1200}
1201
1202# need to do it again because that pile of garbage called PAR nukes it before main
1203unshift @INC, $ENV{PAR_TEMP}
1204 if %PAR::LibCache;
1205
1206use Time::HiRes 'time';
1207use Event;
1208
1209use Crossfire;
1210use Crossfire::Protocol::Constants;
1211
1212use Compress::LZF;
1213
1214use CFPlus;
1215use CFPlus::OpenGL ();
1216use CFPlus::Protocol;
1217use CFPlus::DB;
1218use CFPlus::UI;
1219use CFPlus::UI::Inventory;
1220use CFPlus::UI::SpellList;
1221use CFPlus::Pod;
1222use CFPlus::MapWidget;
1223use CFPlus::Macro;
1224
1225$SIG{QUIT} = sub { Carp::cluck "QUIT" };
1226$SIG{PIPE} = 'IGNORE';
1227
1228$Event::Eval = 1;
1229$Event::DIED = sub {
1230 CFPlus::fatal Carp::longmess $_[1]
1231};
1232
1233my $MAX_FPS = 60;
1234my $MIN_FPS = 5; # unused as of yet
1235
1236our $META_SERVER = "http://metaserver.schmorp.de/current.json";
1237
1238our $LAST_REFRESH;
1239our $NOW;
1240
1241our $CFG;
1242our $CONN;
1243our $PROFILE; # current profile
1244our $FAST; # fast, low-quality mode, possibly useful for software-rendering
1245
1246our $WANT_REFRESH;
1247our $CAN_REFRESH;
1248
1249our @SDL_MODES;
1250our $WIDTH;
1251our $HEIGHT;
1252our $FULLSCREEN;
1253our $FONTSIZE;
1254
1255our $FONT_PROP;
1256our $FONT_FIXED;
1257
1258our $MAP;
1259our $MAPMAP;
1260our $MAPWIDGET;
1261our $BUTTONBAR;
1262our $LOGVIEW;
1263our $CONSOLE;
1264our $METASERVER;
1265our $LOGIN_BUTTON;
1266our $QUIT_DIALOG;
1267our $HOST_ENTRY;
1268our $FULLSCREEN_ENABLE;
1269our $PICKUP_ENABLE;
1270our $SERVER_INFO;
1271
1272our $SETUP_DIALOG;
1273our $SETUP_NOTEBOOK;
1274our $SETUP_SERVER;
1275our $SETUP_KEYBOARD;
1276
1277our $PL_NOTEBOOK;
1278our $PL_WINDOW;
1279
1280our $INVENTORY_PAGE;
1281our $STATS_PAGE;
1282our $SKILL_PAGE;
1283our $SPELL_PAGE;
1284our $SPELL_LIST;
1285
1286our $HELP_WINDOW;
1287our $MESSAGE_WINDOW;
1288our $FLOORBOX;
1289our $GAUGES;
1290our $STATWIDS;
1291
1292our $SDL_ACTIVE;
1293our %SDL_CB;
1294
1295our $SDL_MIXER;
1296our $MUSIC_DEFAULT = "in_a_heartbeat.ogg";
1297our @MUSIC_WANT;
1298our $MUSIC_START;
1299our $MUSIC_PLAYING;
1300our $MUSIC_PLAYER;
1301our $MUSIC_RESUME = 30; # resume music when players less than these many seconds before
1302our @SOUNDS; # event => file mapping
1303our %AUDIO_CHUNKS; # audio files
1304
1305our $ALT_ENTER_MESSAGE;
1306our $STATUSBOX;
1307our $DEBUG_STATUS;
1308
1309our $INV;
1310our $INVR;
1311our $INV_RIGHT_HB;
1312
1313our $PICKUP_CFG;
1314
1315our $IN_BUILD_MODE;
1316our $BUILD_BUTTON;
1317
1318sub status {
1319 $STATUSBOX->add (CFPlus::asxml $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]);
1320}
1321
1322sub debug {
1323 $DEBUG_STATUS->set_text ($_[0]);
1324}
1325
1326sub message {
1327 my ($para) = @_;
1328
1329 my $time = sprintf "%02d:%02d:%02d", (localtime time)[2,1,0];
1330
1331 $para->{markup} = "<span foreground='#ffffff'>$time</span> $para->{markup}";
1332
1333 $LOGVIEW->add_paragraph ($para);
1334 $LOGVIEW->scroll_to_bottom;
1335}
1336
1337sub destroy_query_dialog {
1338 (delete $_[0]{query_dialog})->destroy
1339 if $_[0]{query_dialog};
1340}
1341
1342# FIXME: a very ugly hack to wait for stat update look below! #d#
1343our $QUERY_TIMER; #d#
1344
1345# server query dialog
1346sub server_query {
1347 my ($conn, $flags, $prompt) = @_;
1348
1349 # FIXME: a very ugly hack to wait for stat update #d#
1350 if ($prompt =~ /roll new stats/ and not $conn->{stat_change_with}) {
1351 unless ($QUERY_TIMER) {
1352 $QUERY_TIMER =
1353 Event->timer (
1354 after => 1,
1355 cb => sub {
1356 server_query ($conn, $flags, $prompt, 1);
1357 $QUERY_TIMER = undef
1358 }
1359 );
1360 return;
1361 }
1362 }
1363
1364 $conn->{query_dialog} = my $dialog = new CFPlus::UI::Toplevel
1365 x => "center",
1366 y => "center",
1367 title => "Server Query",
1368 child => my $vbox = new CFPlus::UI::VBox,
1369 ;
1370
1371 my @dialog = my $label = new CFPlus::UI::Label
1372 max_w => $::WIDTH * 0.8,
1373 ellipsise => 0,
1374 text => $prompt;
1375
1376 if ($flags & CS_QUERY_YESNO) {
1377 push @dialog, my $hbox = new CFPlus::UI::HBox;
1378
1379 $hbox->add (new CFPlus::UI::Button
1380 text => "No",
1381 on_activate => sub {
1382 $conn->send ("reply n");
1383 $dialog->destroy;
1384 0
1385 }
1386 );
1387 $hbox->add (new CFPlus::UI::Button
1388 text => "Yes",
1389 on_activate => sub {
1390 $conn->send ("reply y");
1391 destroy_query_dialog $conn;
1392 0
1393 },
1394 );
1395
1396 $dialog->grab_focus;
1397
1398 } elsif ($flags & CS_QUERY_SINGLECHAR) {
1399 if ($prompt =~ /Now choose a character|Press any key for the next race/i) {
1400 $dialog->{tooltip} = "#charcreation_focus";
1401
1402 unshift @dialog, new CFPlus::UI::Label
1403 max_w => $::WIDTH * 0.8,
1404 ellipsise => 0,
1405 markup => "\nOr use your keyboard and the text entry below:\n";
1406
1407 unshift @dialog, my $table = new CFPlus::UI::Table;
1408
1409 $table->add_at (0, 0, new CFPlus::UI::Button
1410 text => "Next Race",
1411 on_activate => sub {
1412 $conn->send ("reply n");
1413 destroy_query_dialog $conn;
1414 0
1415 },
1416 );
1417 $table->add_at (2, 0, new CFPlus::UI::Button
1418 text => "Accept",
1419 on_activate => sub {
1420 $conn->send ("reply d");
1421 destroy_query_dialog $conn;
1422 0
1423 },
1424 );
1425
1426 if ($conn->{chargen_race_description}) {
1427 unshift @dialog, new CFPlus::UI::Label
1428 max_w => $::WIDTH * 0.8,
1429 ellipsise => 0,
1430 markup => "<span foreground='#ccccff'>$conn->{chargen_race_description}</span>",
1431 ;
1432 }
1433
1434 unshift @dialog, new CFPlus::UI::Face
1435 face => $conn->{player}{face},
1436 bg => [.2, .2, .2, 1],
1437 min_w => 64,
1438 min_h => 64,
1439 ;
1440
1441 if ($conn->{chargen_race_title}) {
1442 unshift @dialog, new CFPlus::UI::Label
1443 allign => 1,
1444 ellipsise => 0,
1445 markup => "<span foreground='#ccccff' size='large'>Race: $conn->{chargen_race_title}</span>",
1446 ;
1447 }
1448
1449 unshift @dialog, new CFPlus::UI::Label
1450 max_w => $::WIDTH * 0.4,
1451 ellipsise => 0,
1452 markup => (CFPlus::Pod::section_label ui => "chargen_race"),
1453 ;
1454
1455 } elsif ($prompt =~ /roll new stats/) {
1456 if (my $stat = delete $conn->{stat_change_with}) {
1457 $conn->send ("reply $stat");
1458 destroy_query_dialog $conn;
1459 return;
1460 }
1461
1462 unshift @dialog, new CFPlus::UI::Label
1463 max_w => $::WIDTH * 0.4,
1464 ellipsise => 0,
1465 markup => "\nOr use your keyboard and the text entry below:\n";
1466
1467 unshift @dialog, my $table = new CFPlus::UI::Table;
1468
1469 # left: re-roll
1470 $table->add_at (0, 0, new CFPlus::UI::Button
1471 text => "Roll Again",
1472 on_activate => sub {
1473 $conn->send ("reply y");
1474 destroy_query_dialog $conn;
1475 0
1476 },
1477 );
1478
1479 # center: swap stats
1480 my ($sw1, $sw2) = map +(new CFPlus::UI::Selector
1481 expand => 1,
1482 value => $_,
1483 options => [
1484 [1 => "Str", "Strength ($conn->{stat}{+CS_STAT_STR})"],
1485 [2 => "Dex", "Dexterity ($conn->{stat}{+CS_STAT_DEX})"],
1486 [3 => "Con", "Constitution ($conn->{stat}{+CS_STAT_CON})"],
1487 [4 => "Int", "Intelligence ($conn->{stat}{+CS_STAT_INT})"],
1488 [5 => "Wis", "Wisdom ($conn->{stat}{+CS_STAT_WIS})"],
1489 [6 => "Pow", "Power ($conn->{stat}{+CS_STAT_POW})"],
1490 [7 => "Cha", "Charisma ($conn->{stat}{+CS_STAT_CHA})"],
1491 ],
1492 ), 1 .. 2;
1493
1494 $table->add_at (2, 0, new CFPlus::UI::Button
1495 text => "Swap Stats",
1496 on_activate => sub {
1497 $conn->{stat_change_with} = $sw2->{value};
1498 $conn->send ("reply $sw1->{value}");
1499 destroy_query_dialog $conn;
1500 0
1501 },
1502 );
1503 $table->add_at (2, 1, new CFPlus::UI::HBox children => [$sw1, $sw2]);
1504
1505 # right: accept
1506 $table->add_at (4, 0, new CFPlus::UI::Button
1507 text => "Accept",
1508 on_activate => sub {
1509 $conn->send ("reply n");
1510 $STATS_PAGE->hide;
1511 destroy_query_dialog $conn;
1512 0
1513 },
1514 );
1515
1516 unshift @dialog, my $hbox = new CFPlus::UI::HBox;
1517 for (
1518 [Str => CS_STAT_STR],
1519 [Dex => CS_STAT_DEX],
1520 [Con => CS_STAT_CON],
1521 [Int => CS_STAT_INT],
1522 [Wis => CS_STAT_WIS],
1523 [Pow => CS_STAT_POW],
1524 [Cha => CS_STAT_CHA],
1525 ) {
1526 my ($name, $id) = @$_;
1527 $hbox->add (new CFPlus::UI::Label
1528 markup => "$conn->{stat}{$id} <span foreground='yellow'>$name</span>",
1529 align => 0,
1530 expand => 1,
1531 can_events => 1,
1532 can_hover => 1,
1533 tooltip => "#stat_$name",
1534 );
1535 }
1536
1537 unshift @dialog, new CFPlus::UI::Label
1538 max_w => $::WIDTH * 0.4,
1539 ellipsise => 0,
1540 markup => (CFPlus::Pod::section_label ui => "chargen_stats"),
1541 ;
1542 }
1543
1544 push @dialog, my $entry = new CFPlus::UI::Entry
1545 on_changed => sub {
1546 $conn->send ("reply $_[1]");
1547 destroy_query_dialog $conn;
1548 0
1549 },
1550 ;
1551
1552 $entry->grab_focus;
1553
1554 } else {
1555 $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1556
1557 push @dialog, my $entry = new CFPlus::UI::Entry
1558 $flags & CS_QUERY_HIDEINPUT ? (hidden => "*") : (),
1559 on_activate => sub {
1560 $conn->send ("reply $_[1]");
1561 destroy_query_dialog $conn;
1562 0
1563 },
1564 ;
1565
1566 $entry->grab_focus;
1567 }
1568
1569 $vbox->add (@dialog);
1570 $dialog->show;
1571}
1572
1573sub start_game {
1574 status "logging in...";
1575
1576 $LOGIN_BUTTON->set_text ("Logout");
1577 $SETUP_DIALOG->hide;
1578
1579 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
1580
1581 my ($host, $port) = split /:/, $PROFILE->{host};
1582
1583 $MAP = new CFPlus::Map;
1584
1585 $CONN = eval {
1586 new CFPlus::Protocol
1587 host => $host,
1588 port => $port || 13327,
1589 user => $PROFILE->{user},
1590 pass => $PROFILE->{password},
1591 mapw => $mapsize,
1592 maph => $mapsize,
1593
1594 client => "cfplus $CFPlus::VERSION $] $^O",
1595
1596 map_widget => $MAPWIDGET,
1597 logview => $LOGVIEW,
1598 statusbox => $STATUSBOX,
1599 map => $MAP,
1600 mapmap => $MAPMAP,
1601 query => \&server_query,
1602
1603 setup_req => {
1604 smoothing => $CFG->{map_smoothing}*1,
1605 },
1606
1607 sound_play => sub {
1608 my ($x, $y, $soundnum, $type) = @_;
1609
1610 $SDL_MIXER
1611 or return;
1612
1613 my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1614 or return;
1615
1616 $chunk->play;
1617 },
1618 };
1619
1620 if ($CONN) {
1621 CFPlus::lowdelay fileno $CONN->{fh};
1622
1623 status "login successful";
1624 } else {
1625 status "unable to connect";
1626 stop_game();
1627 }
1628}
1629
1630sub stop_game {
1631 $LOGIN_BUTTON->set_text ("Login");
1632 $SETUP_NOTEBOOK->set_current_page ($SETUP_SERVER);
1633 $SETUP_DIALOG->show;
1634 $PL_WINDOW->hide;
1635 $SPELL_LIST->clear_spells;
1636 $CFPlus::UI::ROOT->emit (stop_game => ! ! $CONN);
1637
1638 &audio_music_set ([]);
1639
1640 return unless $CONN;
1641
1642 status "connection closed";
1643
1644 destroy_query_dialog $CONN;
1645 $CONN->destroy;
1646 $CONN = 0; # false, does not autovivify
1647
1648 undef $MAP;
1649}
1650
1651sub graphics_setup {
1652 my $vbox = new CFPlus::UI::VBox;
1653
1654 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]);
1655
1656 my $row = 0;
1657
1658 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "OpenGL Info");
1659 $table->add_at (1, $row++, new CFPlus::UI::Label valign => 0, fontsize => 0.8, text => CFPlus::OpenGL::gl_vendor . ", " . CFPlus::OpenGL::gl_version,
1660 can_events => 1,
1661 tooltip => "<tt><span size='8192'>" . (CFPlus::OpenGL::gl_extensions) . "</span></tt>");
1662
1663 my $vidmode_tooltip =
1664 "<b>Video Mode.</b> The video mode to use for fullscreen (and the window size for windowed operation). "
1665 . "The format is <i>width</i> x <i>height</i> \@ <i>depth-per-channel</i> + <i>alpha-channel</i>.";
1666
1667 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Video Mode");
1668 $table->add_at (1, $row++, my $hbox = new CFPlus::UI::HBox);
1669
1670 $hbox->add (my $mode_slider = new CFPlus::UI::Slider
1671 force_w => $WIDTH * 0.1, expand => 1, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 0, 1],
1672 tooltip => $vidmode_tooltip);
1673 $hbox->add (my $mode_label = new CFPlus::UI::Label
1674 align => 0, valign => 0, height => 0.8, template => "9999x9999@9+9",
1675 can_events => 1, tooltip => $vidmode_tooltip);
1676
1677 $mode_slider->connect (changed => sub {
1678 my ($self, $value) = @_;
1679
1680 $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
1681 $mode_label->set_text (sprintf '%dx%d@%d+%d', @{$SDL_MODES[$value]});
1682 });
1683 $mode_slider->emit (changed => $mode_slider->{range}[0]);
1684
1685 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fullscreen");
1686 $table->add_at (1, $row++, $FULLSCREEN_ENABLE = new CFPlus::UI::CheckBox
1687 state => $CFG->{fullscreen},
1688 tooltip => "Bring the client into fullscreen mode.",
1689 on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 }
1690 );
1691
1692 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
1693 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
1694 state => $CFG->{fast},
1695 tooltip => "Lower the visual quality considerably to speed up rendering.",
1696 on_changed => sub { my ($self, $value) = @_; $CFG->{fast} = $value; 0 }
1697 );
1698
1699 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
1700 $table->add_at (1, $row++, new CFPlus::UI::Slider
1701 range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
1702 tooltip => "The base font size used by most GUI elements that do not have their own setting.",
1703 on_changed => sub { $CFG->{gui_fontsize} = $_[1]; 0 },
1704 );
1705
1706 $table->add_at (1, $row++, new CFPlus::UI::Button
1707 expand => 1, align => 0, text => "Apply",
1708 tooltip => "Apply the video settings above.",
1709 on_activate => sub {
1710 video_shutdown ();
1711 video_init ();
1712 0
1713 }
1714 );
1715
1716 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Scale");
1717 $table->add_at (1, $row++, new CFPlus::UI::Slider
1718 range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
1719 tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
1720 on_changed => sub { my ($self, $value) = @_; $CFG->{map_scale} = 2 ** $value; 0 }
1721 );
1722
1723 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Smoothing");
1724 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
1725 state => $CFG->{map_smoothing},
1726 tooltip => "<b>Map Smoothing</b> tries to make tile borders less square. "
1727 . "This increases load on the graphics subsystem and works only with 2.x servers. "
1728 . "Changes take effect at next connection only.",
1729 on_changed => sub { my ($self, $value) = @_; $CFG->{map_smoothing} = $value; 0 }
1730 );
1731
1732 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fog of War");
1733 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
1734 state => $CFG->{fow_enable},
1735 tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
1736 on_changed => sub { my ($self, $value) = @_; $CFG->{fow_enable} = $value; 0 }
1737 );
1738
1739 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "FoW Intensity");
1740 $table->add_at (1, $row++, new CFPlus::UI::Slider
1741 range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
1742 tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
1743 on_changed => sub { my ($self, $value) = @_; $CFG->{fow_intensity} = $value; 0 }
1744 );
1745
1746 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Message Fontsize");
1747 $table->add_at (1, $row++, new CFPlus::UI::Slider
1748 range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
1749 tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant.",
1750 on_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 },
1751 );
1752
1753 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
1754 $table->add_at (1, $row++, new CFPlus::UI::Slider
1755 range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
1756 tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
1757 on_changed => sub {
1758 $CFG->{gauge_fontsize} = $_[1];
1759 &set_gauge_window_fontsize;
1760 0
1761 }
1762 );
1763
1764 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Gauge size");
1765 $table->add_at (1, $row++, new CFPlus::UI::Slider
1766 range => [$CFG->{gauge_size}, 0.2, 0.8],
1767 tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
1768 on_changed => sub {
1769 $CFG->{gauge_size} = $_[1];
1770 $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
1771 0
1772 }
1773 );
1774
1775 $vbox
1776}
1777
1778sub audio_setup {
1779 my $vbox = new CFPlus::UI::VBox;
1780
1781 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]);
1782
1783 my $row = 0;
1784
1785 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Audio Enable");
1786 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
1787 state => $CFG->{audio_enable},
1788 tooltip => "<b>Master Audio Enable.</b> If enabled, sound effects and music will be played. If disabled, no audio will be used and the soundcard will not be opened.",
1789 on_changed => sub { $CFG->{audio_enable} = $_[1]; 0 }
1790 );
1791# $table->add_at (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Effects Volume");
1792# $table->add_at (1, 8, new CFPlus::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], on_changed => sub {
1793# $CFG->{effects_volume} = $_[1];
1794# });
1795 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Background Music");
1796 $table->add_at (1, $row++, my $hbox = new CFPlus::UI::HBox);
1797 $hbox->add (new CFPlus::UI::CheckBox
1798 expand => 1, state => $CFG->{bgm_enable},
1799 tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
1800 on_changed => sub { $CFG->{bgm_enable} = $_[1]; 0 }
1801 );
1802 $hbox->add (new CFPlus::UI::Slider
1803 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
1804 tooltip => "The volume of the background music. Changes are instant.",
1805 on_changed => sub { $CFG->{bgm_volume} = $_[1]; CFPlus::MixMusic::volume $_[1] * 128; 0 }
1806 );
1807
1808 $table->add_at (1, $row++, new CFPlus::UI::Button
1809 expand => 1, align => 0, text => "Apply",
1810 tooltip => "Apply the audio settings",
1811 on_activate => sub {
1812 audio_shutdown ();
1813 audio_init ();
1814 0
1815 }
1816 );
1817
1818 $vbox
1819}
1820
1821sub set_gauge_window_fontsize {
1822 for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
1823 $_->set_fontsize ($::CFG->{gauge_fontsize});
1824 }
1825}
1826
1827sub make_gauge_window {
1828 my $gh = int $HEIGHT * $CFG->{gauge_size};
1829
1830 my $win = new CFPlus::UI::Frame (
1831 force_x => 0,
1832 force_y => "max",
1833 force_w => $WIDTH,
1834 force_h => $gh,
1835 );
1836
1837 $win->add (my $hbox = new CFPlus::UI::HBox
1838 children => [
1839 (new CFPlus::UI::HBox expand => 1),
1840 (new CFPlus::UI::VBox children => [
1841 (new CFPlus::UI::Empty expand => 1),
1842 (new CFPlus::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFPlus::UI::Table)),
1843 ]),
1844 (my $vbox = new CFPlus::UI::VBox),
1845 ],
1846 );
1847
1848 $vbox->add (new CFPlus::UI::HBox
1849 expand => 1,
1850 children => [
1851 (new CFPlus::UI::Empty expand => 1),
1852 (my $hb = new CFPlus::UI::HBox),
1853 ],
1854 );
1855
1856 $hb->add (my $hg = new CFPlus::UI::Gauge type => 'hp', tooltip => "#stat_health");
1857 $hb->add (my $mg = new CFPlus::UI::Gauge type => 'mana', tooltip => "#stat_mana");
1858 $hb->add (my $gg = new CFPlus::UI::Gauge type => 'grace', tooltip => "#stat_grace");
1859 $hb->add (my $fg = new CFPlus::UI::Gauge type => 'food', tooltip => "#stat_food");
1860
1861 $vbox->add (my $exp = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_exp");
1862 $vbox->add (my $rng = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_ranged");
1863
1864 $GAUGES = {
1865 exp => $exp, win => $win, range => $rng,
1866 food => $fg, mana => $mg, hp => $hg, grace => $gg
1867 };
1868
1869 &set_gauge_window_fontsize;
1870
1871 $win
1872}
1873
1874sub debug_setup {
1875 my $table = new CFPlus::UI::Table;
1876
1877 $table->add_at (0, 0, new CFPlus::UI::Label text => "Widget Borders");
1878 $table->add_at (1, 0, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1; 0 });
1879 $table->add_at (0, 1, new CFPlus::UI::Label text => "Tooltip Widget Info");
1880 $table->add_at (1, 1, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2; 0 });
1881 $table->add_at (0, 2, new CFPlus::UI::Label text => "Show FPS");
1882 $table->add_at (1, 2, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 });
1883 $table->add_at (0, 3, new CFPlus::UI::Label text => "Suppress Tooltips");
1884 $table->add_at (1, 3, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 });
1885 $table->add_at (0, 4, new CFPlus::UI::Button text => "die on click(tm)", on_activate => sub { &CFPlus::debug() } );
1886
1887 $table->add_at (0, 5, new CFPlus::UI::TextEdit text => "line1\0152\0153");#d#
1888
1889 $table
1890}
1891
1892sub stats_window {
1893 my $r = new CFPlus::UI::ScrolledWindow (
1894 expand => 1,
1895 scroll_y => 1
1896 );
1897 $r->add (my $vb = new CFPlus::UI::VBox);
1898
1899 $vb->add (new CFPlus::UI::FancyFrame
1900 label => "Player",
1901 child => (my $pi = new CFPlus::UI::VBox),
1902 );
1903
1904 $pi->add ($STATWIDS->{title} = new CFPlus::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
1905 can_hover => 1, can_events => 1,
1906 tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
1907 $pi->add ($STATWIDS->{map} = new CFPlus::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
1908 can_hover => 1, can_events => 1,
1909 tooltip => "The map you are currently on (if supported by the server).");
1910
1911 $pi->add (my $hb0 = new CFPlus::UI::HBox);
1912 $hb0->add ($STATWIDS->{weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
1913 can_hover => 1, can_events => 1,
1914 tooltip => "The weight of the player including all inventory items.");
1915 $hb0->add ($STATWIDS->{m_weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
1916 can_hover => 1, can_events => 1,
1917 tooltip => "The weight limit: you cannot carry more than this.");
1918
1919 $vb->add (new CFPlus::UI::FancyFrame
1920 label => "Primary/Secondary Statistics",
1921 child => (my $hb = new CFPlus::UI::HBox expand => 1),
1922 );
1923 $hb->add (my $tbl = new CFPlus::UI::Table expand => 1);
1924
1925 my $color2 = [1, 1, 0];
1926
1927 for (
1928 [0, 0, st_str => "Str", 30],
1929 [0, 1, st_dex => "Dex", 30],
1930 [0, 2, st_con => "Con", 30],
1931 [0, 3, st_int => "Int", 30],
1932 [0, 4, st_wis => "Wis", 30],
1933 [0, 5, st_pow => "Pow", 30],
1934 [0, 6, st_cha => "Cha", 30],
1935
1936 [2, 0, st_wc => "Wc", -120],
1937 [2, 1, st_ac => "Ac", -120],
1938 [2, 2, st_dam => "Dam", 120],
1939 [2, 3, st_arm => "Arm", 120],
1940 [2, 4, st_spd => "Spd", 10.54],
1941 [2, 5, st_wspd => "WSp", 10.54],
1942 ) {
1943 my ($col, $row, $id, $label, $template) = @$_;
1944
1945 $tbl->add ($col , $row, $STATWIDS->{$id} = new CFPlus::UI::Label
1946 font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0,
1947 align => +1, template => $template, tooltip => "#stat_$label");
1948 $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFPlus::UI::Label
1949 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0,
1950 align => -1, text => $label, tooltip => "#stat_$label");
1951 }
1952
1953 $vb->add (new CFPlus::UI::FancyFrame
1954 label => "Resistancies",
1955 child => (my $tbl2 = new CFPlus::UI::Table expand => 1),
1956 );
1957
1958 my $row = 0;
1959 my $col = 0;
1960
1961 my %resist_names = (
1962 slow => ["Slow",
1963 "<b>Slow</b> (slows you down when you are hit by the spell. Monsters will have an opportunity to come near you faster and hit you more often.)"],
1964 holyw => ["Holy Word",
1965 "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)"],
1966 conf => ["Confusion",
1967 "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)"],
1968 fire => ["Fire",
1969 "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)"],
1970 depl => ["Depletion",
1971 "<b>Depletion</b> (some monsters and other effects can cause stats depletion)"],
1972 magic => ["Magic",
1973 "<b>Magic</b> (resistance to magic spells like magic missile or similar)"],
1974 drain => ["Draining",
1975 "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)"],
1976 acid => ["Acid",
1977 "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)"],
1978 pois => ["Poison",
1979 "<b>Poison</b> (resistance to getting poisoned)"],
1980 para => ["Paralysation",
1981 "<b>Paralysation</b> (this resistance affects the chance you get paralysed)"],
1982 deat => ["Death",
1983 "<b>Death</b> (resistance against death spells)"],
1984 phys => ["Physical",
1985 "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat. The value displayed here is also displayed in the 'Arm' field on the left.)"],
1986 blind => ["Blind",
1987 "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)"],
1988 fear => ["Fear",
1989 "<b>Fear</b> (this attack will drive you away from monsters who cast this and hit you successfully, being resistant to this helps a lot when fighting those monsters)"],
1990 tund => ["Turn undead",
1991 "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead..."],
1992 elec => ["Electricity",
1993 "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)"],
1994 cold => ["Cold",
1995 "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)"],
1996 ghit => ["Ghost hit",
1997 "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)"],
1998 );
1999 for (qw/slow holyw conf fire depl magic
2000 drain acid pois para deat phys
2001 blind fear tund elec cold ghit/)
2002 {
2003 $tbl2->add ($col, $row,
2004 $STATWIDS->{"res_$_"} =
2005 new CFPlus::UI::Label
2006 font => $FONT_FIXED,
2007 template => "-100%",
2008 align => +1,
2009 valign => 0,
2010 can_events => 1,
2011 can_hover => 1,
2012 tooltip => $resist_names{$_}->[1],
2013 );
2014 $tbl2->add ($col + 1, $row, new CFPlus::UI::Image
2015 font => $FONT_FIXED,
2016 can_hover => 1,
2017 can_events => 1,
2018 path => "ui/resist/resist_$_.png",
2019 tooltip => $resist_names{$_}->[1],
2020 );
2021 $tbl2->add ($col + 2, $row, new CFPlus::UI::Label
2022 text => $resist_names{$_}->[0],
2023 font => $FONT_FIXED,
2024 can_hover => 1,
2025 can_events => 1,
2026 tooltip => $resist_names{$_}->[1],
2027 );
2028
2029 $row++;
2030 if ($row % 6 == 0) {
2031 $col += 3;
2032 $row = 0;
2033 }
2034 }
2035
2036 #update_stats_window ({});
2037
2038 $r
2039}
2040
2041sub skill_window {
2042 my $sw = new CFPlus::UI::ScrolledWindow (expand => 1);
2043 $sw->add ($STATWIDS->{skill_tbl} = new CFPlus::UI::Table expand => 1, col_expand => [0, 0, 1, 0, 0, 1]);
2044 $sw
2045}
2046
2047sub formsep($) {
2048 scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
2049}
2050
2051my $METASERVER_ATIME;
2052
2053sub update_metaserver {
2054 my ($metaserver_dialog) = @_;
2055
2056 $METASERVER = $metaserver_dialog
2057 if defined $metaserver_dialog;
2058
2059 return if $METASERVER_ATIME > time;
2060 $METASERVER_ATIME = time + 60;
2061
2062 my $table = $METASERVER->{table};
2063 $table->clear;
2064 $table->add_at (0, 0, my $label = new CFPlus::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
2065
2066 my $ok = 0;
2067
2068 CFPlus::background {
2069 my $ua = CFPlus::lwp_useragent;
2070
2071 CFPlus::background_msg CFPlus::from_json +(CFPlus::lwp_check $ua->get ($META_SERVER))->decoded_content;
2072 } sub {
2073 my ($msg) = @_;
2074 if ($msg) {
2075 $table->clear;
2076
2077 my @tip = (
2078 "The current number of users logged in on the server.",
2079 "The hostname of the server.",
2080 "The time this server has been running without being restarted.",
2081 "The server software version - a '+' indicates a Crossfire+ server.",
2082 "Short information about this server provided by its admins.",
2083 );
2084 my @col = qw(#Users Host Uptime Version Description);
2085 $table->add_at ($_, 0, new CFPlus::UI::Label
2086 can_hover => 1, can_events => 1,
2087 align => 0, fg => [1, 1, 0],
2088 text => $col[$_], tooltip => $tip[$_])
2089 for 0 .. $#col;
2090
2091 my @align = qw(1 0 1 1 -1);
2092
2093 my $y = 0;
2094 for my $m (@{ $msg->{servers} }) {
2095 my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime, $highlight) =
2096 @$m{qw(ip age hostname users version description ibytes obytes uptime highlight)};
2097
2098 for ($desc) {
2099 s/<br>/\n/gi;
2100 s/<li>/\n· /gi;
2101 s/<.*?>//sgi;
2102 s/&amp;/&/g;
2103 s/&lt;/</g;
2104 s/&gt;/>/g;
2105 }
2106
2107 $uptime = sprintf "%dd %02d:%02d:%02d",
2108 (int $uptime / 86400),
2109 (int $uptime / 3600) % 24,
2110 (int $uptime / 60) % 60,
2111 $uptime % 60;
2112
2113 $m = [$users, $host, $uptime, $version, $desc];
2114
2115 $y++;
2116
2117 $table->add_at (scalar @$m, $y, new CFPlus::UI::VBox children => [
2118 (new CFPlus::UI::Button
2119 text => "Use",
2120 tooltip => "Put this server into the <b>Host:Port</b> field",
2121 on_activate => sub {
2122 $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
2123 $METASERVER->hide;
2124 0
2125 },
2126 ),
2127 (new CFPlus::UI::Empty expand => 1),
2128 ]);
2129
2130 $table->add_at ($_, $y, new CFPlus::UI::Label
2131 max_w => $::WIDTH * 0.4,
2132 ellipsise => 0,
2133 align => $align[$_],
2134 text => $m->[$_],
2135 tooltip => $tip[$_],
2136 fg => ($highlight ? [1, 1, 1] : [.7, .7, .7]),
2137 can_hover => 1,
2138 can_events => 1,
2139 fontsize => 0.8)
2140 for 0 .. $#$m;
2141 }
2142 } else {
2143 $ok or $label->set_text ("error while contacting metaserver");
2144 }
2145 };
2146
2147}
2148
2149sub metaserver_dialog {
2150 my $vbox = new CFPlus::UI::VBox;
2151 my $table = new CFPlus::UI::Table;
2152 $vbox->add (new CFPlus::UI::ScrolledWindow expand => 1, child => $table);
2153
2154 my $dialog = new CFPlus::UI::Toplevel
2155 title => "Server List",
2156 name => 'metaserver_dialog',
2157 x => 'center',
2158 y => 'center',
2159 z => 3,
2160 force_w => $::WIDTH * 0.9,
2161 force_h => $::HEIGHT * 0.7,
2162 child => $vbox,
2163 has_close_button => 1,
2164 table => $table,
2165 on_visibility_change => sub {
2166 update_metaserver ($_[0]) if $_[1];
2167 0
2168 },
2169 ;
2170
2171 $dialog
2172}
2173
2174sub server_setup {
2175 my $vbox = new CFPlus::UI::VBox;
2176
2177 $vbox->add (new CFPlus::UI::FancyFrame
2178 label => "Connection Settings",
2179 child => (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]),
2180 );
2181 $table->add_at (0, 2, new CFPlus::UI::Label valign => 0, align => 1, text => "Host:Port");
2182
2183 {
2184 $table->add_at (1, 2, my $vbox = new CFPlus::UI::VBox);
2185
2186 $vbox->add (
2187 $HOST_ENTRY = new CFPlus::UI::Entry
2188 expand => 1,
2189 text => $CFG->{profile}{default}{host},
2190 tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
2191 on_changed => sub {
2192 my ($self, $value) = @_;
2193 $CFG->{profile}{default}{host} = $value;
2194 0
2195 }
2196 );
2197
2198 $vbox->add (new CFPlus::UI::Button
2199 expand => 1,
2200 text => "Server List",
2201 other => $METASERVER,
2202 tooltip => "Show a list of available crossfire servers",
2203 on_activate => sub { $METASERVER->toggle_visibility; 0 },
2204 on_visibility_change => sub { $METASERVER->hide unless $_[1]; 0 },
2205 );
2206 }
2207
2208 $table->add_at (0, 4, new CFPlus::UI::Label valign => 0, align => 1, text => "Username");
2209 $table->add_at (1, 4, new CFPlus::UI::Entry
2210 text => $CFG->{profile}{default}{user},
2211 tooltip => "The name of your character on the server",
2212 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value }
2213 );
2214
2215 $table->add_at (0, 5, new CFPlus::UI::Label valign => 0, align => 1, text => "Password");
2216 $table->add_at (1, 5, new CFPlus::UI::Entry
2217 text => $CFG->{profile}{default}{password},
2218 hidden => 1,
2219 tooltip => "The password for your character",
2220 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value }
2221 );
2222
2223 $table->add_at (0, 7, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Size");
2224 $table->add_at (1, 7, new CFPlus::UI::Slider
2225 force_w => 100,
2226 range => [$CFG->{mapsize}, 10, 100, 0, 1],
2227 tooltip => "This is the size of the portion of the map update the server sends you. "
2228 . "If you set this to a high value you will be able to see further, "
2229 . "but you also increase bandwidth requirements and latency. "
2230 . "This option is only used once at log-in.",
2231 on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 0 },
2232 );
2233
2234 $table->add_at (0, 8, new CFPlus::UI::Label valign => 0, align => 1, text => "Face Prefetch");
2235 $table->add_at (1, 8, new CFPlus::UI::CheckBox
2236 state => $CFG->{face_prefetch},
2237 tooltip => "<b>Background Image Prefetch</b>\n\n"
2238 . "If enabled, the client automatically pre-fetches images from the server. "
2239 . "This might increase or create lag, but increases the chances "
2240 . "of faces being ready for display when you encounter them. "
2241 . "It also uses up server bandwidth on every connect, "
2242 . "so only set it if you really need to prefetch images. "
2243 . "This option can be set and unset any time.",
2244 on_changed => sub { $CFG->{face_prefetch} = $_[1]; 0 },
2245 );
2246
2247 $table->add_at (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Rate");
2248 $table->add_at (1, 9, new CFPlus::UI::Entry
2249 text => $CFG->{output_rate},
2250 tooltip => "The approximate bandwidth in bytes per second that the server should not exceed "
2251 . "when sending images, to ensure interactiveness. When 0 or unset, the server "
2252 . "default will be used, which is usually around 100kb/s.",
2253 on_changed => sub { $CFG->{output_rate} = $_[1]; 0 },
2254 );
2255
2256 $table->add_at (0, 10, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Count");
2257 $table->add_at (1, 10, new CFPlus::UI::Entry
2258 text => $CFG->{output_count},
2259 tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
2260 on_changed => sub { $CFG->{output_count} = $_[1]; 0 },
2261 );
2262
2263 $table->add_at (0, 11, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Sync");
2264 $table->add_at (1, 11, new CFPlus::UI::Entry
2265 text => $CFG->{output_sync},
2266 tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
2267 on_changed => sub { $CFG->{output_sync} = $_[1]; 0 },
2268 );
2269
2270 $table->add_at (1, 12, $LOGIN_BUTTON = new CFPlus::UI::Button
2271 expand => 1,
2272 align => 0,
2273 text => "Login",
2274 on_activate => sub {
2275 $CONN ? stop_game
2276 : start_game;
2277 0
2278 },
2279 );
2280
2281 $vbox->add (new CFPlus::UI::FancyFrame
2282 label => "Server Info",
2283 child => ($SERVER_INFO = new CFPlus::UI::Label ellipsise => 0),
2284 );
2285
2286 $vbox
2287}
2288
2289sub client_setup {
2290 my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1];
2291
2292 my $row = 0;
2293
2294 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Chat Command");
2295 $table->add_at (1, $row++, my $saycmd = new CFPlus::UI::Entry
2296 text => $CFG->{say_command},
2297 tooltip => "This is the command that will be used if you write a line in the message window entry or press <b>\"</b> in the map window. "
2298 . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
2299 . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
2300 on_changed => sub {
2301 my ($self, $value) = @_;
2302 $CFG->{say_command} = $value;
2303 0
2304 }
2305 );
2306
2307 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Tip of the day");
2308 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
2309 state => $CFG->{show_tips},
2310 tooltip => "Show the <b>Tip of the day</b> window at startup?",
2311 on_changed => sub {
2312 my ($self, $value) = @_;
2313 $CFG->{show_tips} = $value;
2314 0
2315 }
2316 );
2317
2318 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Messages Window Size");
2319 $table->add_at (1, $row++, my $saycmd = new CFPlus::UI::Entry
2320 text => $CFG->{logview_max_par},
2321 tooltip => "This is maximum number of messages remembered in the <b>Messages</b> window. If the server "
2322 . "sends more messages than this number, older messages get removed to save memory and "
2323 . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
2324 on_changed => sub {
2325 my ($self, $value) = @_;
2326 $LOGVIEW->{max_par} = $CFG->{logview_max_par} = $value*1;
2327 0
2328 },
2329 );
2330
2331 $table
2332}
2333
2334sub message_window {
2335 my $window = new CFPlus::UI::Toplevel
2336 name => "message_window",
2337 title => "Messages",
2338 border_bg => [1, 1, 1, 1],
2339 x => "max",
2340 y => 0,
2341 force_w => $::WIDTH * 0.4,
2342 force_h => $::HEIGHT * 0.5,
2343 child => (my $vbox = new CFPlus::UI::VBox),
2344 has_close_button => 1;
2345
2346 $vbox->add ($LOGVIEW);
2347
2348 $vbox->add (my $input = new CFPlus::UI::Entry
2349 tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
2350 . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
2351 . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
2352 . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
2353 on_focus_in => sub {
2354 my ($input, $prev_focus) = @_;
2355
2356 delete $input->{refocus_map};
2357
2358 if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
2359 $input->{refocus_map} = 1;
2360 }
2361 delete $input->{auto_activated};
2362
2363 0
2364 },
2365 on_activate => sub {
2366 my ($input, $text) = @_;
2367 $input->set_text ('');
2368
2369 if ($text =~ /^\/(.*)/) {
2370 $::CONN->user_send ($1);
2371 } else {
2372 my $say_cmd = $::CFG->{say_command} || 'say';
2373 $::CONN->user_send ("$say_cmd $text");
2374 }
2375 if ($input->{refocus_map}) {
2376 delete $input->{refocus_map};
2377 $MAPWIDGET->focus_in
2378 }
2379
2380 0
2381 },
2382 on_escape => sub {
2383 $MAPWIDGET->grab_focus;
2384
2385 0
2386 },
2387 );
2388
2389 $CONSOLE = {
2390 window => $window,
2391 input => $input,
2392 };
2393
2394 $window
2395}
2396
2397sub autopickup_setup {
2398 my $r = new CFPlus::UI::ScrolledWindow (
2399 expand => 1,
2400 scroll_y => 1
2401 );
2402 $r->add (my $table = new CFPlus::UI::Table
2403 row_expand => [0],
2404 col_expand => [0, 1, 0, 1],
2405 );
2406
2407 for (
2408 ["General", 0, 0,
2409 ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
2410 ["Inhibit autopickup" => PICKUP_INHIBIT],
2411 ["Stop before pickup" => PICKUP_STOP],
2412 ["Debug autopickup" => PICKUP_DEBUG],
2413 ],
2414 ["Weapons", 0, 6,
2415 ["All weapons" => PICKUP_ALLWEAPON],
2416 ["Missile weapons" => PICKUP_MISSILEWEAPON],
2417 ["Bows" => PICKUP_BOW],
2418 ["Arrows" => PICKUP_ARROW],
2419 ],
2420 ["Armour", 0, 12,
2421 ["Helmets" => PICKUP_HELMET],
2422 ["Shields" => PICKUP_SHIELD],
2423 ["Body Armour" => PICKUP_ARMOUR],
2424 ["Boots" => PICKUP_BOOTS],
2425 ["Gloves" => PICKUP_GLOVES],
2426 ["Cloaks" => PICKUP_CLOAK],
2427 ],
2428
2429 ["Readables", 2, 0,
2430 ["Spellbooks" => PICKUP_SPELLBOOK],
2431 ["Skillscrolls" => PICKUP_SKILLSCROLL],
2432 ["Normal Books/Scrolls" => PICKUP_READABLES],
2433 ],
2434 ["Misc", 2, 5,
2435 ["Food" => PICKUP_FOOD],
2436 ["Drinks" => PICKUP_DRINK],
2437 ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
2438 ["Keys" => PICKUP_KEY],
2439 ["Magical Items" => PICKUP_MAGICAL],
2440 ["Potions" => PICKUP_POTION],
2441 ["Magic Devices" => PICKUP_MAGIC_DEVICE],
2442 ["Ignore cursed" => PICKUP_NOT_CURSED],
2443 ["Jewelery" => PICKUP_JEWELS],
2444 ["Flesh" => PICKUP_FLESH],
2445 ],
2446 ["Weight/Value ratio", 2, 17]
2447 )
2448 {
2449 my ($title, $x, $y, @bits) = @$_;
2450 $table->add_at ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
2451
2452 for (@bits) {
2453 ++$y;
2454
2455 my $mask = $_->[1];
2456 $table->add_at ($x , $y, new CFPlus::UI::Label text => $_->[0], align => 1, expand => 1);
2457 $table->add_at ($x+1, $y, my $checkbox = new CFPlus::UI::CheckBox
2458 state => $::CFG->{pickup} & $mask,
2459 on_changed => sub {
2460 my ($box, $value) = @_;
2461
2462 if ($value) {
2463 $::CFG->{pickup} |= $mask;
2464 } else {
2465 $::CFG->{pickup} &= ~$mask;
2466 }
2467
2468 $::CONN->send_command ("pickup $::CFG->{pickup}")
2469 if defined $::CONN;
2470
2471 0
2472 });
2473
2474 ${$_->[2]} = $checkbox if $_->[2];
2475 }
2476 }
2477
2478 $table->add_at (2, 18, new CFPlus::UI::ValSlider
2479 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
2480 template => ">= 99",
2481 to_value => sub { ">= " . 5 * $_[0] },
2482 on_changed => sub {
2483 my ($slider, $value) = @_;
2484
2485 $::CFG->{pickup} &= ~0xF;
2486 $::CFG->{pickup} |= int $value
2487 if $value;
2488 1;
2489 });
2490
2491 $table->add_at (3, 18, new CFPlus::UI::Button
2492 text => "set",
2493 on_activate => sub {
2494 $::CONN->send_command ("pickup $::CFG->{pickup}")
2495 if defined $::CONN;
2496 0
2497 });
2498
2499 $r
2500}
2501
2502my %SORT_ORDER = (
2503 type => undef,
2504 mtime => sub {
2505 my $NOW = time;
2506 sort {
2507 my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
2508 my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
2509
2510 ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
2511 or $btime <=> $atime
2512 or $a->{type} <=> $b->{type}
2513 } @_
2514 },
2515 weight => sub { sort {
2516 $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
2517 or $a->{type} <=> $b->{type}
2518 } @_ },
2519);
2520
2521sub inventory_widget {
2522 my $hb = new CFPlus::UI::HBox homogeneous => 1;
2523
2524 $hb->add (my $vb1 = new CFPlus::UI::VBox);
2525 $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
2526
2527 $vb1->add (my $hb1 = new CFPlus::UI::HBox);
2528
2529 use sort 'stable';
2530
2531 $hb1->add (new CFPlus::UI::Selector
2532 value => $::CFG->{inv_sort},
2533 options => [
2534 [type => "Type/Name"],
2535 [mtime => "Recent/Normal/Locked"],
2536 [weight => "Weight/Type"],
2537 ],
2538 on_changed => sub {
2539 $::CFG->{inv_sort} = $_[1];
2540 $INV->set_sort_order ($SORT_ORDER{$_[1]});
2541 },
2542 );
2543 $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
2544 #TODO# update to weigh/maxweight
2545 $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
2546
2547 $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
2548 $sw1->add ($INV = new CFPlus::UI::Inventory);
2549 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
2550
2551 $hb->add (my $vb2 = new CFPlus::UI::VBox);
2552
2553 $vb2->add ($INV_RIGHT_HB = new CFPlus::UI::HBox);
2554
2555 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
2556 $sw2->add ($INVR = new CFPlus::UI::Inventory);
2557
2558 # XXX: Call after $INVR = ... because set_opencont sets the items
2559 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
2560
2561 $hb
2562}
2563
2564sub toggle_player_page {
2565 my ($widget) = @_;
2566
2567 if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
2568 $PL_WINDOW->hide;
2569 } else {
2570 $PL_NOTEBOOK->set_current_page ($widget);
2571 $PL_WINDOW->show;
2572 }
2573}
2574
2575sub player_window {
2576 my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
2577 x => "center",
2578 y => "center",
2579 force_w => $WIDTH * 9/10,
2580 force_h => $HEIGHT * 9/10,
2581 title => "Player",
2582 name => "playerbook",
2583 has_close_button => 1
2584 ;
2585
2586 my $ntb =
2587 $PL_NOTEBOOK =
2588 new CFPlus::UI::Notebook expand => 1;
2589
2590 $ntb->add (
2591 "Statistics (F2)" => $STATS_PAGE = stats_window,
2592 "Shows statistics, where all your Stats and Resistances are shown."
2593 );
2594 $ntb->add (
2595 "Skills (F3)" => $SKILL_PAGE = skill_window,
2596 "Shows all your Skills."
2597 );
2598
2599 my $spellsw = $SPELL_PAGE = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
2600 $spellsw->add ($SPELL_LIST = new CFPlus::UI::SpellList);
2601 $ntb->add (
2602 "Spellbook (F4)" => $spellsw,
2603 "Displays all spells you have and lets you edit keyboard shortcuts for them."
2604 );
2605 $ntb->add (
2606 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
2607 "Toggles the inventory window, where you can manage your loot (or treasures :). "
2608 . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
2609 );
2610 $ntb->add (Pickup => autopickup_setup,
2611 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
2612
2613 $ntb->set_current_page ($INVENTORY_PAGE);
2614
2615 $plwin->add ($ntb);
2616 $plwin
2617}
2618
2619sub keyboard_setup {
2620 CFPlus::Macro::keyboard_setup
2621}
2622
2623sub help_window {
2624 my $win = new CFPlus::UI::Toplevel
2625 x => 'center',
2626 y => 'center',
2627 z => 4,
2628 name => 'doc_browser',
2629 force_w => int $WIDTH * 7/8,
2630 force_h => int $HEIGHT * 7/8,
2631 title => "Help Browser",
2632 has_close_button => 1;
2633
2634 $win->add (my $vbox = new CFPlus::UI::VBox);
2635
2636 $vbox->add (new CFPlus::UI::FancyFrame
2637 label => "Navigation",
2638 child => (my $buttons = new CFPlus::UI::HBox),
2639 );
2640 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
2641 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2642
2643 my @history;
2644 my @future;
2645 my $curnode;
2646
2647 my $load_node; $load_node = sub {
2648 my ($node, $para) = @_;
2649
2650 $buttons->clear;
2651
2652 $buttons->add (new CFPlus::UI::Button
2653 text => "⇤",
2654 tooltip => "back to the starting page",
2655 on_activate => sub {
2656 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
2657 unshift @future, @history;
2658 @history = ();
2659 $load_node->(@{shift @future});
2660 },
2661 );
2662
2663 if (@history) {
2664 $buttons->add (new CFPlus::UI::Button
2665 text => "⋘",
2666 tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
2667 on_activate => sub {
2668 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
2669 $load_node->(@{pop @history});
2670 },
2671 );
2672 }
2673
2674 if (@future) {
2675 $buttons->add (new CFPlus::UI::Button
2676 text => "â‹™",
2677 tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
2678 on_activate => sub {
2679 push @history, [$curnode, $viewer->current_paragraph];
2680 $load_node->(@{shift @future});
2681 },
2682 );
2683 }
2684
2685 $buttons->add (new CFPlus::UI::Label text => " ");
2686
2687 my @path = CFPlus::Pod::full_path_of $node;
2688 pop @path; # drop current node
2689
2690 for my $node (@path) {
2691 $buttons->add (new CFPlus::UI::Button
2692 text => $node->{kw}[0],
2693 tooltip => "go to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $node) . "</i>",
2694 on_activate => sub {
2695 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2696 $load_node->($node);
2697 },
2698 );
2699 $buttons->add (new CFPlus::UI::Label text => "/");
2700 }
2701
2702 $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
2703
2704 $curnode = $node;
2705
2706 $viewer->clear;
2707 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
2708 $viewer->scroll_to ($para);
2709 };
2710
2711 $load_node->(CFPlus::Pod::find pod => "mainpage");
2712
2713 $CFPlus::Pod::goto_document = sub {
2714 my (@path) = @_;
2715
2716 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2717
2718 $load_node->((CFPlus::Pod::find @path)[0]);
2719 $win->show;
2720 };
2721
2722 $win
2723}
2724
2725sub open_string_query {
2726 my ($title, $cb, $txt, $tooltip) = @_;
2727 my $dialog = new CFPlus::UI::Toplevel
2728 x => "center",
2729 y => "center",
2730 z => 50,
2731 force_w => $WIDTH * 4/5,
2732 title => $title;
2733
2734 $dialog->add (
2735 my $e = new CFPlus::UI::Entry
2736 on_activate => sub { $cb->(@_); $dialog->hide; 0 },
2737 on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
2738 tooltip => $tooltip
2739 );
2740
2741 $e->grab_focus;
2742 $e->set_text ($txt) if $txt;
2743 $dialog->show;
2744}
2745
2746sub open_quit_dialog {
2747 unless ($QUIT_DIALOG) {
2748 $QUIT_DIALOG = new CFPlus::UI::Toplevel
2749 x => "center",
2750 y => "center",
2751 z => 50,
2752 title => "Really Quit?",
2753 on_key_down => sub {
2754 my ($dialog, $ev) = @_;
2755 $ev->{sym} == 27 and $dialog->hide;
2756 }
2757 ;
2758
2759 $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
2760
2761 $vb->add (new CFPlus::UI::Label
2762 text => "You should find a savebed and apply it first!",
2763 max_w => $WIDTH * 0.25,
2764 ellipsize => 0,
2765 );
2766 $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
2767 $hb->add (new CFPlus::UI::Button
2768 text => "Ok",
2769 expand => 1,
2770 on_activate => sub { $QUIT_DIALOG->hide; 0 },
2771 );
2772 $hb->add (new CFPlus::UI::Button
2773 text => "Quit anyway",
2774 expand => 1,
2775 on_activate => sub { exit },
2776 );
2777 }
2778
2779 $QUIT_DIALOG->show;
2780 $QUIT_DIALOG->grab_focus;
2781}
2782
2783sub show_tip_of_the_day {
2784 # find all tips
2785 my @tod = CFPlus::Pod::find tip_of_the_day => "*";
2786
2787 CFPlus::DB::get state => "tip_of_the_day", sub {
2788 my ($todindex) = @_;
2789 $todindex = 0 if $todindex >= @tod;
2790 CFPlus::DB::put state => tip_of_the_day => $todindex + 1, sub { };
2791
2792 # create dialog
2793 my $dialog;
2794
2795 my $close = sub {
2796 $dialog->destroy;
2797 };
2798
2799 $dialog = new CFPlus::UI::Toplevel
2800 x => "center",
2801 y => "center",
2802 z => 3,
2803 name => 'tip_of_the_day',
2804 force_w => int $WIDTH * 4/9,
2805 force_h => int $WIDTH * 2/9,
2806 title => "Tip of the day #" . (1 + $todindex),
2807 child => my $vbox = new CFPlus::UI::VBox,
2808 has_close_button => 1,
2809 on_delete => $close,
2810 ;
2811
2812 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
2813 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2814 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]);
2815
2816 $vbox->add (my $table = new CFPlus::UI::Table col_expand => [0, 1]);
2817
2818 $table->add_at (0, 0, new CFPlus::UI::Button
2819 text => "Close",
2820 tooltip => "Close the tip of the day window. To never see it again, disable the tip of the day in the <b>Server Setup</b>.",
2821 on_activate => $close,
2822 );
2823
2824 $table->add_at (2, 0, new CFPlus::UI::Button
2825 text => "Next",
2826 tooltip => "Show the next <b>Tip of the day</b>.",
2827 on_activate => sub {
2828 $close->();
2829 &show_tip_of_the_day;
2830 },
2831 );
2832
2833 $dialog->show;
2834 };
2835}
2836
2837sub sdl_init {
2838 CFPlus::SDL_Init
2839 and die "SDL::Init failed!\n";
2840}
2841
2842sub video_init {
2843 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
2844
2845 my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
2846
2847 ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
2848 $FULLSCREEN = $CFG->{fullscreen};
2849 $FAST = $CFG->{fast};
2850
2851 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
2852 or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
2853
2854 $SDL_ACTIVE = 1;
2855 $LAST_REFRESH = time - 0.01;
2856
2857 CFPlus::OpenGL::init;
2858
2859 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
2860
2861 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
2862
2863 #############################################################################
2864
2865 if ($DEBUG_STATUS) {
2866 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
2867 } else {
2868 # create the widgets
2869
2870 $DEBUG_STATUS = new CFPlus::UI::Label
2871 padding => 0,
2872 z => 100,
2873 force_x => "max",
2874 force_y => 0;
2875 $DEBUG_STATUS->show;
2876
2877 $STATUSBOX = new CFPlus::UI::Statusbox;
2878 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
2879
2880 (new CFPlus::UI::Frame
2881 bg => [0, 0, 0, 0.4],
2882 force_x => 0,
2883 force_y => "max",
2884 child => $STATUSBOX,
2885 )->show;
2886
2887 CFPlus::UI::Toplevel->new (
2888 title => "Map",
2889 name => "mapmap",
2890 x => 0,
2891 y => $FONTSIZE + 8,
2892 border_bg => [1, 1, 1, 192/255],
2893 bg => [1, 1, 1, 0],
2894 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
2895 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
2896 ),
2897 )->show;
2898
2899 $MAPWIDGET = new CFPlus::MapWidget;
2900 $MAPWIDGET->connect (activate_console => sub {
2901 my ($mapwidget, $preset) = @_;
2902
2903 if ($CONSOLE) {
2904 $CONSOLE->{input}->{auto_activated} = 1;
2905 $CONSOLE->{input}->grab_focus;
2906
2907 if ($preset && $CONSOLE->{input}->get_text eq '') {
2908 $CONSOLE->{input}->set_text ($preset);
2909 }
2910 }
2911 });
2912 $MAPWIDGET->show;
2913 $MAPWIDGET->grab_focus;
2914
2915 $LOGVIEW = new CFPlus::UI::TextScroller
2916 expand => 1,
2917 font => $FONT_FIXED,
2918 fontsize => $::CFG->{log_fontsize},
2919 indent => -4,
2920 can_hover => 1,
2921 can_events => 1,
2922 max_par => $CFG->{logview_max_par},
2923 tooltip => "<b>Server Log</b>. This text viewer contains all recent messages sent by the server.",
2924 ;
2925
2926 $SETUP_DIALOG = new CFPlus::UI::Toplevel
2927 title => "Setup",
2928 name => "setup_dialog",
2929 x => 'center',
2930 y => 'center',
2931 z => 2,
2932 force_w => $::WIDTH * 0.6,
2933 force_h => $::HEIGHT * 0.6,
2934 has_close_button => 1,
2935 ;
2936
2937 $METASERVER = metaserver_dialog;
2938
2939 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
2940 filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
2941
2942 $SETUP_NOTEBOOK->add (Server => $SETUP_SERVER = server_setup,
2943 "Configure the server to play on, your username, password and other server-related options.");
2944 $SETUP_NOTEBOOK->add (Client => client_setup,
2945 "Configure various client-specific settings.");
2946 $SETUP_NOTEBOOK->add (Graphics => graphics_setup,
2947 "Configure the video mode, performance, fonts and other graphical aspects of the game.");
2948 $SETUP_NOTEBOOK->add (Audio => audio_setup,
2949 "Configure the use of audio, sound effects and background music.");
2950 $SETUP_NOTEBOOK->add (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
2951 "Lets you define, edit and delete key bindings."
2952 . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
2953 . "with nothing set and the recording started. After doing the actions you "
2954 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
2955 . "After pressing the combo the binding will be saved automatically and the "
2956 . "binding editor closes");
2957 $SETUP_NOTEBOOK->add (Debug => debug_setup,
2958 "Some debuggin' options. Do not ask.");
2959
2960 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
2961
2962 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
2963 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
2964
2965 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
2966 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
2967
2968 make_gauge_window->show; # XXX: this has to be set before make_stats_window as make_stats_window calls update_stats_window which updated the gauges also X-D
2969
2970 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
2971 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
2972
2973 $BUTTONBAR->add (new CFPlus::UI::Button
2974 text => "Save Config",
2975 tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
2976 on_activate => sub {
2977 $::CFG->{layout} = CFPlus::UI::get_layout;
2978 CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
2979 status "Configuration Saved";
2980 0
2981 },
2982 );
2983
2984 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
2985 tooltip => "View Documentation");
2986
2987
2988 $BUTTONBAR->add (new CFPlus::UI::Button
2989 text => "Quit",
2990 tooltip => "Terminates the program",
2991 on_activate => sub {
2992 if ($CONN) {
2993 open_quit_dialog;
2994 } else {
2995 exit;
2996 }
2997 0
2998 },
2999 );
3000
3001 $BUTTONBAR->show;
3002 $SETUP_DIALOG->show;
3003 }
3004
3005 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
3006}
3007
3008sub setup_build_button {
3009 my ($enabled) = @_;
3010 if ($enabled) {
3011 $BUILD_BUTTON->hide if $BUILD_BUTTON;
3012 $BUILD_BUTTON ||= new CFPlus::UI::Button
3013 text => "Build",
3014 tooltip => "Opens the ingame builder",
3015 on_activate => sub {
3016 if ($CONN) {
3017 $CONN->send_ext_req (builder_player_items => sub {
3018 open_ingame_editor ($_[0]) if exists $_[0]->{items};
3019 });
3020 }
3021 0
3022 };
3023 $BUTTONBAR->add ($BUILD_BUTTON);
3024 } else {
3025 $BUILD_BUTTON->hide if $BUILD_BUTTON;
3026 }
3027}
3028
3029sub open_ingame_editor {
3030 my ($msg) = @_;
3031
3032 my $win = new CFPlus::UI::Toplevel
3033 x => 0,
3034 y => 'center',
3035 z => 4,
3036 name => 'builder_window',
3037 force_w => int $WIDTH * 1/4,
3038 force_h => int $HEIGHT * 3/4,
3039 title => "In game builder",
3040 has_close_button => 1;
3041
3042 my $r = new CFPlus::UI::ScrolledWindow (
3043 expand => 1,
3044 scroll_y => 1
3045 );
3046 $r->add (my $vb = new CFPlus::UI::VBox);
3047 $win->add ($r);
3048
3049
3050 $vb->add (
3051 new CFPlus::UI::Button
3052 text => "Disable build mode",
3053 on_activate => sub { $::IN_BUILD_MODE = undef }
3054 );
3055 $vb->add (
3056 new CFPlus::UI::Button
3057 text => "ERASE",
3058 on_activate => sub { $::IN_BUILD_MODE = { do_erase => 1 } }
3059 );
3060
3061 for my $itemarchname (
3062 sort {
3063 $msg->{items}->{$a}->{build_arch_name}
3064 cmp $msg->{items}->{$b}->{build_arch_name}
3065 } keys %{$msg->{items}}
3066 ) {
3067 my $info = $msg->{items}->{$itemarchname};
3068 $vb->add (
3069 new CFPlus::UI::Button text => $info->{build_arch_name},
3070 on_activate => sub {
3071 $::IN_BUILD_MODE = { item => $itemarchname, info => $info };
3072
3073 if (grep { $msg->{items}->{$itemarchname}->{$_} } qw/has_connection has_name has_text/) {
3074 build_mode_query_arch_info ();
3075 }
3076 }
3077 );
3078 }
3079
3080 $win->show;
3081}
3082
3083sub build_mode_query_arch_info {
3084 my ($iteminfo) = $::IN_BUILD_MODE;
3085 my $itemarchname = $iteminfo->{item};
3086 my $info = $iteminfo->{info};
3087
3088 my $dialog = new CFPlus::UI::Toplevel
3089 x => "center",
3090 y => "center",
3091 z => 50,
3092 force_w => int $WIDTH * 1/2,
3093 title => "Enter information for placement of '$itemarchname'",
3094 has_close_button => 1;
3095
3096 $dialog->add (my $vb = new CFPlus::UI::VBox expand => 1);
3097
3098 $vb->add (my $table = new CFPlus::UI::Table expand => 1);
3099 my $row = 0;
3100 if ($info->{has_name}) {
3101 $table->add_at (0, $row, new CFPlus::UI::Label text => "Name:");
3102 $table->add_at (1, $row++, new CFPlus::UI::Entry expand => 1, on_changed => sub { $::IN_BUILD_MODE->{name} = $_[1]; 0 });
3103 }
3104 if ($info->{has_text}) {
3105 $table->add_at (0, $row, new CFPlus::UI::Label text => "Text:");
3106 $table->add_at (1, $row++, new CFPlus::UI::Entry expand => 1, on_changed => sub { $::IN_BUILD_MODE->{text} = $_[1]; 0 });
3107 }
3108 if ($info->{has_connection}) {
3109 $table->add_at (0, $row, new CFPlus::UI::Label text => "Connection ID:");
3110 $table->add_at (1, $row++,
3111 new CFPlus::UI::Entry
3112 expand => 1,
3113 on_changed => sub { $::IN_BUILD_MODE->{connection} = $_[1]; 0 },
3114 tooltip => "Enter the connection ID here. The connection ID connects actors like a lever to a gate or a magic ear to a gate"
3115 );
3116 }
3117
3118 $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
3119 $hb->add (new CFPlus::UI::Button
3120 text => "Close",
3121 expand => 1,
3122 on_activate => sub { $dialog->hide; 0 },
3123 );
3124 $dialog->show;
3125}
3126
3127sub video_shutdown {
3128 CFPlus::OpenGL::shutdown;
3129
3130 undef $SDL_ACTIVE;
3131}
3132
3133sub audio_channel_finished {
3134 my ($channel) = @_;
3135
3136 #warn "channel $channel finished\n";#d#
3137}
3138
3139sub audio_music_set {
3140 my ($songs) = @_;
3141
3142 my @want =
3143 grep $_,
3144 map $CONN->{music_meta}{$_},
3145 @$songs;
3146
3147 if (@want) {
3148 @MUSIC_WANT = @want;
3149 &audio_music_changed ();
3150 }
3151}
3152
3153sub audio_music_start {
3154 my $path = $MUSIC_PLAYING->{path}
3155 or return;
3156
3157 CFPlus::DB::prefetch_file $path, 1024_000, sub {
3158 # music might have changed...
3159 $path eq $MUSIC_PLAYING->{path}
3160 or return &audio_music_start ();
3161
3162 $MUSIC_PLAYER = new_from_file CFPlus::MixMusic $path;
3163
3164 my $NOW = time;
3165
3166 if ($MUSIC_PLAYING->{stop_time} > $NOW - $MUSIC_RESUME) {
3167 my $pos = $MUSIC_PLAYING->{stop_pos};
3168 $MUSIC_PLAYER->fade_in_pos (0, 1000, $pos);
3169 $MUSIC_START = time - $pos;
3170 } else {
3171 $MUSIC_PLAYER->play (0);
3172 $MUSIC_START = time;
3173 }
3174
3175 delete $MUSIC_PLAYING->{stop_time};
3176 delete $MUSIC_PLAYING->{stop_pos};
3177 }
3178}
3179
3180sub audio_music_changed {
3181 return unless $CFG->{bgm_enable};
3182
3183 # default MUSIC_WANT == MUSIC_DEFAULT
3184 @MUSIC_WANT = { path => CFPlus::find_rcfile "music/$MUSIC_DEFAULT" } unless @MUSIC_WANT;
3185
3186 # if the currently playing song is acceptable, let it continue
3187 return if $MUSIC_PLAYING
3188 && grep $MUSIC_PLAYING->{path} eq $_->{path}, @MUSIC_WANT;
3189
3190 my $NOW = time;
3191
3192 if ($MUSIC_PLAYING) {
3193 $MUSIC_PLAYING->{stop_time} = $NOW;
3194 $MUSIC_PLAYING->{stop_pos} = $NOW - $MUSIC_START;
3195 CFPlus::MixMusic::fade_out 1000;
3196 } else {
3197 # sort by stop time, oldest first
3198 @MUSIC_WANT = sort { $a->{stop_time} <=> $b->{stop_time} } @MUSIC_WANT;
3199
3200 # if the most recently-played piece played very recently,
3201 # resume it, else choose the oldest piece for rotation.
3202 $MUSIC_PLAYING =
3203 $MUSIC_WANT[-1]{stop_time} > $NOW - $MUSIC_RESUME
3204 ? $MUSIC_WANT[-1]
3205 : $MUSIC_WANT[0];
3206
3207 audio_music_start;
3208 }
3209}
3210
3211sub audio_music_finished {
3212 $MUSIC_PLAYING = undef;
3213 undef $MUSIC_PLAYER;
3214
3215 audio_music_changed;
3216}
3217
3218sub audio_init {
3219 if ($CFG->{audio_enable}) {
3220 if (open my $fh, "<", CFPlus::find_rcfile "sounds/config") {
3221 $SDL_MIXER = !CFPlus::Mix_OpenAudio;
3222
3223 unless ($SDL_MIXER) {
3224 status "Unable to open sound device: there will be no sound";
3225 return;
3226 }
3227
3228 CFPlus::Mix_AllocateChannels 8;
3229 CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
3230
3231 audio_music_finished;
3232
3233 local $_;
3234 while (<$fh>) {
3235 next if /^\s*#/;
3236 next if /^\s*$/;
3237
3238 my ($file, $volume, $event) = split /\s+/, $_, 3;
3239
3240 push @SOUNDS, "$volume,$file";
3241
3242 $AUDIO_CHUNKS{"$volume,$file"} ||= do {
3243 my $chunk = new_from_file CFPlus::MixChunk CFPlus::find_rcfile "sounds/$file";
3244 $chunk->volume ($volume * 128 / 100);
3245 $chunk
3246 };
3247 }
3248 } else {
3249 status "unable to open sound config: $!";
3250 }
3251 }
3252}
3253
3254sub audio_shutdown {
3255 CFPlus::Mix_CloseAudio if $SDL_MIXER;
3256 undef $SDL_MIXER;
3257 @SOUNDS = ();
3258 %AUDIO_CHUNKS = ();
3259}
3260
3261my %animate_object;
3262my $animate_timer;
3263
3264my $fps = 9;
3265
3266my %demo;#d#
3267
3268sub force_refresh {
3269 $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
3270 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
3271
3272 $CFPlus::UI::ROOT->draw;
3273
3274 $WANT_REFRESH = 0;
3275 $CAN_REFRESH = 0;
3276 $LAST_REFRESH = $NOW;
3277
3278 CFPlus::SDL_GL_SwapBuffers;
3279}
3280
3281my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
3282 $NOW = time;
3283
3284 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
3285 for CFPlus::poll_events;
3286
3287 if (%animate_object) {
3288 $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
3289 ++$WANT_REFRESH;
3290 }
3291
3292 if ($WANT_REFRESH) {
3293 force_refresh;
3294 } else {
3295 $CAN_REFRESH = 1;
3296 }
3297});
3298
3299sub animation_start {
3300 my ($widget) = @_;
3301 $animate_object{$widget} = $widget;
3302}
3303
3304sub animation_stop {
3305 my ($widget) = @_;
3306 delete $animate_object{$widget};
3307}
3308
3309# check once/second for faces that need to be prefetched
3310# this should, of course, only run on demand, but
3311# SDL forces worse things on us....
3312
3313Event->timer (after => 1, interval => 0.25, cb => sub {
3314 $CONN->face_prefetch
3315 if $CONN;
3316});
3317
3318%SDL_CB = (
3319 CFPlus::SDL_QUIT => sub {
3320 exit;
3321 },
3322 CFPlus::SDL_VIDEORESIZE => sub {
3323 },
3324 CFPlus::SDL_VIDEOEXPOSE => sub {
3325 CFPlus::UI::full_refresh;
3326 },
3327 CFPlus::SDL_ACTIVEEVENT => sub {
3328# not useful, as APPACTIVE include sonly iconified state, not unmapped
3329# printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, CFPlus::SDL_GetAppState;#d#
3330# printf "a %x\n", CFPlus::SDL_GetAppState & CFPlus::SDL_APPACTIVE;#d#
3331# printf "A\n" if $_[0]{state} & CFPlus::SDL_APPACTIVE;
3332# printf "K\n" if $_[0]{state} & CFPlus::SDL_APPINPUTFOCUS;
3333# printf "M\n" if $_[0]{state} & CFPlus::SDL_APPMOUSEFOCUS;
3334 },
3335 CFPlus::SDL_KEYDOWN => sub {
3336 if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
3337 # alt-enter
3338 $FULLSCREEN_ENABLE->toggle;
3339 video_shutdown;
3340 video_init;
3341 } else {
3342 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
3343 }
3344 },
3345 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
3346 CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
3347 CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
3348 CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
3349 CFPlus::SDL_USEREVENT => sub {
3350 if ($_[0]{code} == 1) {
3351 audio_channel_finished $_[0]{data1};
3352 } elsif ($_[0]{code} == 0) {
3353 audio_music_finished;
3354 }
3355 },
3356);
3357
3358#############################################################################
3359
3360$SIG{INT} = $SIG{TERM} = sub { exit };
3361
3362{
3363 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
3364 CFPlus::DB::Server::run;
3365
3366 CFPlus::UI::set_layout ($::CFG->{layout});
3367
3368 my %DEF_CFG = (
3369 sdl_mode => 0,
3370 width => 640,
3371 height => 480,
3372 fullscreen => 0,
3373 fast => 0,
3374 map_scale => 1,
3375 fow_enable => 1,
3376 fow_intensity => 0,
3377 map_smoothing => 1,
3378 gui_fontsize => 1,
3379 log_fontsize => 0.7,
3380 gauge_fontsize => 1,
3381 gauge_size => 0.35,
3382 stat_fontsize => 0.7,
3383 mapsize => 100,
3384 say_command => 'chat',
3385 audio_enable => 1,
3386 bgm_enable => 1,
3387 bgm_volume => 0.25,
3388 face_prefetch => 0,
3389 output_sync => 1,
3390 output_count => 1,
3391 output_rate => "",
3392 pickup => 0,
3393 inv_sort => "mtime",
3394 default => "profile", # default profile
3395 show_tips => 1,
3396 logview_max_par => 1000,
3397 );
3398
3399 while (my ($k, $v) = each %DEF_CFG) {
3400 $CFG->{$k} = $v unless exists $CFG->{$k};
3401 }
3402
3403 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
3404 $PROFILE = $CFG->{profile}{default};
3405
3406 # convert old bindings (only default profile matters)
3407 if (my $bindings = delete $PROFILE->{bindings}) {
3408 while (my ($mod, $syms) = each %$bindings) {
3409 while (my ($sym, $cmds) = each %$syms) {
3410 push @{ $PROFILE->{macro} }, {
3411 accelkey => [$mod*1, $sym*1],
3412 action => $cmds,
3413 };
3414 }
3415 }
3416 }
3417
3418 sdl_init;
3419
3420 @SDL_MODES = CFPlus::SDL_ListModes 8, 8;
3421 @SDL_MODES = CFPlus::SDL_ListModes 5, 0 unless @SDL_MODES;
3422 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
3423
3424 @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
3425
3426 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
3427
3428 {
3429 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
3430 DejaVuSans.ttf
3431 DejaVuSansMono.ttf
3432 DejaVuSans-Bold.ttf
3433 DejaVuSansMono-Bold.ttf
3434 DejaVuSans-Oblique.ttf
3435 DejaVuSansMono-Oblique.ttf
3436 DejaVuSans-BoldOblique.ttf
3437 DejaVuSansMono-BoldOblique.ttf
3438 );
3439
3440 CFPlus::add_font $_ for @fonts;
3441
3442 CFPlus::pango_init;
3443
3444 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
3445 $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
3446
3447 $FONT_PROP->make_default;
3448 }
3449
3450# compare mono (ft) vs. rgba (cairo)
3451# ft - 1.8s, cairo 3s, even in alpha-only mode
3452# for my $rgba (0..1) {
3453# my $t1 = Time::HiRes::time;
3454# for (1..1000) {
3455# my $layout = CFPlus::Layout->new ($rgba);
3456# $layout->set_text ("hallo" x 100);
3457# $layout->render;
3458# }
3459# my $t2 = Time::HiRes::time;
3460# warn $t2-$t1;
3461# }
3462
3463 $startup_done->();
3464
3465 video_init;
3466 audio_init;
3467}
3468
3469show_tip_of_the_day if $CFG->{show_tips};
3470
3471Event::loop;
3472#CFPlus::SDL_Quit;
3473#CFPlus::_exit 0;
3474
3475END {
3476 CFPlus::SDL_Quit;
3477 CFPlus::DB::Server::stop;
3478}
3479
3480=head1 NAME
3481
3482cfplus - A Crossfire+ and Crossfire game client
3483
3484=head1 SYNOPSIS
3485
3486Just run it - no commandline arguments are supported.
3487
3488=head1 USAGE
3489
3490cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
3491fullscreen and interactively.
3492
3493=head1 DEBUGGING
3494
3495
3496CFPLUS_DEBUG - environment variable
3497
3498 1 draw borders around widgets
3499 2 add low-level widget info to tooltips
3500 4 show fps
3501 8 suppress tooltips
3502
3503=head1 AUTHOR
3504
3505Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
3506
3507
3508
3509 : $self->{vslider}->hide;
3510 }
3511}
3512
3513sub update {
3514 my ($self) = @_;
3515
3516 $self->SUPER::update;
3517 $self->update_slider;
3518}
3519
1058sub invoke_mouse_wheel { 3520sub invoke_mouse_wheel {
1059 my ($self, $ev) = @_; 3521 my ($self, $ev) = @_;
1060 3522
1061 return 0 unless $ev->{dy}; # only vertical movements 3523 return 0 unless $ev->{dy}; # only vertical movements for now
1062 3524
1063 $self->{slider}->emit (mouse_wheel => $ev); 3525 $self->{vslider}->emit (mouse_wheel => $ev);
1064 3526
1065 1 3527 1
1066} 3528}
1067 3529
1068sub update_slider { 3530sub invoke_button_down {
1069 my ($self) = @_; 3531 my ($self, $ev, $x, $y) = @_;
1070 3532
1071 $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $self->{vp}->child->{h}, $self->{vp}{h}, 1]); 3533 if ($ev->{button} == 2) {
1072} 3534 $self->grab_focus;
1073 3535
1074sub update { 3536 my $ox = $self->{vp}{view_x} + $ev->{x};
1075 my ($self) = @_; 3537 my $oy = $self->{vp}{view_y} + $ev->{y};
3538
3539 $self->{motion} = sub {
3540 my ($ev, $x, $y) = @_;
1076 3541
1077 $self->SUPER::update; 3542 $self->{vp}->set_offset ($ox - $ev->{x}, $oy - $ev->{y});
3543 $self->update;
3544 };
1078 3545
1079 $self->update_slider; 3546 return 1;
3547 }
3548
3549 0
3550}
3551
3552sub invoke_button_up {
3553 my ($self, $ev, $x, $y) = @_;
3554
3555 if (delete $self->{motion}) {
3556 return 1;
3557 }
3558
3559 0
3560}
3561
3562sub invoke_mouse_motion {
3563 my ($self, $ev, $x, $y) = @_;
3564
3565 if ($self->{motion}) {
3566 $self->{motion}->($ev, $x, $y);
3567 return 1;
3568 }
3569
3570 0
1080} 3571}
1081 3572
1082sub invoke_size_allocate { 3573sub invoke_size_allocate {
1083 my ($self, $w, $h) = @_; 3574 my ($self, $w, $h) = @_;
1084 3575
1085 $self->update_slider; 3576 $self->update_slider;
1086
1087 $self->SUPER::invoke_size_allocate ($w, $h) 3577 $self->SUPER::invoke_size_allocate ($w, $h)
1088} 3578}
1089 3579
1090############################################################################# 3580#############################################################################
1091 3581
1092package CFClient::UI::Frame; 3582package CFPlus::UI::Frame;
1093 3583
1094our @ISA = CFClient::UI::Bin::; 3584our @ISA = CFPlus::UI::Bin::;
1095 3585
1096use CFClient::OpenGL; 3586use CFPlus::OpenGL;
1097 3587
1098sub new { 3588sub new {
1099 my $class = shift; 3589 my $class = shift;
1100 3590
1101 $class->SUPER::new ( 3591 $class->SUPER::new (
1127 $self->SUPER::_draw; 3617 $self->SUPER::_draw;
1128} 3618}
1129 3619
1130############################################################################# 3620#############################################################################
1131 3621
1132package CFClient::UI::FancyFrame; 3622package CFPlus::UI::FancyFrame;
1133 3623
1134our @ISA = CFClient::UI::Bin::; 3624our @ISA = CFPlus::UI::Bin::;
1135 3625
1136use CFClient::OpenGL; 3626use CFPlus::OpenGL;
3627
3628sub new {
3629 my ($class, %arg) = @_;
3630
3631 if ((exists $arg{label}) && !ref $arg{label}) {
3632 $arg{label} = new CFPlus::UI::Label
3633 align => 1,
3634 valign => 0,
3635 text => $arg{label},
3636 fontsize => ($arg{border} || 0.8) * 0.75;
3637 }
3638
3639 my $self = $class->SUPER::new (
3640 # label => "",
3641 fg => [0.6, 0.3, 0.1],
3642 border => 0.8,
3643 style => 'single',
3644 %arg,
3645 );
3646
3647 $self
3648}
3649
3650sub add {
3651 my ($self, @widgets) = @_;
3652
3653 $self->SUPER::add (@widgets);
3654 $self->CFPlus::UI::Container::add ($self->{label}) if $self->{label};
3655}
3656
3657sub border {
3658 int $_[0]{border} * $::FONTSIZE
3659}
3660
3661sub size_request {
3662 my ($self) = @_;
3663
3664 ($self->{label_w}, undef) = $self->{label}->size_request
3665 if $self->{label};
3666
3667 my ($w, $h) = $self->SUPER::size_request;
3668
3669 (
3670 $w + $self->border * 2,
3671 $h + $self->border * 2,
3672 )
3673}
3674
3675sub invoke_size_allocate {
3676 my ($self, $w, $h) = @_;
3677
3678 my $border = $self->border;
3679
3680 $w -= List::Util::max 0, $border * 2;
3681 $h -= List::Util::max 0, $border * 2;
3682
3683 if (my $label = $self->{label}) {
3684 $label->{w} = List::Util::max 0, List::Util::min $self->{label_w}, $w - $border * 2;
3685 $label->{h} = List::Util::min $h, $border;
3686 $label->invoke_size_allocate ($label->{w}, $label->{h});
3687 }
3688
3689 $self->child->configure ($border, $border, $w, $h);
3690
3691 1
3692}
3693
3694sub _draw {
3695 my ($self) = @_;
3696
3697 my $child = $self->{children}[0];
3698
3699 my $border = $self->border;
3700 my ($w, $h) = ($self->{w}, $self->{h});
3701
3702 $child->draw;
3703
3704 glColor @{$self->{fg}};
3705 glBegin GL_LINE_STRIP;
3706 glVertex $border * 1.5 , $border * 0.5 + 0.5;
3707 glVertex $border * 0.5 + 0.5, $border * 0.5 + 0.5;
3708 glVertex $border * 0.5 + 0.5, $h - $border * 0.5 + 0.5;
3709 glVertex $w - $border * 0.5 + 0.5, $h - $border * 0.5 + 0.5;
3710 glVertex $w - $border * 0.5 + 0.5, $border * 0.5 + 0.5;
3711 glVertex $self->{label} ? $border * 2 + $self->{label}{w} : $border * 1.5, $border * 0.5 + 0.5;
3712 glEnd;
3713
3714 if ($self->{label}) {
3715 glTranslate $border * 2, 0;
3716 $self->{label}->_draw;
3717 }
3718}
3719
3720#############################################################################
3721
3722package CFPlus::UI::Toplevel;
3723
3724our @ISA = CFPlus::UI::Bin::;
3725
3726use CFPlus::OpenGL;
1137 3727
1138my $bg = 3728my $bg =
1139 new_from_file CFClient::Texture CFClient::find_rcfile "d1_bg.png", 3729 new_from_file CFPlus::Texture CFPlus::find_rcfile "d1_bg.png",
1140 mipmap => 1, wrap => 1; 3730 mipmap => 1, wrap => 1;
1141 3731
1142my @border = 3732my @border =
1143 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 3733 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1144 qw(d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png); 3734 qw(d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png);
3735
3736my @icon =
3737 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
3738 qw(x1_move.png x1_resize.png);
1145 3739
1146sub new { 3740sub new {
1147 my ($class, %arg) = @_; 3741 my ($class, %arg) = @_;
1148 3742
1149 my $self = $class->SUPER::new ( 3743 my $self = $class->SUPER::new (
1154 min_w => 64, 3748 min_w => 64,
1155 min_h => 32, 3749 min_h => 32,
1156 %arg, 3750 %arg,
1157 ); 3751 );
1158 3752
1159 $self->{title_widget} = new CFClient::UI::Label 3753 $self->{title_widget} = new CFPlus::UI::Label
1160 align => 0, 3754 align => 0,
1161 valign => 1, 3755 valign => 1,
1162 text => $self->{title}, 3756 text => $self->{title},
1163 fontsize => $self->{border}, 3757 fontsize => $self->{border},
1164 if exists $self->{title}; 3758 if exists $self->{title};
1165 3759
1166 if ($self->{has_close_button}) { 3760 if ($self->{has_close_button}) {
1167 $self->{close_button} = 3761 $self->{close_button} =
1168 new CFClient::UI::ImageButton 3762 new CFPlus::UI::ImageButton
1169 path => 'x1_close.png', 3763 path => 'x1_close.png',
1170 on_activate => sub { $self->emit ("delete") }; 3764 on_activate => sub { $self->emit ("delete") };
1171 3765
1172 $self->CFClient::UI::Container::add ($self->{close_button}); 3766 $self->CFPlus::UI::Container::add ($self->{close_button});
1173 } 3767 }
1174 3768
1175 $self 3769 $self
1176} 3770}
1177 3771
1178sub add { 3772sub add {
1179 my ($self, @widgets) = @_; 3773 my ($self, @widgets) = @_;
1180 3774
1181 $self->SUPER::add (@widgets); 3775 $self->SUPER::add (@widgets);
1182 $self->CFClient::UI::Container::add ($self->{close_button}) if $self->{close_button}; 3776 $self->CFPlus::UI::Container::add ($self->{close_button}) if $self->{close_button};
1183 $self->CFClient::UI::Container::add ($self->{title_widget}) if $self->{title_widget}; 3777 $self->CFPlus::UI::Container::add ($self->{title_widget}) if $self->{title_widget};
1184} 3778}
1185 3779
1186sub border { 3780sub border {
1187 int $_[0]{border} * $::FONTSIZE 3781 int $_[0]{border} * $::FONTSIZE
1188} 3782}
1296 $self->{motion}->($ev, $x, $y) if $self->{motion}; 3890 $self->{motion}->($ev, $x, $y) if $self->{motion};
1297 3891
1298 ! ! $self->{motion} 3892 ! ! $self->{motion}
1299} 3893}
1300 3894
3895sub invoke_visibility_change {
3896 my ($self, $visible) = @_;
3897
3898 delete $self->{motion} unless $visible;
3899
3900 0
3901}
3902
1301sub _draw { 3903sub _draw {
1302 my ($self) = @_; 3904 my ($self) = @_;
1303 3905
1304 my $child = $self->{children}[0]; 3906 my $child = $self->{children}[0];
1305 3907
1310 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE; 3912 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
1311 3913
1312 my $border = $self->border; 3914 my $border = $self->border;
1313 3915
1314 glColor @{ $self->{border_bg} }; 3916 glColor @{ $self->{border_bg} };
1315 $border[0]->draw_quad_alpha (0, 0, $w, $border); 3917 $border[0]->draw_quad_alpha ( 0, 0, $w, $border);
1316 $border[1]->draw_quad_alpha (0, $border, $border, $ch); 3918 $border[1]->draw_quad_alpha ( 0, $border, $border, $ch);
1317 $border[2]->draw_quad_alpha ($w - $border, $border, $border, $ch); 3919 $border[2]->draw_quad_alpha ($w - $border, $border, $border, $ch);
1318 $border[3]->draw_quad_alpha (0, $h - $border, $w, $border); 3920 $border[3]->draw_quad_alpha ( 0, $h - $border, $w, $border);
3921
3922 # move
3923 my $w2 = ($w - $border) * .5;
3924 my $h2 = ($h - $border) * .5;
3925 $icon[0]->draw_quad_alpha ( 0, $h2, $border, $border);
3926 $icon[0]->draw_quad_alpha ($w - $border, $h2, $border, $border);
3927 $icon[0]->draw_quad_alpha ($w2 , $h - $border, $border, $border);
3928
3929 # resize
3930 $icon[1]->draw_quad_alpha ( 0, 0, $border, $border);
3931 $icon[1]->draw_quad_alpha ($w - $border, 0, $border, $border)
3932 unless $self->{has_close_button};
3933 $icon[1]->draw_quad_alpha ( 0, $h - $border, $border, $border);
3934 $icon[1]->draw_quad_alpha ($w - $border, $h - $border, $border, $border);
1319 3935
1320 if (@{$self->{bg}} < 4 || $self->{bg}[3]) { 3936 if (@{$self->{bg}} < 4 || $self->{bg}[3]) {
1321 glColor @{ $self->{bg} }; 3937 glColor @{ $self->{bg} };
1322 3938
1323 # TODO: repeat texture not scale 3939 # TODO: repeat texture not scale
1342 if $self->{close_button}; 3958 if $self->{close_button};
1343} 3959}
1344 3960
1345############################################################################# 3961#############################################################################
1346 3962
1347package CFClient::UI::Table; 3963package CFPlus::UI::Table;
1348 3964
1349our @ISA = CFClient::UI::Base::; 3965our @ISA = CFPlus::UI::Base::;
1350 3966
1351use List::Util qw(max sum); 3967use List::Util qw(max sum);
1352 3968
1353use CFClient::OpenGL; 3969use CFPlus::OpenGL;
1354 3970
1355sub new { 3971sub new {
1356 my $class = shift; 3972 my $class = shift;
1357 3973
1358 $class->SUPER::new ( 3974 $class->SUPER::new (
3975 children => [],
1359 col_expand => [], 3976 col_expand => [],
3977 row_expand => [],
1360 @_, 3978 @_,
1361 ) 3979 )
1362} 3980}
1363 3981
1364sub children { 3982sub children {
1365 grep $_, map @$_, grep $_, @{ $_[0]{children} } 3983 grep $_, map @$_, grep $_, @{ $_[0]{children} }
1366} 3984}
1367 3985
3986# TODO: store row/col info in child widget and use standard add/del
1368sub add { 3987sub add {
1369 my ($self) = shift; 3988 my $self = shift;
3989
3990 Carp::cluck "please use the add_at method instead of calling add, thank you.\n";#d#
3991 $self->add_at (@_);
3992}
3993
3994sub add_at {
3995 my $self = shift;
1370 3996
1371 while (@_) { 3997 while (@_) {
1372 my ($x, $y, $child) = splice @_, 0, 3, (); 3998 my ($col, $row, $child) = splice @_, 0, 3, ();
3999
1373 $child->set_parent ($self); 4000 $child->set_parent ($self);
1374 $self->{children}[$y][$x] = $child; 4001 $self->{children}[$row][$col] = $child;
1375 } 4002 }
1376 4003
1377 $self->{force_realloc} = 1; 4004 $self->{force_realloc} = 1;
1378 $self->{force_size_alloc} = 1; 4005 $self->{force_size_alloc} = 1;
1379 $self->realloc; 4006 $self->realloc;
1380} 4007}
1381 4008
1382sub remove { 4009sub remove {
1383 my ($self, $child) = @_; 4010 my ($self, $child) = @_;
1384 4011
1385 # TODO: not yet implemented 4012 for (@{ $self->{children} }) {
4013 for (@{ $_ || [] }) {
4014 $_ = undef if $_ == $child;
4015 }
4016 }
1386} 4017}
1387 4018
1388# TODO: move to container class maybe? send children a signal on removal? 4019# TODO: move to container class maybe? send children a signal on removal?
1389sub clear { 4020sub clear {
1390 my ($self) = @_; 4021 my ($self) = @_;
1439 my ($ws, $hs) = $self->get_wh; 4070 my ($ws, $hs) = $self->get_wh;
1440 4071
1441 my $req_w = (sum @$ws) || 1; 4072 my $req_w = (sum @$ws) || 1;
1442 my $req_h = (sum @$hs) || 1; 4073 my $req_h = (sum @$hs) || 1;
1443 4074
1444 # TODO: nicer code && do row_expand 4075 # TODO: nicer code
1445 my @col_expand = @{$self->{col_expand}}; 4076 my @col_expand = @{$self->{col_expand}};
1446 @col_expand = (1) x @$ws unless @col_expand; 4077 @col_expand = (1) x @$ws unless @col_expand;
1447 my $col_expand = (sum @col_expand) || 1; 4078 my $col_expand = (sum @col_expand) || 1;
1448 4079
1449 # linearly scale sizes
1450 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws; 4080 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws;
1451 $hs->[$_] *= 1 * $h / $req_h for 0 .. $#$hs;
1452 4081
1453 CFClient::UI::harmonize $ws; 4082 CFPlus::UI::harmonize $ws;
4083
4084 my @row_expand = @{$self->{row_expand}};
4085 @row_expand = (1) x @$ws unless @row_expand;
4086 my $row_expand = (sum @row_expand) || 1;
4087
4088 $hs->[$_] += $row_expand[$_] / $row_expand * ($h - $req_h) for 0 .. $#$hs;
4089
1454 CFClient::UI::harmonize $hs; 4090 CFPlus::UI::harmonize $hs;
1455 4091
1456 my $y; 4092 my $y;
1457 4093
1458 for my $r (0 .. $#{$self->{children}}) { 4094 for my $r (0 .. $#{$self->{children}}) {
1459 my $row = $self->{children}[$r] 4095 my $row = $self->{children}[$r]
1502 } 4138 }
1503} 4139}
1504 4140
1505############################################################################# 4141#############################################################################
1506 4142
1507package CFClient::UI::Box; 4143package CFPlus::UI::Fixed;
1508 4144
4145use List::Util qw(min max);
4146
1509our @ISA = CFClient::UI::Container::; 4147our @ISA = CFPlus::UI::Container::;
4148
4149sub add {
4150 my ($self, $child, $posmode, $x, $y, $sizemode, $w, $h) = @_;
4151
4152 $child->{_fixed} = [$posmode, $x, $y, $sizemode, $w, $h];
4153 $self->SUPER::add ($child);
4154}
4155
4156sub _scale($$$) {
4157 my ($mode, $val, $max) = @_;
4158
4159 $mode eq "abs" ? $val
4160 : $mode eq "rel" ? $val * $max
4161 : 0
4162}
4163
4164sub size_request {
4165 my ($self) = @_;
4166
4167 my ($x1, $y1, $x2, $y2) = (0, 0, 0, 0);
4168
4169 # determine overall size by querying abs widgets
4170 for my $child ($self->visible_children) {
4171 my ($pos, $x, $y, $size, $w, $h) = @{ $child->{_fixed} };
4172
4173 if ($pos eq "abs") {
4174 $w = _scale $size, $w, $child->{req_w};
4175 $h = _scale $size, $h, $child->{req_h};
4176
4177 $x1 = min $x1, $x; $x2 = max $x2, $x + $w;
4178 $y1 = min $y1, $y; $y2 = max $y2, $y + $h;
4179 }
4180 }
4181
4182 my $W = $x2 - $x1;
4183 my $H = $y2 - $y1;
4184
4185 # now layout remaining widgets
4186 for my $child ($self->visible_children) {
4187 my ($pos, $x, $y, $size, $w, $h) = @{ $child->{_fixed} };
4188
4189 if ($pos ne "abs") {
4190 $x = _scale $pos, $x, $W;
4191 $y = _scale $pos, $x, $H;
4192 $w = _scale $size, $w, $child->{req_w};
4193 $h = _scale $size, $h, $child->{req_h};
4194
4195 $x1 = min $x1, $x; $x2 = max $x2, $x + $w;
4196 $y1 = min $y1, $y; $y2 = max $y2, $y + $h;
4197 }
4198 }
4199
4200 my $W = $x2 - $x1;
4201 my $H = $y2 - $y1;
4202
4203 ($W, $H)
4204}
4205
4206sub invoke_size_allocate {
4207 my ($self, $W, $H) = @_;
4208
4209 for my $child ($self->visible_children) {
4210 my ($pos, $x, $y, $size, $w, $h) = @{ $child->{_fixed} };
4211
4212 $x = _scale $pos, $x, $W;
4213 $y = _scale $pos, $x, $H;
4214 $w = _scale $size, $w, $child->{req_w};
4215 $h = _scale $size, $h, $child->{req_h};
4216
4217 $child->configure ($x, $y, $w, $h);
4218 }
4219
4220 1
4221}
4222
4223#############################################################################
4224
4225package CFPlus::UI::Box;
4226
4227our @ISA = CFPlus::UI::Container::;
1510 4228
1511sub size_request { 4229sub size_request {
1512 my ($self) = @_; 4230 my ($self) = @_;
1513 4231
1514 $self->{vertical} 4232 $self->{vertical}
1547 $req[$_] += $space * $children[$_]{expand} 4265 $req[$_] += $space * $children[$_]{expand}
1548 for 0 .. $#children; 4266 for 0 .. $#children;
1549 } 4267 }
1550 } 4268 }
1551 4269
1552 CFClient::UI::harmonize \@req; 4270 CFPlus::UI::harmonize \@req;
1553 4271
1554 my $pos = 0; 4272 my $pos = 0;
1555 for (0 .. $#children) { 4273 for (0 .. $#children) {
1556 my $alloc = $req[$_]; 4274 my $alloc = $req[$_];
1557 $children[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h)); 4275 $children[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h));
1562 1 4280 1
1563} 4281}
1564 4282
1565############################################################################# 4283#############################################################################
1566 4284
1567package CFClient::UI::HBox; 4285package CFPlus::UI::HBox;
1568 4286
1569our @ISA = CFClient::UI::Box::; 4287our @ISA = CFPlus::UI::Box::;
1570 4288
1571sub new { 4289sub new {
1572 my $class = shift; 4290 my $class = shift;
1573 4291
1574 $class->SUPER::new ( 4292 $class->SUPER::new (
1577 ) 4295 )
1578} 4296}
1579 4297
1580############################################################################# 4298#############################################################################
1581 4299
1582package CFClient::UI::VBox; 4300package CFPlus::UI::VBox;
1583 4301
1584our @ISA = CFClient::UI::Box::; 4302our @ISA = CFPlus::UI::Box::;
1585 4303
1586sub new { 4304sub new {
1587 my $class = shift; 4305 my $class = shift;
1588 4306
1589 $class->SUPER::new ( 4307 $class->SUPER::new (
1592 ) 4310 )
1593} 4311}
1594 4312
1595############################################################################# 4313#############################################################################
1596 4314
1597package CFClient::UI::Label; 4315package CFPlus::UI::Label;
1598 4316
1599our @ISA = CFClient::UI::DrawBG::; 4317our @ISA = CFPlus::UI::DrawBG::;
1600 4318
1601use CFClient::OpenGL; 4319use CFPlus::OpenGL;
1602 4320
1603sub new { 4321sub new {
1604 my ($class, %arg) = @_; 4322 my ($class, %arg) = @_;
1605 4323
1606 my $self = $class->SUPER::new ( 4324 my $self = $class->SUPER::new (
1609 #active_bg => none 4327 #active_bg => none
1610 #font => default_font 4328 #font => default_font
1611 #text => initial text 4329 #text => initial text
1612 #markup => initial narkup 4330 #markup => initial narkup
1613 #max_w => maximum pixel width 4331 #max_w => maximum pixel width
4332 #style => 0, # render flags
1614 ellipsise => 3, # end 4333 ellipsise => 3, # end
1615 layout => (new CFClient::Layout), 4334 layout => (new CFPlus::Layout),
1616 fontsize => 1, 4335 fontsize => 1,
1617 align => -1, 4336 align => -1,
1618 valign => -1, 4337 valign => -1,
1619 padding_x => 2, 4338 padding_x => 2,
1620 padding_y => 2, 4339 padding_y => 2,
1621 can_events => 0, 4340 can_events => 0,
1622 %arg 4341 %arg
1623 ); 4342 );
1624 4343
1625 if (exists $self->{template}) { 4344 if (exists $self->{template}) {
1626 my $layout = new CFClient::Layout; 4345 my $layout = new CFPlus::Layout;
1627 $layout->set_text (delete $self->{template}); 4346 $layout->set_text (delete $self->{template});
1628 $self->{template} = $layout; 4347 $self->{template} = $layout;
1629 } 4348 }
1630 4349
1631 if (exists $self->{markup}) { 4350 if (exists $self->{markup}) {
1769 }; 4488 };
1770 4489
1771 my $w = List::Util::min $self->{w} + 4, $size->[0]; 4490 my $w = List::Util::min $self->{w} + 4, $size->[0];
1772 my $h = List::Util::min $self->{h} + 2, $size->[1]; 4491 my $h = List::Util::min $self->{h} + 2, $size->[1];
1773 4492
1774 $self->{layout}->render ($self->{ox}, $self->{oy}); 4493 $self->{layout}->render ($self->{ox}, $self->{oy}, $self->{style});
1775} 4494}
1776 4495
1777############################################################################# 4496#############################################################################
1778 4497
1779package CFClient::UI::EntryBase; 4498package CFPlus::UI::EntryBase;
1780 4499
1781our @ISA = CFClient::UI::Label::; 4500our @ISA = CFPlus::UI::Label::;
1782 4501
1783use CFClient::OpenGL; 4502use CFPlus::OpenGL;
1784 4503
1785sub new { 4504sub new {
1786 my $class = shift; 4505 my $class = shift;
1787 4506
1788 $class->SUPER::new ( 4507 $class->SUPER::new (
1792 active_fg => [0, 0, 0], 4511 active_fg => [0, 0, 0],
1793 can_hover => 1, 4512 can_hover => 1,
1794 can_focus => 1, 4513 can_focus => 1,
1795 valign => 0, 4514 valign => 0,
1796 can_events => 1, 4515 can_events => 1,
4516 ellipsise => 0,
1797 #text => ... 4517 #text => ...
1798 #hidden => "*", 4518 #hidden => "*",
1799 @_ 4519 @_
1800 ) 4520 )
1801} 4521}
1846 my $sym = $ev->{sym}; 4566 my $sym = $ev->{sym};
1847 my $uni = $ev->{unicode}; 4567 my $uni = $ev->{unicode};
1848 4568
1849 my $text = $self->get_text; 4569 my $text = $self->get_text;
1850 4570
4571 $self->{cursor} = List::Util::max 0, List::Util::min $self->{cursor}, length $text;
4572
1851 if ($uni == 8) { 4573 if ($uni == 8) {
1852 substr $text, --$self->{cursor}, 1, "" if $self->{cursor}; 4574 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1853 } elsif ($uni == 127) { 4575 } elsif ($uni == 127) {
1854 substr $text, $self->{cursor}, 1, ""; 4576 substr $text, $self->{cursor}, 1, "";
1855 } elsif ($sym == CFClient::SDLK_LEFT) { 4577 } elsif ($sym == CFPlus::SDLK_LEFT) {
1856 --$self->{cursor} if $self->{cursor}; 4578 --$self->{cursor} if $self->{cursor};
1857 } elsif ($sym == CFClient::SDLK_RIGHT) { 4579 } elsif ($sym == CFPlus::SDLK_RIGHT) {
1858 ++$self->{cursor} if $self->{cursor} < length $self->{text}; 4580 ++$self->{cursor} if $self->{cursor} < length $self->{text};
1859 } elsif ($sym == CFClient::SDLK_HOME) { 4581 } elsif ($sym == CFPlus::SDLK_HOME) {
4582 # what a hack
4583 $self->{cursor} =
4584 (substr $self->{text}, 0, $self->{cursor}) =~ /^(.*\012)/
4585 ? length $1
4586 : 0;
4587 } elsif ($sym == CFPlus::SDLK_END) {
4588 # uh, again
4589 $self->{cursor} =
4590 (substr $self->{text}, $self->{cursor}) =~ /^([^\012]*)\012/
4591 ? $self->{cursor} + length $1
4592 : length $self->{text};
4593 } elsif ($uni == 21) { # ctrl-u
4594 $text = "";
1860 $self->{cursor} = 0; 4595 $self->{cursor} = 0;
1861 } elsif ($sym == CFClient::SDLK_END) {
1862 $self->{cursor} = length $text;
1863 } elsif ($uni == 27) { 4596 } elsif ($uni == 27) {
1864 $self->emit ('escape'); 4597 $self->emit ('escape');
1865 } elsif ($uni) { 4598 } elsif ($uni == 0x0d) {
4599 substr $text, $self->{cursor}++, 0, "\012";
4600 } elsif ($uni >= 0x20) {
1866 substr $text, $self->{cursor}++, 0, chr $uni; 4601 substr $text, $self->{cursor}++, 0, chr $uni;
1867 } else { 4602 } else {
1868 return 0; 4603 return 0;
1869 } 4604 }
1870 4605
1871 $self->_set_text ($text); 4606 $self->_set_text ($text);
1872 4607
1873 $self->realloc; 4608 $self->realloc;
4609 $self->update;
1874 4610
1875 1 4611 1
1876} 4612}
1877 4613
1878sub invoke_focus_in { 4614sub invoke_focus_in {
1940 utf8::encode $text; 4676 utf8::encode $text;
1941 4677
1942 @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text) 4678 @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text)
1943 } 4679 }
1944 4680
1945 glColor @{$self->{fg}};
1946 glBegin GL_LINES; 4681 glBegin GL_LINES;
1947 glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy}; 4682 glVertex 0.5 + $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy};
1948 glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy} + $self->{cur_h}; 4683 glVertex 0.5 + $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy} + $self->{cur_h};
1949 glEnd; 4684 glEnd;
1950 } 4685 }
1951} 4686}
1952 4687
4688#############################################################################
4689
1953package CFClient::UI::Entry; 4690package CFPlus::UI::Entry;
1954 4691
1955our @ISA = CFClient::UI::EntryBase::; 4692our @ISA = CFPlus::UI::EntryBase::;
1956 4693
1957use CFClient::OpenGL; 4694use CFPlus::OpenGL;
1958 4695
1959sub invoke_key_down { 4696sub invoke_key_down {
1960 my ($self, $ev) = @_; 4697 my ($self, $ev) = @_;
1961 4698
1962 my $sym = $ev->{sym}; 4699 my $sym = $ev->{sym};
1963 4700
1964 if ($sym == 13) { 4701 if ($ev->{uni} == 0x0d || $sym == 13) {
1965 unshift @{$self->{history}}, 4702 unshift @{$self->{history}},
1966 my $txt = $self->get_text; 4703 my $txt = $self->get_text;
1967 4704
1968 $self->{history_pointer} = -1; 4705 $self->{history_pointer} = -1;
1969 $self->{history_saveback} = ''; 4706 $self->{history_saveback} = '';
1970 $self->emit (activate => $txt); 4707 $self->emit (activate => $txt);
1971 $self->update; 4708 $self->update;
1972 4709
1973 } elsif ($sym == CFClient::SDLK_UP) { 4710 } elsif ($sym == CFPlus::SDLK_UP) {
1974 if ($self->{history_pointer} < 0) { 4711 if ($self->{history_pointer} < 0) {
1975 $self->{history_saveback} = $self->get_text; 4712 $self->{history_saveback} = $self->get_text;
1976 } 4713 }
1977 if (@{$self->{history} || []} > 0) { 4714 if (@{$self->{history} || []} > 0) {
1978 $self->{history_pointer}++; 4715 $self->{history_pointer}++;
1980 $self->{history_pointer} = @{$self->{history} || []} - 1; 4717 $self->{history_pointer} = @{$self->{history} || []} - 1;
1981 } 4718 }
1982 $self->set_text ($self->{history}->[$self->{history_pointer}]); 4719 $self->set_text ($self->{history}->[$self->{history_pointer}]);
1983 } 4720 }
1984 4721
1985 } elsif ($sym == CFClient::SDLK_DOWN) { 4722 } elsif ($sym == CFPlus::SDLK_DOWN) {
1986 $self->{history_pointer}--; 4723 $self->{history_pointer}--;
1987 $self->{history_pointer} = -1 if $self->{history_pointer} < 0; 4724 $self->{history_pointer} = -1 if $self->{history_pointer} < 0;
1988 4725
1989 if ($self->{history_pointer} >= 0) { 4726 if ($self->{history_pointer} >= 0) {
1990 $self->set_text ($self->{history}->[$self->{history_pointer}]); 4727 $self->set_text ($self->{history}->[$self->{history_pointer}]);
1999 1 4736 1
2000} 4737}
2001 4738
2002############################################################################# 4739#############################################################################
2003 4740
4741package CFPlus::UI::TextEdit;
4742
4743our @ISA = CFPlus::UI::EntryBase::;
4744
4745use CFPlus::OpenGL;
4746
4747sub move_cursor_ver {
4748 my ($self, $dy) = @_;
4749
4750 my ($y, $x) = $self->{layout}->index_to_line_x ($self->{cursor});
4751
4752 $y += $dy;
4753
4754 if (defined (my $index = $self->{layout}->line_x_to_index ($y, $x))) {
4755 $self->{cursor} = $index;
4756 delete $self->{cur_h};
4757 $self->update;
4758 return;
4759 }
4760}
4761
4762sub invoke_key_down {
4763 my ($self, $ev) = @_;
4764
4765 my $sym = $ev->{sym};
4766
4767 if ($sym == CFPlus::SDLK_UP) {
4768 $self->move_cursor_ver (-1);
4769 } elsif ($sym == CFPlus::SDLK_DOWN) {
4770 $self->move_cursor_ver (+1);
4771 } else {
4772 return $self->SUPER::invoke_key_down ($ev)
4773 }
4774
4775 1
4776}
4777
4778#############################################################################
4779
2004package CFClient::UI::Button; 4780package CFPlus::UI::Button;
2005 4781
2006our @ISA = CFClient::UI::Label::; 4782our @ISA = CFPlus::UI::Label::;
2007 4783
2008use CFClient::OpenGL; 4784use CFPlus::OpenGL;
2009 4785
2010my @tex = 4786my @tex =
2011 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 4787 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
2012 qw(b1_button_active.png); 4788 qw(b1_button_inactive.png b1_button_active.png);
2013 4789
2014sub new { 4790sub new {
2015 my $class = shift; 4791 my $class = shift;
2016 4792
2017 $class->SUPER::new ( 4793 $class->SUPER::new (
2018 padding_x => 4, 4794 padding_x => 4,
2019 padding_y => 4, 4795 padding_y => 4,
2020 fg => [1, 1, 1], 4796 fg => [1.0, 1.0, 1.0],
2021 active_fg => [0, 0, 1], 4797 active_fg => [0.8, 0.8, 0.8],
2022 can_hover => 1, 4798 can_hover => 1,
2023 align => 0, 4799 align => 0,
2024 valign => 0, 4800 valign => 0,
2025 can_events => 1, 4801 can_events => 1,
2026 @_ 4802 @_
2044 4820
2045 glEnable GL_TEXTURE_2D; 4821 glEnable GL_TEXTURE_2D;
2046 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 4822 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2047 glColor 0, 0, 0, 1; 4823 glColor 0, 0, 0, 1;
2048 4824
4825 my $tex = $tex[$GRAB == $self];
2049 $tex[0]->draw_quad_alpha (0, 0, $self->{w}, $self->{h}); 4826 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2050 4827
2051 glDisable GL_TEXTURE_2D; 4828 glDisable GL_TEXTURE_2D;
2052 4829
2053 $self->SUPER::_draw; 4830 $self->SUPER::_draw;
2054} 4831}
2055 4832
2056############################################################################# 4833#############################################################################
2057 4834
2058package CFClient::UI::CheckBox; 4835package CFPlus::UI::CheckBox;
2059 4836
2060our @ISA = CFClient::UI::DrawBG::; 4837our @ISA = CFPlus::UI::DrawBG::;
2061 4838
2062my @tex = 4839my @tex =
2063 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 4840 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
2064 qw(c1_checkbox_bg.png c1_checkbox_active.png); 4841 qw(c1_checkbox_bg.png c1_checkbox_active.png);
2065 4842
2066use CFClient::OpenGL; 4843use CFPlus::OpenGL;
2067 4844
2068sub new { 4845sub new {
2069 my $class = shift; 4846 my $class = shift;
2070 4847
2071 $class->SUPER::new ( 4848 $class->SUPER::new (
2128 glDisable GL_TEXTURE_2D; 4905 glDisable GL_TEXTURE_2D;
2129} 4906}
2130 4907
2131############################################################################# 4908#############################################################################
2132 4909
2133package CFClient::UI::Image; 4910package CFPlus::UI::Image;
2134 4911
2135our @ISA = CFClient::UI::Base::; 4912our @ISA = CFPlus::UI::Base::;
2136 4913
2137use CFClient::OpenGL; 4914use CFPlus::OpenGL;
2138 4915
2139our %texture_cache; 4916our %texture_cache;
2140 4917
2141sub new { 4918sub new {
2142 my $class = shift; 4919 my $class = shift;
2148 4925
2149 $self->{path} || $self->{tex} 4926 $self->{path} || $self->{tex}
2150 or Carp::croak "'path' or 'tex' attributes required"; 4927 or Carp::croak "'path' or 'tex' attributes required";
2151 4928
2152 $self->{tex} ||= $texture_cache{$self->{path}} ||= 4929 $self->{tex} ||= $texture_cache{$self->{path}} ||=
2153 new_from_file CFClient::Texture CFClient::find_rcfile $self->{path}, mipmap => 1; 4930 new_from_file CFPlus::Texture CFPlus::find_rcfile $self->{path}, mipmap => 1;
2154 4931
2155 Scalar::Util::weaken $texture_cache{$self->{path}}; 4932 CFPlus::weaken $texture_cache{$self->{path}};
2156 4933
2157 $self->{aspect} ||= $self->{tex}{w} / $self->{tex}{h}; 4934 $self->{aspect} ||= $self->{tex}{w} / $self->{tex}{h};
2158 4935
2159 $self 4936 $self
2160} 4937}
2161 4938
2162sub STORABLE_freeze { 4939sub STORABLE_freeze {
2163 my ($self, $cloning) = @_; 4940 my ($self, $cloning) = @_;
2164 4941
2165 $self->{path} 4942 $self->{path}
2166 or die "cannot serialise CFClient::UI::Image on non-loadable images\n"; 4943 or die "cannot serialise CFPlus::UI::Image on non-loadable images\n";
2167 4944
2168 $self->{path} 4945 $self->{path}
2169} 4946}
2170 4947
2171sub STORABLE_attach { 4948sub STORABLE_attach {
2202 glDisable GL_TEXTURE_2D; 4979 glDisable GL_TEXTURE_2D;
2203} 4980}
2204 4981
2205############################################################################# 4982#############################################################################
2206 4983
2207package CFClient::UI::ImageButton; 4984package CFPlus::UI::ImageButton;
2208 4985
2209our @ISA = CFClient::UI::Image::; 4986our @ISA = CFPlus::UI::Image::;
2210 4987
2211use CFClient::OpenGL; 4988use CFPlus::OpenGL;
2212 4989
2213my %textures; 4990my %textures;
2214 4991
2215sub new { 4992sub new {
2216 my $class = shift; 4993 my $class = shift;
2238 1 5015 1
2239} 5016}
2240 5017
2241############################################################################# 5018#############################################################################
2242 5019
2243package CFClient::UI::VGauge; 5020package CFPlus::UI::VGauge;
2244 5021
2245our @ISA = CFClient::UI::Base::; 5022our @ISA = CFPlus::UI::Base::;
2246 5023
2247use List::Util qw(min max); 5024use List::Util qw(min max);
2248 5025
2249use CFClient::OpenGL; 5026use CFPlus::OpenGL;
2250 5027
2251my %tex = ( 5028my %tex = (
2252 food => [ 5029 food => [
2253 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 5030 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
2254 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/ 5031 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/
2255 ], 5032 ],
2256 grace => [ 5033 grace => [
2257 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 5034 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
2258 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/ 5035 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/
2259 ], 5036 ],
2260 hp => [ 5037 hp => [
2261 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 5038 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
2262 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/ 5039 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/
2263 ], 5040 ],
2264 mana => [ 5041 mana => [
2265 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 5042 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
2266 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/ 5043 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/
2267 ], 5044 ],
2268); 5045);
2269 5046
2270# eg. VGauge->new (gauge => 'food'), default gauge: food 5047# eg. VGauge->new (gauge => 'food'), default gauge: food
2373 glDisable GL_TEXTURE_2D; 5150 glDisable GL_TEXTURE_2D;
2374} 5151}
2375 5152
2376############################################################################# 5153#############################################################################
2377 5154
2378package CFClient::UI::Gauge; 5155package CFPlus::UI::Gauge;
2379 5156
2380our @ISA = CFClient::UI::VBox::; 5157our @ISA = CFPlus::UI::VBox::;
2381 5158
2382sub new { 5159sub new {
2383 my ($class, %arg) = @_; 5160 my ($class, %arg) = @_;
2384 5161
2385 my $self = $class->SUPER::new ( 5162 my $self = $class->SUPER::new (
2387 can_hover => 1, 5164 can_hover => 1,
2388 can_events => 1, 5165 can_events => 1,
2389 %arg, 5166 %arg,
2390 ); 5167 );
2391 5168
2392 $self->add ($self->{value} = new CFClient::UI::Label valign => +1, align => 0, template => "999"); 5169 $self->add ($self->{value} = new CFPlus::UI::Label valign => +1, align => 0, template => "999");
2393 $self->add ($self->{gauge} = new CFClient::UI::VGauge type => $self->{type}, expand => 1, can_hover => 1); 5170 $self->add ($self->{gauge} = new CFPlus::UI::VGauge type => $self->{type}, expand => 1, can_hover => 1);
2394 $self->add ($self->{max} = new CFClient::UI::Label valign => -1, align => 0, template => "999"); 5171 $self->add ($self->{max} = new CFPlus::UI::Label valign => -1, align => 0, template => "999");
2395 5172
2396 $self 5173 $self
2397} 5174}
2398 5175
2399sub set_fontsize { 5176sub set_fontsize {
2420 $self->{value}->set_text ($val); 5197 $self->{value}->set_text ($val);
2421} 5198}
2422 5199
2423############################################################################# 5200#############################################################################
2424 5201
2425package CFClient::UI::Slider; 5202package CFPlus::UI::Slider;
2426 5203
2427use strict; 5204use strict;
2428 5205
2429use CFClient::OpenGL; 5206use CFPlus::OpenGL;
2430 5207
2431our @ISA = CFClient::UI::DrawBG::; 5208our @ISA = CFPlus::UI::DrawBG::;
2432 5209
2433my @tex = 5210my @tex =
2434 map { new_from_file CFClient::Texture CFClient::find_rcfile $_ } 5211 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_ }
2435 qw(s1_slider.png s1_slider_bg.png); 5212 qw(s1_slider.png s1_slider_bg.png);
2436 5213
2437sub new { 5214sub new {
2438 my $class = shift; 5215 my $class = shift;
2439 5216
2533sub invoke_mouse_wheel { 5310sub invoke_mouse_wheel {
2534 my ($self, $ev) = @_; 5311 my ($self, $ev) = @_;
2535 5312
2536 my $delta = $self->{vertical} ? $ev->{dy} : $ev->{dx}; 5313 my $delta = $self->{vertical} ? $ev->{dy} : $ev->{dx};
2537 5314
5315 my $pagepart = $ev->{mod} & CFPlus::KMOD_SHIFT ? 1 : 0.2;
5316
2538 $self->set_value ($self->{range}[0] + $delta * $self->{range}[3] * 0.2); 5317 $self->set_value ($self->{range}[0] + $delta * $self->{range}[3] * $pagepart);
2539 5318
2540 ! ! $delta 5319 ! ! $delta
2541} 5320}
2542 5321
2543sub update { 5322sub update {
2594 glDisable GL_TEXTURE_2D; 5373 glDisable GL_TEXTURE_2D;
2595} 5374}
2596 5375
2597############################################################################# 5376#############################################################################
2598 5377
2599package CFClient::UI::ValSlider; 5378package CFPlus::UI::ValSlider;
2600 5379
2601our @ISA = CFClient::UI::HBox::; 5380our @ISA = CFPlus::UI::HBox::;
2602 5381
2603sub new { 5382sub new {
2604 my ($class, %arg) = @_; 5383 my ($class, %arg) = @_;
2605 5384
2606 my $range = delete $arg{range}; 5385 my $range = delete $arg{range};
2607 5386
2608 my $self = $class->SUPER::new ( 5387 my $self = $class->SUPER::new (
2609 slider => (new CFClient::UI::Slider expand => 1, range => $range), 5388 slider => (new CFPlus::UI::Slider expand => 1, range => $range),
2610 entry => (new CFClient::UI::Label text => "", template => delete $arg{template}), 5389 entry => (new CFPlus::UI::Label text => "", template => delete $arg{template}),
2611 to_value => sub { shift }, 5390 to_value => sub { shift },
2612 from_value => sub { shift }, 5391 from_value => sub { shift },
2613 %arg, 5392 %arg,
2614 ); 5393 );
2615 5394
2635sub set_range { shift->{slider}->set_range (@_) } 5414sub set_range { shift->{slider}->set_range (@_) }
2636sub set_value { shift->{slider}->set_value (@_) } 5415sub set_value { shift->{slider}->set_value (@_) }
2637 5416
2638############################################################################# 5417#############################################################################
2639 5418
2640package CFClient::UI::TextScroller; 5419package CFPlus::UI::TextScroller;
2641 5420
2642our @ISA = CFClient::UI::HBox::; 5421our @ISA = CFPlus::UI::HBox::;
2643 5422
2644use CFClient::OpenGL; 5423use CFPlus::OpenGL;
2645 5424
2646sub new { 5425sub new {
2647 my $class = shift; 5426 my $class = shift;
2648 5427
2649 my $self = $class->SUPER::new ( 5428 my $self = $class->SUPER::new (
2651 can_events => 1, 5430 can_events => 1,
2652 indent => 0, 5431 indent => 0,
2653 #font => default_font 5432 #font => default_font
2654 @_, 5433 @_,
2655 5434
2656 layout => (new CFClient::Layout), 5435 layout => (new CFPlus::Layout),
2657 par => [], 5436 par => [],
5437 max_par => 0,
2658 height => 0, 5438 height => 0,
2659 children => [ 5439 children => [
2660 (new CFClient::UI::Empty expand => 1), 5440 (new CFPlus::UI::Empty expand => 1),
2661 (new CFClient::UI::Slider vertical => 1), 5441 (new CFPlus::UI::Slider vertical => 1),
2662 ], 5442 ],
2663 ); 5443 );
2664 5444
2665 $self->{children}[1]->connect (changed => sub { $self->update }); 5445 $self->{children}[1]->connect (changed => sub { $self->update });
2666 5446
2743 5523
2744 # todo: base offset on lines or so, not on pixels 5524 # todo: base offset on lines or so, not on pixels
2745 $self->{children}[1]->set_value ($offset); 5525 $self->{children}[1]->set_value ($offset);
2746} 5526}
2747 5527
5528sub current_paragraph {
5529 my ($self) = @_;
5530
5531 $self->{top_paragraph} - 1
5532}
5533
5534sub scroll_to {
5535 my ($self, $para) = @_;
5536
5537 $para = List::Util::max 0, List::Util::min $#{$self->{par}}, $para;
5538
5539 $self->{scroll_to} = $para;
5540 $self->update;
5541}
5542
2748sub clear { 5543sub clear {
2749 my ($self) = @_; 5544 my ($self) = @_;
2750 5545
2751 my (undef, undef, @other) = @{ $self->{children} }; 5546 my (undef, undef, @other) = @{ $self->{children} };
2752 $self->remove ($_) for @other; 5547 $self->remove ($_) for @other;
2755 $self->{height} = 0; 5550 $self->{height} = 0;
2756 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]); 5551 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]);
2757} 5552}
2758 5553
2759sub add_paragraph { 5554sub add_paragraph {
2760 my ($self, $color, $para, $indent) = @_; 5555 my $self = shift;
2761 5556
2762 my ($text, @w) = ref $para ? @$para : $para; 5557 for my $para (@_) {
2763
2764 $para = { 5558 $para = {
5559 fg => [1, 1, 1, 1],
5560 indent => 0,
5561 markup => "",
5562 widget => [],
5563 ref $para ? %$para : (markup => $para),
2765 w => 1e10, 5564 w => 1e10,
2766 wrapped => 1, 5565 wrapped => 1,
2767 fg => $color,
2768 indent => $indent,
2769 markup => $text,
2770 widget => \@w,
2771 }; 5566 };
2772 5567
2773 $self->add (@w) if @w; 5568 $self->add (@{ $para->{widget} }) if @{ $para->{widget} };
2774 push @{$self->{par}}, $para; 5569 push @{$self->{par}}, $para;
5570 }
5571
5572 if (my $max = $self->{max_par}) {
5573 shift @{$self->{par}} while @{$self->{par}} > $max;
5574 }
2775 5575
2776 $self->{need_reflow}++; 5576 $self->{need_reflow}++;
2777 $self->update; 5577 $self->update;
2778} 5578}
2779 5579
2780sub scroll_to_bottom { 5580sub scroll_to_bottom {
2781 my ($self) = @_; 5581 my ($self) = @_;
2782 5582
2783 $self->{scroll_to_bottom} = 1; 5583 $self->{scroll_to} = $#{$self->{par}};
2784 $self->update; 5584 $self->update;
2785} 5585}
2786 5586
5587sub force_uptodate {
5588 my ($self) = @_;
5589
5590 if (delete $self->{need_reflow}) {
5591 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
5592
5593 my $height = 0;
5594
5595 for my $para (@{$self->{par}}) {
5596 if ($para->{w} != $W && ($para->{wrapped} || $para->{w} > $W)) {
5597 my $layout = $self->get_layout ($para);
5598 my ($w, $h) = $layout->size;
5599
5600 $para->{w} = $w + $para->{indent};
5601 $para->{h} = $h;
5602 $para->{wrapped} = $layout->has_wrapped;
5603 }
5604
5605 $para->{y} = $height;
5606 $height += $para->{h};
5607 }
5608
5609 $self->{height} = $height;
5610 $self->{children}[1]->set_range ([$self->{children}[1]{range}[0], 0, $height, $H, 1]);
5611
5612 delete $self->{texture};
5613 }
5614
5615 if (my $paridx = delete $self->{scroll_to}) {
5616 $self->{children}[1]->set_value ($self->{par}[$paridx]{y});
5617 }
5618}
5619
2787sub update { 5620sub update {
2788 my ($self) = @_; 5621 my ($self) = @_;
2789 5622
2790 $self->SUPER::update; 5623 $self->SUPER::update;
2791 5624
2792 return unless $self->{h} > 0; 5625 return unless $self->{h} > 0;
2793 5626
2794 delete $self->{texture}; 5627 delete $self->{texture};
2795 5628
2796 $ROOT->on_post_alloc ($self => sub { 5629 $ROOT->on_post_alloc ($self => sub {
5630 $self->force_uptodate;
5631
2797 my ($W, $H) = @{$self->{children}[0]}{qw(w h)}; 5632 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
2798 5633
2799 if (delete $self->{need_reflow}) {
2800 my $height = 0;
2801
2802 for my $para (@{$self->{par}}) {
2803 if ($para->{w} != $W && ($para->{wrapped} || $para->{w} > $W)) {
2804 my $layout = $self->get_layout ($para);
2805 my ($w, $h) = $layout->size;
2806
2807 $para->{w} = $w + $para->{indent};
2808 $para->{h} = $h;
2809 $para->{wrapped} = $layout->has_wrapped;
2810 }
2811
2812 $height += $para->{h};
2813 }
2814
2815 $self->{height} = $height;
2816
2817 $self->{children}[1]->set_range ([$self->{children}[1]{range}[0], 0, $height, $H, 1]);
2818
2819 delete $self->{texture};
2820 }
2821
2822 if (delete $self->{scroll_to_bottom}) {
2823 $self->{children}[1]->set_value (1e10);
2824 }
2825
2826 $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub { 5634 $self->{texture} ||= new_from_opengl CFPlus::Texture $W, $H, sub {
2827 glClearColor 0, 0, 0, 0; 5635 glClearColor 0, 0, 0, 0;
2828 glClear GL_COLOR_BUFFER_BIT; 5636 glClear GL_COLOR_BUFFER_BIT;
2829 5637
5638 package CFPlus::UI::Base;
5639 local ($draw_x, $draw_y, $draw_w, $draw_h) =
5640 (0, 0, $self->{w}, $self->{h});
5641
5642 my $top = int $self->{children}[1]{range}[0];
5643
5644 my $paridx = 0;
5645 my $top_paragraph;
2830 my $top = int $self->{children}[1]{range}[0]; 5646 my $top = int $self->{children}[1]{range}[0];
2831 5647
2832 my $y0 = $top; 5648 my $y0 = $top;
2833 my $y1 = $top + $H; 5649 my $y1 = $top + $H;
2834 5650
2835 my $y = 0;
2836
2837 for my $para (@{$self->{par}}) { 5651 for my $para (@{$self->{par}}) {
2838 my $h = $para->{h}; 5652 my $h = $para->{h};
5653 my $y = $para->{y};
2839 5654
2840 if ($y0 < $y + $h && $y < $y1) { 5655 if ($y0 < $y + $h && $y < $y1) {
2841
2842 my $layout = $self->get_layout ($para); 5656 my $layout = $self->get_layout ($para);
2843 5657
2844 $layout->render ($para->{indent}, $y - $y0); 5658 $layout->render ($para->{indent}, $y - $y0);
2845 5659
2846 if (my @w = @{ $para->{widget} }) { 5660 if (my @w = @{ $para->{widget} }) {
2855 $_->draw; 5669 $_->draw;
2856 } 5670 }
2857 } 5671 }
2858 } 5672 }
2859 5673
2860 $y += $h; 5674 $paridx++;
5675 $top_paragraph ||= $paridx if $y >= $top;
2861 } 5676 }
5677
5678 $self->{top_paragraph} = $top_paragraph;
2862 }; 5679 };
2863 }); 5680 });
2864} 5681}
2865 5682
2866sub reconfigure { 5683sub reconfigure {
2884 $self->{children}[1]->draw; 5701 $self->{children}[1]->draw;
2885} 5702}
2886 5703
2887############################################################################# 5704#############################################################################
2888 5705
2889package CFClient::UI::Animator; 5706package CFPlus::UI::Animator;
2890 5707
2891use CFClient::OpenGL; 5708use CFPlus::OpenGL;
2892 5709
2893our @ISA = CFClient::UI::Bin::; 5710our @ISA = CFPlus::UI::Bin::;
2894 5711
2895sub moveto { 5712sub moveto {
2896 my ($self, $x, $y) = @_; 5713 my ($self, $x, $y) = @_;
2897 5714
2898 $self->{moveto} = [$self->{x}, $self->{y}, $x, $y]; 5715 $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
2926 glPopMatrix; 5743 glPopMatrix;
2927} 5744}
2928 5745
2929############################################################################# 5746#############################################################################
2930 5747
2931package CFClient::UI::Flopper; 5748package CFPlus::UI::Flopper;
2932 5749
2933our @ISA = CFClient::UI::Button::; 5750our @ISA = CFPlus::UI::Button::;
2934 5751
2935sub new { 5752sub new {
2936 my $class = shift; 5753 my $class = shift;
2937 5754
2938 my $self = $class->SUPER::new ( 5755 my $self = $class->SUPER::new (
2950 $self->{other}->toggle_visibility; 5767 $self->{other}->toggle_visibility;
2951} 5768}
2952 5769
2953############################################################################# 5770#############################################################################
2954 5771
2955package CFClient::UI::Tooltip; 5772package CFPlus::UI::Tooltip;
2956 5773
2957our @ISA = CFClient::UI::Bin::; 5774our @ISA = CFPlus::UI::Bin::;
2958 5775
2959use CFClient::OpenGL; 5776use CFPlus::OpenGL;
2960 5777
2961sub new { 5778sub new {
2962 my $class = shift; 5779 my $class = shift;
2963 5780
2964 $class->SUPER::new ( 5781 $class->SUPER::new (
2967 ) 5784 )
2968} 5785}
2969 5786
2970sub set_tooltip_from { 5787sub set_tooltip_from {
2971 my ($self, $widget) = @_; 5788 my ($self, $widget) = @_;
5789
5790 $widget->{tooltip} = CFPlus::Pod::section_label tooltip => $1
5791 if $widget->{tooltip} =~ /^#(.*)$/;
2972 5792
2973 my $tooltip = $widget->{tooltip}; 5793 my $tooltip = $widget->{tooltip};
2974 5794
2975 if ($ENV{CFPLUS_DEBUG} & 2) { 5795 if ($ENV{CFPLUS_DEBUG} & 2) {
2976 $tooltip .= "\n\n" . (ref $widget) . "\n" 5796 $tooltip .= "\n\n" . (ref $widget) . "\n"
2980 } 5800 }
2981 5801
2982 $tooltip =~ s/^\n+//; 5802 $tooltip =~ s/^\n+//;
2983 $tooltip =~ s/\n+$//; 5803 $tooltip =~ s/\n+$//;
2984 5804
2985 $self->add (new CFClient::UI::Label 5805 $self->add (new CFPlus::UI::Label
2986 markup => $tooltip, 5806 markup => $tooltip,
2987 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH, 5807 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH,
2988 fontsize => 0.8, 5808 fontsize => 0.8,
2989 fg => [0, 0, 0, 1], 5809 style => 1, # FLAG_INVERSE
2990 ellipsise => 0, 5810 ellipsise => 0,
2991 font => ($widget->{tooltip_font} || $::FONT_PROP), 5811 font => ($widget->{tooltip_font} || $::FONT_PROP),
2992 ); 5812 );
2993} 5813}
2994 5814
3056 $self->SUPER::_draw; 5876 $self->SUPER::_draw;
3057} 5877}
3058 5878
3059############################################################################# 5879#############################################################################
3060 5880
3061package CFClient::UI::Face; 5881package CFPlus::UI::Face;
3062 5882
3063our @ISA = CFClient::UI::DrawBG::; 5883our @ISA = CFPlus::UI::DrawBG::;
3064 5884
3065use CFClient::OpenGL; 5885use CFPlus::OpenGL;
3066 5886
3067sub new { 5887sub new {
3068 my $class = shift; 5888 my $class = shift;
3069 5889
3070 my $self = $class->SUPER::new ( 5890 my $self = $class->SUPER::new (
5891 size_w => 32,
5892 size_h => 8,
3071 aspect => 1, 5893 aspect => 1,
3072 can_events => 0, 5894 can_events => 0,
3073 @_, 5895 @_,
3074 ); 5896 );
3075 5897
3076 if ($self->{anim} && $self->{animspeed}) { 5898 if ($self->{anim} && $self->{animspeed}) {
3077 Scalar::Util::weaken (my $widget = $self); 5899 CFPlus::weaken (my $widget = $self);
3078 5900
5901 $widget->{animspeed} = List::Util::max 0.05, $widget->{animspeed};
5902 $widget->{anim_start} = $self->{animspeed} * Event::time / $self->{animspeed};
3079 $self->{timer} = Event->timer ( 5903 $self->{timer} = Event->timer (
3080 at => $self->{animspeed} * int $::NOW / $self->{animspeed},
3081 hard => 1, 5904 parked => 1,
3082 interval => $self->{animspeed},
3083 cb => sub { 5905 cb => sub {
5906 return unless $::CONN && $widget;
5907
3084 ++$widget->{frame}; 5908 ++$widget->{frame};
5909 $widget->update_face;
3085 $widget->update; 5910 $widget->update;
5911
5912 $widget->update_timer;
3086 }, 5913 },
3087 ); 5914 );
5915
5916 $self->update_face;
5917 $self->update_timer;
3088 } 5918 }
3089 5919
3090 $self 5920 $self
3091} 5921}
3092 5922
5923sub update_timer {
5924 my ($self) = @_;
5925
5926 return unless $self->{timer};
5927
5928 if ($self->{visible}) {
5929 $self->{timer}->at (
5930 $self->{anim_start}
5931 + $self->{animspeed}
5932 * int 1.5 + (Event::time - $self->{anim_start}) / $self->{animspeed}
5933 );
5934 $self->{timer}->start;
5935 } else {
5936 $self->{timer}->stop;
5937 }
5938}
5939
5940sub update_face {
5941 my ($self) = @_;
5942
5943 return unless $::CONN;
5944
5945 if (my $anim = $::CONN->{anim}[$self->{anim}]) {
5946 if ($anim && @$anim) {
5947 delete $self->{wait_face};
5948 $self->{face} = $anim->[ $self->{frame} % @$anim ];
5949 }
5950 }
5951}
5952
3093sub size_request { 5953sub size_request {
3094 (32, 8) 5954 my ($self) = @_;
5955
5956 if ($::CONN) {
5957 if (my $faceid = $::CONN->{faceid}[$self->{face}]) {
5958 if (my $tex = $::CONN->{texture}[$faceid]) {
5959 return ($self->{size_w} || $tex->{w}, $self->{size_h} || $tex->{h});
5960 } else {
5961 $self->{wait_face} ||= $::CONN->connect_face_update ($faceid, sub {
5962 $self->realloc;
5963 });
5964 }
5965 }
5966 }
5967
5968 ($self->{size_w} || 8, $self->{size_h} || 8)
3095} 5969}
3096 5970
3097sub update { 5971sub update {
3098 my ($self) = @_; 5972 my ($self) = @_;
3099 5973
3100 return unless $self->{visible}; 5974 return unless $self->{visible};
3101 5975
3102 $self->SUPER::update; 5976 $self->SUPER::update;
3103} 5977}
3104 5978
5979sub invoke_visibility_change {
5980 my ($self) = @_;
5981
5982 $self->update_timer;
5983
5984 0
5985}
5986
3105sub _draw { 5987sub _draw {
3106 my ($self) = @_; 5988 my ($self) = @_;
3107 5989
3108 return unless $::CONN; 5990 return unless $::CONN;
3109 5991
3110 $self->SUPER::_draw; 5992 $self->SUPER::_draw;
3111 5993
3112 my $face;
3113
3114 if ($self->{frame}) {
3115 my $anim = $::CONN->{anim}[$self->{anim}]; 5994 my $faceid = $::CONN->{faceid}[$self->{face}]
3116 5995 or return;
3117 $face = $anim->[ $self->{frame} % @$anim ]
3118 if $anim && @$anim;
3119 }
3120 5996
3121 my $tex = $::CONN->{texture}[$::CONN->{faceid}[$face || $self->{face}]]; 5997 my $tex = $::CONN->{texture}[$faceid];
3122 5998
3123 if ($tex) { 5999 if ($tex) {
3124 glEnable GL_TEXTURE_2D; 6000 glEnable GL_TEXTURE_2D;
3125 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 6001 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
3126 glColor 0, 0, 0, 1; 6002 glColor 0, 0, 0, 1;
3130} 6006}
3131 6007
3132sub destroy { 6008sub destroy {
3133 my ($self) = @_; 6009 my ($self) = @_;
3134 6010
3135 $self->{timer}->cancel 6011 (delete $self->{timer})->cancel
3136 if $self->{timer}; 6012 if $self->{timer};
3137 6013
3138 $self->SUPER::destroy; 6014 $self->SUPER::destroy;
3139} 6015}
3140 6016
3141############################################################################# 6017#############################################################################
3142 6018
3143package CFClient::UI::Buttonbar; 6019package CFPlus::UI::Buttonbar;
3144 6020
3145our @ISA = CFClient::UI::HBox::; 6021our @ISA = CFPlus::UI::HBox::;
3146 6022
3147# TODO: should actualyl wrap buttons and other goodies. 6023# TODO: should actually wrap buttons and other goodies.
3148 6024
3149############################################################################# 6025#############################################################################
3150 6026
3151package CFClient::UI::Menu; 6027package CFPlus::UI::Menu;
3152 6028
3153our @ISA = CFClient::UI::FancyFrame::; 6029our @ISA = CFPlus::UI::Toplevel::;
3154 6030
3155use CFClient::OpenGL; 6031use CFPlus::OpenGL;
3156 6032
3157sub new { 6033sub new {
3158 my $class = shift; 6034 my $class = shift;
3159 6035
3160 my $self = $class->SUPER::new ( 6036 my $self = $class->SUPER::new (
3161 items => [], 6037 items => [],
3162 z => 100, 6038 z => 100,
3163 @_, 6039 @_,
3164 ); 6040 );
3165 6041
3166 $self->add ($self->{vbox} = new CFClient::UI::VBox); 6042 $self->add ($self->{vbox} = new CFPlus::UI::VBox);
3167 6043
3168 for my $item (@{ $self->{items} }) { 6044 for my $item (@{ $self->{items} }) {
3169 my ($widget, $cb, $tooltip) = @$item; 6045 my ($widget, $cb, $tooltip) = @$item;
3170 6046
3171 # handle various types of items, only text for now 6047 # handle various types of items, only text for now
3172 if (!ref $widget) { 6048 if (!ref $widget) {
3173 if ($widget =~ /\t/) { 6049 if ($widget =~ /\t/) {
3174 my ($left, $right) = split /\t/, $widget, 2; 6050 my ($left, $right) = split /\t/, $widget, 2;
3175 6051
3176 $widget = new CFClient::UI::HBox 6052 $widget = new CFPlus::UI::HBox
3177 can_hover => 1, 6053 can_hover => 1,
3178 can_events => 1, 6054 can_events => 1,
3179 tooltip => $tooltip, 6055 tooltip => $tooltip,
3180 children => [ 6056 children => [
3181 (new CFClient::UI::Label markup => $left, expand => 1), 6057 (new CFPlus::UI::Label markup => $left, expand => 1),
3182 (new CFClient::UI::Label markup => $right, align => +1), 6058 (new CFPlus::UI::Label markup => $right, align => +1),
3183 ], 6059 ],
3184 ; 6060 ;
3185 6061
3186 } else { 6062 } else {
3187 $widget = new CFClient::UI::Label 6063 $widget = new CFPlus::UI::Label
3188 can_hover => 1, 6064 can_hover => 1,
3189 can_events => 1, 6065 can_events => 1,
3190 markup => $widget, 6066 markup => $widget,
3191 tooltip => $tooltip; 6067 tooltip => $tooltip;
3192 } 6068 }
3240 1 6116 1
3241} 6117}
3242 6118
3243############################################################################# 6119#############################################################################
3244 6120
3245package CFClient::UI::Multiplexer; 6121package CFPlus::UI::Multiplexer;
3246 6122
3247our @ISA = CFClient::UI::Container::; 6123our @ISA = CFPlus::UI::Container::;
3248 6124
3249sub new { 6125sub new {
3250 my $class = shift; 6126 my $class = shift;
3251 6127
3252 my $self = $class->SUPER::new ( 6128 my $self = $class->SUPER::new (
3313 $self->{current}->draw; 6189 $self->{current}->draw;
3314} 6190}
3315 6191
3316############################################################################# 6192#############################################################################
3317 6193
3318package CFClient::UI::Notebook; 6194package CFPlus::UI::Notebook;
3319 6195
3320our @ISA = CFClient::UI::VBox::; 6196our @ISA = CFPlus::UI::VBox::;
3321 6197
3322sub new { 6198sub new {
3323 my $class = shift; 6199 my $class = shift;
3324 6200
3325 my $self = $class->SUPER::new ( 6201 my $self = $class->SUPER::new (
3326 buttonbar => (new CFClient::UI::Buttonbar), 6202 buttonbar => (new CFPlus::UI::Buttonbar),
3327 multiplexer => (new CFClient::UI::Multiplexer expand => 1), 6203 multiplexer => (new CFPlus::UI::Multiplexer expand => 1),
3328 # filter => # will be put between multiplexer and $self 6204 # filter => # will be put between multiplexer and $self
3329 @_, 6205 @_,
3330 ); 6206 );
3331 6207
3332 $self->{filter}->add ($self->{multiplexer}) if $self->{filter}; 6208 $self->{filter}->add ($self->{multiplexer}) if $self->{filter};
3336} 6212}
3337 6213
3338sub add { 6214sub add {
3339 my ($self, $title, $widget, $tooltip) = @_; 6215 my ($self, $title, $widget, $tooltip) = @_;
3340 6216
3341 Scalar::Util::weaken $self; 6217 CFPlus::weaken $self;
3342 6218
3343 $self->{buttonbar}->add (new CFClient::UI::Button 6219 $self->{buttonbar}->add (new CFPlus::UI::Button
3344 markup => $title, 6220 markup => $title,
3345 tooltip => $tooltip, 6221 tooltip => $tooltip,
3346 on_activate => sub { $self->set_current_page ($widget) }, 6222 on_activate => sub { $self->set_current_page ($widget) },
3347 ); 6223 );
3348 6224
3362 $self->emit (page_changed => $self->{multiplexer}{current}); 6238 $self->emit (page_changed => $self->{multiplexer}{current});
3363} 6239}
3364 6240
3365############################################################################# 6241#############################################################################
3366 6242
3367package CFClient::UI::Selector; 6243package CFPlus::UI::Selector;
3368 6244
3369use utf8; 6245use utf8;
3370 6246
3371our @ISA = CFClient::UI::Button::; 6247our @ISA = CFPlus::UI::Button::;
3372 6248
3373sub new { 6249sub new {
3374 my $class = shift; 6250 my $class = shift;
3375 6251
3376 my $self = $class->SUPER::new ( 6252 my $self = $class->SUPER::new (
3393 my ($value, $title, $tooltip) = @$_; 6269 my ($value, $title, $tooltip) = @$_;
3394 6270
3395 push @menu_items, [$tooltip || $title, sub { $self->set_value ($value) }]; 6271 push @menu_items, [$tooltip || $title, sub { $self->set_value ($value) }];
3396 } 6272 }
3397 6273
3398 CFClient::UI::Menu->new (items => \@menu_items)->popup ($ev); 6274 CFPlus::UI::Menu->new (items => \@menu_items)->popup ($ev);
3399} 6275}
3400 6276
3401sub _set_value { 6277sub _set_value {
3402 my ($self, $value) = @_; 6278 my ($self, $value) = @_;
3403 6279
3418 $self->emit (changed => $value); 6294 $self->emit (changed => $value);
3419} 6295}
3420 6296
3421############################################################################# 6297#############################################################################
3422 6298
3423package CFClient::UI::Statusbox; 6299package CFPlus::UI::Statusbox;
3424 6300
3425our @ISA = CFClient::UI::VBox::; 6301our @ISA = CFPlus::UI::VBox::;
3426 6302
3427sub new { 6303sub new {
3428 my $class = shift; 6304 my $class = shift;
3429 6305
3430 my $self = $class->SUPER::new ( 6306 my $self = $class->SUPER::new (
3431 fontsize => 0.8, 6307 fontsize => 0.8,
3432 @_, 6308 @_,
3433 ); 6309 );
3434 6310
3435 Scalar::Util::weaken (my $this = $self); 6311 CFPlus::weaken (my $this = $self);
3436 6312
3437 $self->{timer} = Event->timer (after => 1, interval => 1, cb => sub { $this->reorder }); 6313 $self->{timer} = Event->timer (after => 1, interval => 1, cb => sub { $this->reorder });
3438 6314
3439 $self 6315 $self
3440} 6316}
3442sub reorder { 6318sub reorder {
3443 my ($self) = @_; 6319 my ($self) = @_;
3444 my $NOW = Time::HiRes::time; 6320 my $NOW = Time::HiRes::time;
3445 6321
3446 # freeze display when hovering over any label 6322 # freeze display when hovering over any label
3447 return if $CFClient::UI::TOOLTIP->{owner} 6323 return if $CFPlus::UI::TOOLTIP->{owner}
3448 && grep $CFClient::UI::TOOLTIP->{owner} == $_->{label}, 6324 && grep $CFPlus::UI::TOOLTIP->{owner} == $_->{label},
3449 values %{ $self->{item} }; 6325 values %{ $self->{item} };
3450 6326
3451 while (my ($k, $v) = each %{ $self->{item} }) { 6327 while (my ($k, $v) = each %{ $self->{item} }) {
3452 delete $self->{item}{$k} if $v->{timeout} < $NOW; 6328 delete $self->{item}{$k} if $v->{timeout} < $NOW;
3453 } 6329 }
3474 for ($short) { 6350 for ($short) {
3475 s/^\s+//; 6351 s/^\s+//;
3476 s/\s+/ /g; 6352 s/\s+/ /g;
3477 } 6353 }
3478 6354
3479 new CFClient::UI::Label 6355 new CFPlus::UI::Label
3480 markup => $short, 6356 markup => $short,
3481 tooltip => $item->{tooltip}, 6357 tooltip => $item->{tooltip},
3482 tooltip_font => $::FONT_PROP, 6358 tooltip_font => $::FONT_PROP,
3483 tooltip_width => 0.67, 6359 tooltip_width => 0.67,
3484 fontsize => $item->{fontsize} || $self->{fontsize}, 6360 fontsize => $item->{fontsize} || $self->{fontsize},
3563 $self->SUPER::destroy; 6439 $self->SUPER::destroy;
3564} 6440}
3565 6441
3566############################################################################# 6442#############################################################################
3567 6443
3568package CFClient::UI::Inventory;
3569
3570our @ISA = CFClient::UI::Table::;
3571
3572sub new {
3573 my $class = shift;
3574
3575 my $self = $class->SUPER::new (
3576 col_expand => [0, 1, 0],
3577 items => [],
3578 @_,
3579 );
3580
3581 $self->set_sort_order (undef);
3582
3583 $self
3584}
3585
3586sub update_items {
3587 my ($self) = @_;
3588
3589 $self->clear;
3590
3591 my @item = $self->{sort}->(@{ $self->{items} });
3592
3593 my @adds;
3594 my $row = 0;
3595 for my $item ($self->{sort}->(@{ $self->{items} })) {
3596 CFClient::Item::update_widgets $item;
3597
3598 push @adds, 0, $row, $item->{face_widget};
3599 push @adds, 1, $row, $item->{desc_widget};
3600 push @adds, 2, $row, $item->{weight_widget};
3601
3602 $row++;
3603 }
3604
3605 $self->add (@adds);
3606}
3607
3608sub set_sort_order {
3609 my ($self, $order) = @_;
3610
3611 $self->{sort} = $order ||= sub {
3612 sort {
3613 $a->{type} <=> $b->{type}
3614 or $a->{name} cmp $b->{name}
3615 } @_
3616 };
3617
3618 $self->update_items;
3619}
3620
3621sub set_items {
3622 my ($self, $items) = @_;
3623
3624 $self->{items} = [$items ? values %$items : ()];
3625 $self->update_items;
3626}
3627
3628#############################################################################
3629
3630package CFClient::UI::SpellList;
3631
3632our @ISA = CFClient::UI::Table::;
3633
3634sub new {
3635 my $class = shift;
3636
3637 my $self = $class->SUPER::new (
3638 binding => [],
3639 commands => [],
3640 @_,
3641 )
3642}
3643
3644my $TOOLTIP_ALL = "\n\n<small>Left click - ready spell\nMiddle click - invoke spell\nRight click - further options</small>";
3645
3646my @TOOLTIP_NAME = (align => -1, can_events => 1, can_hover => 1, tooltip =>
3647 "<b>Name</b>. The name of the spell.$TOOLTIP_ALL");
3648my @TOOLTIP_SKILL = (align => -1, can_events => 1, can_hover => 1, tooltip =>
3649 "<b>Skill</b>. The skill (or magic school) required to be able to attempt casting this spell.$TOOLTIP_ALL");
3650my @TOOLTIP_LVL = (align => 1, can_events => 1, can_hover => 1, tooltip =>
3651 "<b>Level</b>. Minimum level the caster needs in the associated skill to be able to attempt casting this spell.$TOOLTIP_ALL");
3652my @TOOLTIP_SP = (align => 1, can_events => 1, can_hover => 1, tooltip =>
3653 "<b>Spell points / Grace points</b>. Amount of spell or grace points used by each invocation.$TOOLTIP_ALL");
3654my @TOOLTIP_DMG = (align => 1, can_events => 1, can_hover => 1, tooltip =>
3655 "<b>Damage</b>. The amount of damage the spell deals when it hits.$TOOLTIP_ALL");
3656
3657sub rebuild_spell_list {
3658 my ($self) = @_;
3659
3660 $CFClient::UI::ROOT->on_refresh ($self => sub {
3661 $self->clear;
3662
3663 return unless $::CONN;
3664
3665 $self->add (1, 0, new CFClient::UI::Label text => "Spell Name", @TOOLTIP_NAME);
3666 $self->add (2, 0, new CFClient::UI::Label text => "Skill", @TOOLTIP_SKILL);
3667 $self->add (3, 0, new CFClient::UI::Label text => "Lvl" , @TOOLTIP_LVL);
3668 $self->add (4, 0, new CFClient::UI::Label text => "Sp/Gp", @TOOLTIP_SP);
3669 $self->add (5, 0, new CFClient::UI::Label text => "Dmg" , @TOOLTIP_DMG);
3670
3671 my $row = 0;
3672
3673 for (sort { $a cmp $b } keys %{ $self->{spell} }) {
3674 my $spell = $self->{spell}{$_};
3675
3676 $row++;
3677
3678 my $spell_cb = sub {
3679 my ($widget, $ev) = @_;
3680
3681 if ($ev->{button} == 1) {
3682 $::CONN->user_send ("cast $spell->{name}");
3683 } elsif ($ev->{button} == 2) {
3684 $::CONN->user_send ("invoke $spell->{name}");
3685 } elsif ($ev->{button} == 3) {
3686 (new CFClient::UI::Menu
3687 items => [
3688 ["bind <i>cast $spell->{name}</i> to a key" => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) }],
3689 ["bind <i>invoke $spell->{name}</i> to a key" => sub { $::BIND_EDITOR->do_quick_binding (["invoke $spell->{name}"]) }],
3690 ],
3691 )->popup ($ev);
3692 } else {
3693 return 0;
3694 }
3695
3696 1
3697 };
3698
3699 my $tooltip = "$spell->{message}$TOOLTIP_ALL";
3700
3701 #TODO: add path info to tooltip
3702 #$self->add (6, $row, new CFClient::UI::Label text => $spell->{path});
3703
3704 $self->add (0, $row, new CFClient::UI::Face
3705 face => $spell->{face},
3706 can_hover => 1,
3707 can_events => 1,
3708 tooltip => $tooltip,
3709 on_button_down => $spell_cb,
3710 );
3711
3712 $self->add (1, $row, new CFClient::UI::Label
3713 expand => 1,
3714 text => $spell->{name},
3715 can_hover => 1,
3716 can_events => 1,
3717 tooltip => $tooltip,
3718 on_button_down => $spell_cb,
3719 );
3720
3721 $self->add (2, $row, new CFClient::UI::Label text => $::CONN->{skill_info}{$spell->{skill}}, @TOOLTIP_SKILL);
3722 $self->add (3, $row, new CFClient::UI::Label text => $spell->{level}, @TOOLTIP_LVL);
3723 $self->add (4, $row, new CFClient::UI::Label text => $spell->{mana} || $spell->{grace}, @TOOLTIP_SP);
3724 $self->add (5, $row, new CFClient::UI::Label text => $spell->{damage}, @TOOLTIP_DMG);
3725 }
3726 });
3727}
3728
3729sub add_spell {
3730 my ($self, $spell) = @_;
3731
3732 $self->{spell}->{$spell->{name}} = $spell;
3733 $self->rebuild_spell_list;
3734}
3735
3736sub remove_spell {
3737 my ($self, $spell) = @_;
3738
3739 delete $self->{spell}->{$spell->{name}};
3740 $self->rebuild_spell_list;
3741}
3742
3743sub clear_spells {
3744 my ($self) = @_;
3745
3746 $self->{spell} = {};
3747 $self->rebuild_spell_list;
3748}
3749
3750#############################################################################
3751
3752package CFClient::UI::Root; 6444package CFPlus::UI::Root;
3753 6445
3754our @ISA = CFClient::UI::Container::; 6446our @ISA = CFPlus::UI::Container::;
3755 6447
3756use List::Util qw(min max); 6448use List::Util qw(min max);
3757 6449
3758use CFClient::OpenGL; 6450use CFPlus::OpenGL;
3759 6451
3760sub new { 6452sub new {
3761 my $class = shift; 6453 my $class = shift;
3762 6454
3763 my $self = $class->SUPER::new ( 6455 my $self = $class->SUPER::new (
3764 visible => 1, 6456 visible => 1,
3765 @_, 6457 @_,
3766 ); 6458 );
3767 6459
3768 Scalar::Util::weaken ($self->{root} = $self); 6460 CFPlus::weaken ($self->{root} = $self);
3769 6461
3770 $self 6462 $self
3771} 6463}
3772 6464
3773sub size_request { 6465sub size_request {
3968 while ($self->{post_alloc_hook}) { 6660 while ($self->{post_alloc_hook}) {
3969 $_->() 6661 $_->()
3970 for values %{delete $self->{post_alloc_hook}}; 6662 for values %{delete $self->{post_alloc_hook}};
3971 } 6663 }
3972 6664
3973
3974 glViewport 0, 0, $::WIDTH, $::HEIGHT; 6665 glViewport 0, 0, $::WIDTH, $::HEIGHT;
3975 glClearColor +($::CFG->{fow_intensity}) x 3, 1; 6666 glClearColor +($::CFG->{fow_intensity}) x 3, 1;
3976 glClear GL_COLOR_BUFFER_BIT; 6667 glClear GL_COLOR_BUFFER_BIT;
3977 6668
3978 glMatrixMode GL_PROJECTION; 6669 glMatrixMode GL_PROJECTION;
3980 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000; 6671 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000;
3981 glMatrixMode GL_MODELVIEW; 6672 glMatrixMode GL_MODELVIEW;
3982 glLoadIdentity; 6673 glLoadIdentity;
3983 6674
3984 { 6675 {
3985 package CFClient::UI::Base; 6676 package CFPlus::UI::Base;
3986 6677
3987 ($draw_x, $draw_y, $draw_w, $draw_h) = 6678 local ($draw_x, $draw_y, $draw_w, $draw_h) =
3988 (0, 0, $self->{w}, $self->{h}); 6679 (0, 0, $self->{w}, $self->{h});
3989 }
3990 6680
3991 $self->_draw; 6681 $self->_draw;
6682 }
3992} 6683}
3993 6684
3994############################################################################# 6685#############################################################################
3995 6686
3996package CFClient::UI; 6687package CFPlus::UI;
3997 6688
3998$ROOT = new CFClient::UI::Root; 6689$ROOT = new CFPlus::UI::Root;
3999$TOOLTIP = new CFClient::UI::Tooltip z => 900; 6690$TOOLTIP = new CFPlus::UI::Tooltip z => 900;
4000 6691
40011 66921
4002 6693

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines