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.240 by root, Fri May 26 18:28:23 2006 UTC vs.
Revision 1.265 by root, Thu Jun 1 02:59:46 2006 UTC

9use CFClient; 9use CFClient;
10use CFClient::Texture; 10use CFClient::Texture;
11 11
12our ($FOCUS, $HOVER, $GRAB); # various widgets 12our ($FOCUS, $HOVER, $GRAB); # various widgets
13 13
14our $LAYOUT;
14our $ROOT; 15our $ROOT;
15our $TOOLTIP; 16our $TOOLTIP;
16our $BUTTON_STATE; 17our $BUTTON_STATE;
17 18
18our %WIDGET; # all widgets, weak-referenced 19our %WIDGET; # all widgets, weak-referenced
20
21sub get_layout {
22 my $layout;
23
24 for (grep { $_->{name} } values %WIDGET) {
25 my $win = $layout->{$_->{name}} = { };
26
27 $win->{x} = ($_->{x} + $_->{w} * 0.5) / $::WIDTH if $_->{x} =~ /^[0-9.]+$/;
28 $win->{y} = ($_->{y} + $_->{h} * 0.5) / $::HEIGHT if $_->{y} =~ /^[0-9.]+$/;
29 $win->{w} = $_->{w} / $::WIDTH if defined $_->{w};
30 $win->{h} = $_->{h} / $::HEIGHT if defined $_->{h};
31
32 $win->{show} = $_->{visible} && $_->{is_toplevel};
33 }
34
35 $layout
36}
37
38sub set_layout {
39 my ($layout) = @_;
40
41 $LAYOUT = $layout;
42}
19 43
20sub check_tooltip { 44sub check_tooltip {
21 if (!$GRAB) { 45 if (!$GRAB) {
22 for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) { 46 for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) {
23 if (length $widget->{tooltip}) { 47 if (length $widget->{tooltip}) {
24 48
25 if ($TOOLTIP->{owner} != $widget) { 49 if ($TOOLTIP->{owner} != $widget) {
50 $TOOLTIP->hide;
51
26 $TOOLTIP->{owner} = $widget; 52 $TOOLTIP->{owner} = $widget;
27 53
28 my $tip = $widget->{tooltip}; 54 my $tip = $widget->{tooltip};
29 55
30 $tip = $tip->($widget) if CODE:: eq ref $tip; 56 $tip = $tip->($widget) if CODE:: eq ref $tip;
31 57
32 $TOOLTIP->set_tooltip_from ($widget); 58 $TOOLTIP->set_tooltip_from ($widget);
33 $TOOLTIP->show; 59 $TOOLTIP->show;
34
35 my ($x, $y) = $widget->coord2global ($widget->{w}, 0);
36
37 ($x, $y) = $widget->coord2global (-$TOOLTIP->{w}, 0)
38 if $x + $TOOLTIP->{w} > $::WIDTH;
39
40 $TOOLTIP->move ($x, $y);
41 $TOOLTIP->check_size;
42 $TOOLTIP->update;
43 } 60 }
44 61
45 return; 62 return;
46 } 63 }
47 } 64 }
153sub rescale_widgets { 170sub rescale_widgets {
154 my ($sx, $sy) = @_; 171 my ($sx, $sy) = @_;
155 172
156 for my $widget (values %WIDGET) { 173 for my $widget (values %WIDGET) {
157 if ($widget->{is_toplevel}) { 174 if ($widget->{is_toplevel}) {
175 $widget->{x} += $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/;
176 $widget->{y} += $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/;
177
158 $widget->{x} = int 0.5 + $widget->{x} * $sx if exists $widget->{x}; 178 $widget->{x} = int 0.5 + $widget->{x} * $sx if $widget->{x} =~ /^[0-9.]+$/;
159 $widget->{w} = int 0.5 + $widget->{w} * $sx if exists $widget->{w}; 179 $widget->{w} = int 0.5 + $widget->{w} * $sx if exists $widget->{w};
160 $widget->{req_w} = int 0.5 + $widget->{req_w} * $sx if exists $widget->{req_w}; 180 $widget->{force_w} = int 0.5 + $widget->{force_w} * $sx if exists $widget->{force_w};
161 $widget->{user_w} = int 0.5 + $widget->{user_w} * $sx if exists $widget->{user_w};
162 $widget->{y} = int 0.5 + $widget->{y} * $sy if exists $widget->{y}; 181 $widget->{y} = int 0.5 + $widget->{y} * $sy if $widget->{y} =~ /^[0-9.]+$/;
163 $widget->{h} = int 0.5 + $widget->{h} * $sy if exists $widget->{h}; 182 $widget->{h} = int 0.5 + $widget->{h} * $sy if exists $widget->{h};
164 $widget->{req_h} = int 0.5 + $widget->{req_h} * $sy if exists $widget->{req_h}; 183 $widget->{force_h} = int 0.5 + $widget->{force_h} * $sy if exists $widget->{force_h};
165 $widget->{user_h} = int 0.5 + $widget->{user_h} * $sy if exists $widget->{user_h}; 184
185 $widget->{x} -= $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/;
186 $widget->{y} -= $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/;
187
166 } 188 }
167 } 189 }
168 190
169 reconfigure_widgets; 191 reconfigure_widgets;
170} 192}
179 201
180sub new { 202sub new {
181 my $class = shift; 203 my $class = shift;
182 204
183 my $self = bless { 205 my $self = bless {
184 x => 0, 206 x => "center",
185 y => 0, 207 y => "center",
186 z => 0, 208 z => 0,
209 w => undef,
210 h => undef,
187 can_events => 1, 211 can_events => 1,
188 @_ 212 @_
189 }, $class; 213 }, $class;
190 214
215 Scalar::Util::weaken ($CFClient::UI::WIDGET{$self+0} = $self);
216
191 for (keys %$self) { 217 for (keys %$self) {
192 if (/^connect_(.*)$/) { 218 if (/^on_(.*)$/) {
193 $self->connect ($1 => delete $self->{$_}); 219 $self->connect ($1 => delete $self->{$_});
194 } 220 }
195 } 221 }
196 222
197 Scalar::Util::weaken ($CFClient::UI::WIDGET{$self+0} = $self); 223 if (my $layout = $CFClient::UI::LAYOUT->{$self->{name}}) {
224 $self->{x} = $layout->{x} * $CFClient::UI::ROOT->{alloc_w} if exists $layout->{x};
225 $self->{y} = $layout->{y} * $CFClient::UI::ROOT->{alloc_h} if exists $layout->{y};
226 $self->{force_w} = $layout->{w} * $CFClient::UI::ROOT->{alloc_w} if exists $layout->{w};
227 $self->{force_h} = $layout->{h} * $CFClient::UI::ROOT->{alloc_h} if exists $layout->{h};
228
229 $self->{x} -= $self->{force_w} * 0.5 if exists $layout->{x};
230 $self->{y} -= $self->{force_h} * 0.5 if exists $layout->{y};
231
232 $self->show if $layout->{show};
233 }
198 234
199 $self 235 $self
200} 236}
201 237
202sub destroy { 238sub destroy {
206 %$self = (); 242 %$self = ();
207} 243}
208 244
209sub show { 245sub show {
210 my ($self) = @_; 246 my ($self) = @_;
247
211 return if $self->{parent}; 248 return if $self->{parent};
212 249
213 $CFClient::UI::ROOT->add ($self); 250 $CFClient::UI::ROOT->add ($self);
214} 251}
215 252
216sub show_centered { 253sub set_visible {
217 my ($self) = @_; 254 my ($self) = @_;
255
218 return if $self->{parent}; 256 return if $self->{visible};
219 257
220 $self->show; 258 $self->{root} = $self->{parent}{root};
259 $self->{visible} = $self->{parent}{visible} + 1;
221 260
222 $CFClient::UI::ROOT->on_post_alloc ( 261 $self->emit (visibility_change => 1);
223 "centered $self" => sub { 262
224 $self->move (($::WIDTH - $self->{w}) * 0.5, ($::HEIGHT - $self->{h}) * 0.5); 263 $self->realloc if !exists $self->{req_w};
225 }, 264
226 ); 265 $_->set_visible for $self->children;
227} 266}
228 267
229sub set_invisible { 268sub set_invisible {
230 my ($self) = @_; 269 my ($self) = @_;
231 270
232 # broken show/hide model 271 return unless $self->{visible};
233 272
273 $_->set_invisible for $self->children;
274
275 delete $self->{root};
234 delete $self->{visible}; 276 delete $self->{visible};
235 277
236 undef $GRAB if $GRAB == $self; 278 undef $GRAB if $GRAB == $self;
237 undef $HOVER if $HOVER == $self; 279 undef $HOVER if $HOVER == $self;
238 280
239 CFClient::UI::check_tooltip 281 CFClient::UI::check_tooltip
240 if $CFClient::UI::TOOLTIP->{owner} == $self; 282 if $TOOLTIP->{owner} == $self;
241 283
242 $self->focus_out; 284 $self->focus_out;
285
286 $self->emit (visibility_change => 0);
287}
288
289sub set_visibility {
290 my ($self, $visible) = @_;
291
292 return if $self->{visible} == $visible;
293
294 $visible ? $self->hide
295 : $self->show;
296}
297
298sub toggle_visibility {
299 my ($self) = @_;
300
301 $self->{visible}
302 ? $self->hide
303 : $self->show;
243} 304}
244 305
245sub hide { 306sub hide {
246 my ($self) = @_; 307 my ($self) = @_;
247 308
249 310
250 $self->{parent}->remove ($self) 311 $self->{parent}->remove ($self)
251 if $self->{parent}; 312 if $self->{parent};
252} 313}
253 314
254sub move { 315sub move_abs {
255 my ($self, $x, $y, $z) = @_; 316 my ($self, $x, $y, $z) = @_;
256 317
257 $self->{x} = int $x; 318 $self->{x} = List::Util::max 0, int $x;
258 $self->{y} = int $y; 319 $self->{y} = List::Util::max 0, int $y;
259 $self->{z} = $z if defined $z; 320 $self->{z} = $z if defined $z;
260 321
261 $self->update; 322 $self->update;
262} 323}
263 324
264sub set_size { 325sub set_size {
265 my ($self, $w, $h) = @_; 326 my ($self, $w, $h) = @_;
266 327
267 $self->{user_w} = $w; 328 $self->{force_w} = $w;
268 $self->{user_h} = $h; 329 $self->{force_h} = $h;
269 330
270 $self->check_size; 331 $self->realloc;
271} 332}
272 333
273sub size_request { 334sub size_request {
274 require Carp; 335 require Carp;
275 Carp::confess "size_request is abstract"; 336 Carp::confess "size_request is abstract";
277 338
278sub configure { 339sub configure {
279 my ($self, $x, $y, $w, $h) = @_; 340 my ($self, $x, $y, $w, $h) = @_;
280 341
281 if ($self->{aspect}) { 342 if ($self->{aspect}) {
343 my ($ow, $oh) = ($w, $h);
344
282 my $w2 = List::Util::min $w, int $h * $self->{aspect}; 345 $w = List::Util::min $w, int $h * $self->{aspect};
283 my $h2 = List::Util::min $h, int $w / $self->{aspect}; 346 $h = List::Util::min $h, int $w / $self->{aspect};
284 347
285 # use alignment to adjust x, y 348 # use alignment to adjust x, y
286 349
287 $x += int +($w - $w2) * 0.5; 350 $x += int 0.5 * ($ow - $w);
288 $y += int +($h - $h2) * 0.5; 351 $y += int 0.5 * ($oh - $h);
289
290 ($w, $h) = ($w2, $h2);
291 } 352 }
292 353
293 if ($self->{x} != $x || $self->{y} != $y) { 354 if ($self->{x} ne $x || $self->{y} ne $y) {
294 $self->{x} = $x; 355 $self->{x} = $x;
295 $self->{y} = $y; 356 $self->{y} = $y;
296 $self->update; 357 $self->update;
297 } 358 }
298 359
299 if ($self->{w} != $w || $self->{h} != $h) { 360 if ($self->{alloc_w} != $w || $self->{alloc_h} != $h) {
300 $CFClient::UI::ROOT->{size_alloc}{$self} = [$self, $w, $h]; 361 return unless $self->{visible};
362
363 $self->{alloc_w} = $w;
364 $self->{alloc_h} = $h;
365
366 $self->{root}{size_alloc}{$self+0} = $self;
301 } 367 }
302} 368}
303 369
304sub size_allocate { 370sub size_allocate {
305 # nothing to be done 371 # nothing to be done
306}
307
308sub reconfigure {
309 my ($self) = @_;
310
311 $self->check_size (1);
312 $self->update;
313} 372}
314 373
315sub children { 374sub children {
316} 375}
317 376
395sub w { $_[0]{w} = $_[1] if @_ > 1; $_[0]{w} } 454sub w { $_[0]{w} = $_[1] if @_ > 1; $_[0]{w} }
396sub h { $_[0]{h} = $_[1] if @_ > 1; $_[0]{h} } 455sub h { $_[0]{h} = $_[1] if @_ > 1; $_[0]{h} }
397sub x { $_[0]{x} = $_[1] if @_ > 1; $_[0]{x} } 456sub x { $_[0]{x} = $_[1] if @_ > 1; $_[0]{x} }
398sub y { $_[0]{y} = $_[1] if @_ > 1; $_[0]{y} } 457sub y { $_[0]{y} = $_[1] if @_ > 1; $_[0]{y} }
399sub z { $_[0]{z} = $_[1] if @_ > 1; $_[0]{z} } 458sub z { $_[0]{z} = $_[1] if @_ > 1; $_[0]{z} }
459
460sub find_widget {
461 my ($self, $x, $y) = @_;
462
463 return () unless $self->{can_events};
464
465 return $self
466 if $x >= $self->{x} && $x < $self->{x} + $self->{w}
467 && $y >= $self->{y} && $y < $self->{y} + $self->{h};
468
469 ()
470}
471
472sub set_parent {
473 my ($self, $parent) = @_;
474
475 Scalar::Util::weaken ($self->{parent} = $parent);
476 $self->set_visible if $parent->{visible};
477}
478
479sub connect {
480 my ($self, $signal, $cb) = @_;
481
482 push @{ $self->{signal_cb}{$signal} }, $cb;
483}
484
485sub _emit {
486 my ($self, $signal, @args) = @_;
487
488 List::Util::sum map $_->($self, @args), @{$self->{signal_cb}{$signal} || []}
489}
490
491sub emit {
492 my ($self, $signal, @args) = @_;
493
494 $self->_emit ($signal, @args)
495 || $self->$signal (@args);
496}
497
498sub visibility_change {
499 #my ($self, $visible) = @_;
500}
501
502sub realloc {
503 my ($self) = @_;
504
505 if ($self->{visible}) {
506 return if $self->{root}{realloc}{$self+0};
507
508 $self->{root}{realloc}{$self+0} = $self;
509 $self->{root}->update;
510 } else {
511 delete $self->{req_w};
512 delete $self->{req_h};
513 }
514}
515
516sub update {
517 my ($self) = @_;
518
519 $self->{parent}->update
520 if $self->{parent};
521}
522
523sub reconfigure {
524 my ($self) = @_;
525
526 $self->realloc;
527 $self->update;
528}
400 529
401sub draw { 530sub draw {
402 my ($self) = @_; 531 my ($self) = @_;
403 532
404 return unless $self->{h} && $self->{w}; 533 return unless $self->{h} && $self->{w};
421 glVertex $x , $y + $self->{h}; 550 glVertex $x , $y + $self->{h};
422 glEnd; 551 glEnd;
423 glDisable GL_BLEND; 552 glDisable GL_BLEND;
424 } 553 }
425 554
426 if ($ENV{PCLIENT_DEBUG}) { 555 if ($ENV{CFPLUS_DEBUG} & 1) {
427 glPushMatrix; 556 glPushMatrix;
428 glColor 1, 1, 0, 1; 557 glColor 1, 1, 0, 1;
429 glTranslate $self->{x} + 0.375, $self->{y} + 0.375; 558 glTranslate $self->{x} + 0.375, $self->{y} + 0.375;
430 glBegin GL_LINE_LOOP; 559 glBegin GL_LINE_LOOP;
431 glVertex 0 , 0; 560 glVertex 0 , 0;
442 my ($self) = @_; 571 my ($self) = @_;
443 572
444 warn "no draw defined for $self\n"; 573 warn "no draw defined for $self\n";
445} 574}
446 575
447sub find_widget {
448 my ($self, $x, $y) = @_;
449
450 return () unless $self->{can_events};
451
452 return $self
453 if $x >= $self->{x} && $x < $self->{x} + $self->{w}
454 && $y >= $self->{y} && $y < $self->{y} + $self->{h};
455
456 ()
457}
458
459sub set_parent {
460 my ($self, $parent) = @_;
461
462 Scalar::Util::weaken ($self->{parent} = $parent);
463
464 $self->{root} = $parent->{root};
465 $self->{visible} = $parent->{visible};
466
467 # TODO: req_w _does_change after ->reconfigure
468 $self->check_size
469 unless exists $self->{req_w};
470
471 $self->show;
472}
473
474sub check_size {
475 my ($self, $forced) = @_;
476
477 $self->{force_alloc} = 1 if $forced;
478 $CFClient::UI::ROOT->{check_size}{$self} = $self;
479}
480
481sub update {
482 my ($self) = @_;
483
484 $self->{parent}->update
485 if $self->{parent};
486}
487
488sub connect {
489 my ($self, $signal, $cb) = @_;
490
491 push @{ $self->{signal_cb}{$signal} }, $cb;
492}
493
494sub _emit {
495 my ($self, $signal, @args) = @_;
496
497 List::Util::sum map $_->($self, @args), @{$self->{signal_cb}{$signal} || []}
498}
499
500sub emit {
501 my ($self, $signal, @args) = @_;
502
503 $self->_emit ($signal, @args)
504 || $self->$signal (@args);
505}
506
507sub DESTROY { 576sub DESTROY {
508 my ($self) = @_; 577 my ($self) = @_;
509 578
510 delete $WIDGET{$self+0}; 579 delete $WIDGET{$self+0};
511 #$self->deactivate; 580 #$self->deactivate;
567 my ($class, %arg) = @_; 636 my ($class, %arg) = @_;
568 $class->SUPER::new (can_events => 0, %arg); 637 $class->SUPER::new (can_events => 0, %arg);
569} 638}
570 639
571sub size_request { 640sub size_request {
572 (0, 0) 641 my ($self) = @_;
642
643 ($self->{w} + 0, $self->{h} + 0)
573} 644}
574 645
575sub draw { } 646sub draw { }
576 647
577############################################################################# 648#############################################################################
606 $self->{children} = [ 677 $self->{children} = [
607 sort { $a->{z} <=> $b->{z} } 678 sort { $a->{z} <=> $b->{z} }
608 @{$self->{children}}, @widgets 679 @{$self->{children}}, @widgets
609 ]; 680 ];
610 681
611 $self->check_size (1); 682 $self->realloc;
612 $self->update;
613} 683}
614 684
615sub children { 685sub children {
616 @{ $_[0]{children} } 686 @{ $_[0]{children} }
617} 687}
622 delete $child->{parent}; 692 delete $child->{parent};
623 $child->hide; 693 $child->hide;
624 694
625 $self->{children} = [ grep $_ != $child, @{ $self->{children} } ]; 695 $self->{children} = [ grep $_ != $child, @{ $self->{children} } ];
626 696
627 $self->check_size (1); 697 $self->realloc;
628 $self->update;
629} 698}
630 699
631sub clear { 700sub clear {
632 my ($self) = @_; 701 my ($self) = @_;
633 702
637 for (@$children) { 706 for (@$children) {
638 delete $_->{parent}; 707 delete $_->{parent};
639 $_->hide; 708 $_->hide;
640 } 709 }
641 710
642 $self->check_size; 711 $self->realloc;
643 $self->update;
644} 712}
645 713
646sub find_widget { 714sub find_widget {
647 my ($self, $x, $y) = @_; 715 my ($self, $x, $y) = @_;
648 716
785} 853}
786 854
787sub size_request { 855sub size_request {
788 my ($self) = @_; 856 my ($self) = @_;
789 857
790 @$self{qw(child_w child_h)} = @{$self->child}{qw(req_w req_h)}; 858 my ($w, $h) = @{$self->child}{qw(req_w req_h)};
791 859
792 @$self{qw(child_w child_h)} 860 $w = 10 if $self->{scroll_x};
861 $h = 10 if $self->{scroll_y};
862
863 ($w, $h)
793} 864}
794 865
795sub size_allocate { 866sub size_allocate {
796 my ($self, $w, $h) = @_; 867 my ($self, $w, $h) = @_;
797 868
869 my $child = $self->child;
870
798 $w = $self->{child_w} if $self->{scroll_x} && $self->{child_w}; 871 $w = $child->{req_w} if $self->{scroll_x} && $child->{req_w};
799 $h = $self->{child_h} if $self->{scroll_y} && $self->{child_h}; 872 $h = $child->{req_h} if $self->{scroll_y} && $child->{req_h};
800 873
801 $self->child->configure (0, 0, $w, $h); 874 $self->child->configure (0, 0, $w, $h);
802 $self->update; 875 $self->update;
803} 876}
804 877
857 my $class = shift; 930 my $class = shift;
858 931
859 my $self; 932 my $self;
860 933
861 my $slider = new CFClient::UI::Slider 934 my $slider = new CFClient::UI::Slider
862 vertical => 1, 935 vertical => 1,
863 range => [0, 0, 1, 0.01], # HACK fix 936 range => [0, 0, 1, 0.01], # HACK fix
864 connect_changed => sub { 937 on_changed => sub {
865 $self->{vp}->set_offset (0, $_[1]); 938 $self->{vp}->set_offset (0, $_[1]);
866 }, 939 },
867 ; 940 ;
868 941
869 $self = $class->SUPER::new ( 942 $self = $class->SUPER::new (
947 1020
948our @ISA = CFClient::UI::Bin::; 1021our @ISA = CFClient::UI::Bin::;
949 1022
950use CFClient::OpenGL; 1023use CFClient::OpenGL;
951 1024
952my @tex = 1025my $bg =
1026 new_from_file CFClient::Texture CFClient::find_rcfile "d1_bg.png",
1027 mipmap => 1, wrap => 1;
1028
1029my @border =
953 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 1030 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
954 qw(d1_bg.png d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png); 1031 qw(d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png);
955 1032
956sub new { 1033sub new {
957 my $class = shift; 1034 my $class = shift;
958
959 # TODO: user_x, user_y, overwrite moveto?
960 1035
961 my $self = $class->SUPER::new ( 1036 my $self = $class->SUPER::new (
962 bg => [1, 1, 1, 1], 1037 bg => [1, 1, 1, 1],
963 border_bg => [1, 1, 1, 1], 1038 border_bg => [1, 1, 1, 1],
964 border => 0.6, 1039 border => 0.6,
965 is_toplevel => 1,
966 can_events => 1, 1040 can_events => 1,
1041 min_w => 16,
1042 min_h => 16,
967 @_ 1043 @_
968 ); 1044 );
969 1045
970 $self->{title} &&= new CFClient::UI::Label 1046 $self->{title} &&= new CFClient::UI::Label
971 align => 0, 1047 align => 0,
1024 my ($ev, $x, $y) = @_; 1100 my ($ev, $x, $y) = @_;
1025 1101
1026 my $dx = $ev->{x} - $ox; 1102 my $dx = $ev->{x} - $ox;
1027 my $dy = $ev->{y} - $oy; 1103 my $dy = $ev->{y} - $oy;
1028 1104
1029 $self->{user_w} = $bw + $dx * ($mx ? -1 : 1); 1105 $self->{force_w} = $bw + $dx * ($mx ? -1 : 1);
1030 $self->{user_h} = $bh + $dy * ($my ? -1 : 1); 1106 $self->{force_h} = $bh + $dy * ($my ? -1 : 1);
1107
1108 $self->realloc;
1031 $self->move ($wx + $dx * $mx, $wy + $dy * $my); 1109 $self->move_abs ($wx + $dx * $mx, $wy + $dy * $my);
1032 $self->check_size;
1033 }; 1110 };
1034 1111
1035 } elsif ($lr ^ $td) { 1112 } elsif ($lr ^ $td) {
1036 my ($ox, $oy) = ($ev->{x}, $ev->{y}); 1113 my ($ox, $oy) = ($ev->{x}, $ev->{y});
1037 my ($bx, $by) = ($self->{x}, $self->{y}); 1114 my ($bx, $by) = ($self->{x}, $self->{y});
1039 $self->{motion} = sub { 1116 $self->{motion} = sub {
1040 my ($ev, $x, $y) = @_; 1117 my ($ev, $x, $y) = @_;
1041 1118
1042 ($x, $y) = ($ev->{x}, $ev->{y}); 1119 ($x, $y) = ($ev->{x}, $ev->{y});
1043 1120
1044 $self->move ($bx + $x - $ox, $by + $y - $oy); 1121 $self->move_abs ($bx + $x - $ox, $by + $y - $oy);
1045 $self->update;
1046 }; 1122 };
1047 } 1123 }
1048} 1124}
1049 1125
1050sub button_up { 1126sub button_up {
1069 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE; 1145 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
1070 1146
1071 my $border = $self->border; 1147 my $border = $self->border;
1072 1148
1073 glColor @{ $self->{border_bg} }; 1149 glColor @{ $self->{border_bg} };
1074 $tex[1]->draw_quad_alpha (0, 0, $w, $border); 1150 $border[0]->draw_quad_alpha (0, 0, $w, $border);
1075 $tex[3]->draw_quad_alpha (0, $border, $border, $ch); 1151 $border[1]->draw_quad_alpha (0, $border, $border, $ch);
1076 $tex[2]->draw_quad_alpha ($w - $border, $border, $border, $ch); 1152 $border[2]->draw_quad_alpha ($w - $border, $border, $border, $ch);
1077 $tex[4]->draw_quad_alpha (0, $h - $border, $w, $border); 1153 $border[3]->draw_quad_alpha (0, $h - $border, $w, $border);
1078 1154
1079 if (@{$self->{bg}} < 4 || $self->{bg}[3]) { 1155 if (@{$self->{bg}} < 4 || $self->{bg}[3]) {
1080 my $bg = $tex[0]; 1156 glColor @{ $self->{bg} };
1081 1157
1082 # TODO: repeat texture not scale 1158 # TODO: repeat texture not scale
1159 # solve this better(?)
1083 my $rep_x = $cw / $bg->{w}; 1160 $bg->{s} = $cw / $bg->{w};
1084 my $rep_y = $ch / $bg->{h}; 1161 $bg->{t} = $ch / $bg->{h};
1085
1086 glColor @{ $self->{bg} };
1087
1088 $bg->{s} = $rep_x;
1089 $bg->{t} = $rep_y;
1090 $bg->{wrap_mode} = 1;
1091 $bg->draw_quad_alpha ($border, $border, $cw, $ch); 1162 $bg->draw_quad_alpha ($border, $border, $cw, $ch);
1092 } 1163 }
1093 1164
1094 glDisable GL_TEXTURE_2D; 1165 glDisable GL_TEXTURE_2D;
1095 1166
1125 my ($self, $x, $y, $child) = @_; 1196 my ($self, $x, $y, $child) = @_;
1126 1197
1127 $child->set_parent ($self); 1198 $child->set_parent ($self);
1128 $self->{children}[$y][$x] = $child; 1199 $self->{children}[$y][$x] = $child;
1129 1200
1130 $self->check_size (1); 1201 $self->realloc;
1131} 1202}
1132 1203
1133# TODO: move to container class maybe? send children a signal on removal? 1204# TODO: move to container class maybe? send children a signal on removal?
1134sub clear { 1205sub clear {
1135 my ($self) = @_; 1206 my ($self) = @_;
1140 for (@children) { 1211 for (@children) {
1141 delete $_->{parent}; 1212 delete $_->{parent};
1142 $_->hide; 1213 $_->hide;
1143 } 1214 }
1144 1215
1145 $self->check_size (1); 1216 $self->realloc;
1146 $self->update;
1147} 1217}
1148 1218
1149sub get_wh { 1219sub get_wh {
1150 my ($self) = @_; 1220 my ($self) = @_;
1151 1221
1247 } 1317 }
1248} 1318}
1249 1319
1250############################################################################# 1320#############################################################################
1251 1321
1252package CFClient::UI::HBox; 1322package CFClient::UI::Box;
1253
1254# TODO: wrap into common Box base class
1255 1323
1256our @ISA = CFClient::UI::Container::; 1324our @ISA = CFClient::UI::Container::;
1257 1325
1258sub size_request { 1326sub size_request {
1259 my ($self) = @_; 1327 my ($self) = @_;
1260 1328
1261 my @alloc = map [$_->size_request], @{$self->{children}}; 1329 $self->{vertical}
1262 1330 ? (
1263 ( 1331 (List::Util::max map $_->{req_w}, @{$self->{children}}),
1264 (List::Util::sum map $_->[0], @alloc), 1332 (List::Util::sum map $_->{req_h}, @{$self->{children}}),
1265 (List::Util::max map $_->[1], @alloc), 1333 )
1266 ) 1334 : (
1335 (List::Util::sum map $_->{req_w}, @{$self->{children}}),
1336 (List::Util::max map $_->{req_h}, @{$self->{children}}),
1337 )
1267} 1338}
1268 1339
1269sub size_allocate { 1340sub size_allocate {
1270 my ($self, $w, $h) = @_; 1341 my ($self, $w, $h) = @_;
1271 1342
1272 ($h, $w) = ($w, $h); 1343 my $space = $self->{vertical} ? $h : $w;
1273
1274 my $children = $self->{children}; 1344 my $children = $self->{children};
1275 1345
1276 my @h = map $_->{req_w}, @$children; 1346 my @req;
1277 1347
1278 my $req_h = List::Util::sum @h; 1348 if ($self->{homogeneous}) {
1279 1349 @req = ($space / (@$children || 1)) x @$children;
1280 if ($req_h > $h) {
1281 # ah well, not enough space
1282 $_ *= $h / $req_h for @h;
1283 } else { 1350 } else {
1351 @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @$children;
1352 my $req = List::Util::sum @req;
1353
1354 if ($req > $space) {
1355 # ah well, not enough space
1356 $_ *= $space / $req for @req;
1357 } else {
1284 my $exp = List::Util::sum map $_->{expand}, @$children; 1358 my $expand = (List::Util::sum map $_->{expand}, @$children) || 1;
1285 $exp ||= 1;
1286 1359
1360 $space = ($space - $req) / $expand; # remaining space to give away
1361
1362 $req[$_] += $space * $children->[$_]{expand}
1287 for (0 .. $#$children) { 1363 for 0 .. $#$children;
1288 my $child = $children->[$_];
1289
1290 my $alloc_h = $h[$_];
1291 $alloc_h += ($h - $req_h) * $child->{expand} / $exp;
1292 $h[$_] = $alloc_h;
1293 } 1364 }
1294 } 1365 }
1295 1366
1296 CFClient::UI::harmonize \@h; 1367 CFClient::UI::harmonize \@req;
1297 1368
1298 my $y = 0; 1369 my $pos = 0;
1299 for (0 .. $#$children) { 1370 for (0 .. $#$children) {
1300 my $child = $children->[$_];
1301 my $h = $h[$_]; 1371 my $alloc = $req[$_];
1302 $child->configure ($y, 0, $h, $w); 1372 $children->[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h));
1303 1373
1304 $y += $h; 1374 $pos += $alloc;
1305 } 1375 }
1306 1376
1307 1 1377 1
1308} 1378}
1309 1379
1310############################################################################# 1380#############################################################################
1311 1381
1382package CFClient::UI::HBox;
1383
1384our @ISA = CFClient::UI::Box::;
1385
1386sub new {
1387 my $class = shift;
1388
1389 $class->SUPER::new (
1390 vertical => 0,
1391 @_,
1392 )
1393}
1394
1395#############################################################################
1396
1312package CFClient::UI::VBox; 1397package CFClient::UI::VBox;
1313 1398
1314# TODO: wrap into common Box base class
1315
1316our @ISA = CFClient::UI::Container::; 1399our @ISA = CFClient::UI::Box::;
1317 1400
1318sub size_request { 1401sub new {
1319 my ($self) = @_; 1402 my $class = shift;
1320 1403
1321 my @alloc = map [$_->size_request], @{$self->{children}}; 1404 $class->SUPER::new (
1322 1405 vertical => 1,
1323 ( 1406 @_,
1324 (List::Util::max map $_->[0], @alloc),
1325 (List::Util::sum map $_->[1], @alloc),
1326 ) 1407 )
1327}
1328
1329sub size_allocate {
1330 my ($self, $w, $h) = @_;
1331
1332 Carp::confess "negative size" if $w < 0 || $h < 0;#d#
1333
1334 my $children = $self->{children};
1335
1336 my @h = map $_->{req_h}, @$children;
1337
1338 my $req_h = List::Util::sum @h;
1339
1340 if ($req_h > $h) {
1341 # ah well, not enough space
1342 $_ *= $h / $req_h for @h;
1343 } else {
1344 my $exp = List::Util::sum map $_->{expand}, @$children;
1345 $exp ||= 1;
1346
1347 for (0 .. $#$children) {
1348 my $child = $children->[$_];
1349
1350 $h[$_] += ($h - $req_h) * $child->{expand} / $exp;
1351 }
1352 }
1353
1354 CFClient::UI::harmonize \@h;
1355
1356 my $y = 0;
1357 for (0 .. $#$children) {
1358 my $child = $children->[$_];
1359 my $h = $h[$_];
1360 $child->configure (0, $y, $w, $h);
1361
1362 $y += $h;
1363 }
1364
1365 1
1366} 1408}
1367 1409
1368############################################################################# 1410#############################################################################
1369 1411
1370package CFClient::UI::Label; 1412package CFClient::UI::Label;
1387 ellipsise => 3, # end 1429 ellipsise => 3, # end
1388 layout => (new CFClient::Layout), 1430 layout => (new CFClient::Layout),
1389 fontsize => 1, 1431 fontsize => 1,
1390 align => -1, 1432 align => -1,
1391 valign => -1, 1433 valign => -1,
1392 padding => 2, 1434 padding_x => 2,
1435 padding_y => 2,
1393 can_events => 0, 1436 can_events => 0,
1394 %arg 1437 %arg
1395 ); 1438 );
1396 1439
1397 if (exists $self->{template}) { 1440 if (exists $self->{template}) {
1433 $self->{text} = "T$text"; 1476 $self->{text} = "T$text";
1434 1477
1435 $self->{layout} = new CFClient::Layout if $self->{layout}->is_rgba; 1478 $self->{layout} = new CFClient::Layout if $self->{layout}->is_rgba;
1436 $self->{layout}->set_text ($text); 1479 $self->{layout}->set_text ($text);
1437 1480
1481 $self->realloc;
1438 $self->update; 1482 $self->update;
1439 $self->check_size;
1440} 1483}
1441 1484
1442sub set_markup { 1485sub set_markup {
1443 my ($self, $markup) = @_; 1486 my ($self, $markup) = @_;
1444 1487
1448 my $rgba = $markup =~ /span.*(?:foreground|background)/; 1491 my $rgba = $markup =~ /span.*(?:foreground|background)/;
1449 1492
1450 $self->{layout} = new CFClient::Layout $rgba if $self->{layout}->is_rgba != $rgba; 1493 $self->{layout} = new CFClient::Layout $rgba if $self->{layout}->is_rgba != $rgba;
1451 $self->{layout}->set_markup ($markup); 1494 $self->{layout}->set_markup ($markup);
1452 1495
1496 $self->realloc;
1453 $self->update; 1497 $self->update;
1454 $self->check_size;
1455} 1498}
1456 1499
1457sub size_request { 1500sub size_request {
1458 my ($self) = @_; 1501 my ($self) = @_;
1459 1502
1473 1516
1474 $w = List::Util::max $w, $w2; 1517 $w = List::Util::max $w, $w2;
1475 $h = List::Util::max $h, $h2; 1518 $h = List::Util::max $h, $h2;
1476 } 1519 }
1477 1520
1478 ( 1521 ($w, $h)
1479 $w + $self->{padding} * 2,
1480 $h + $self->{padding} * 2,
1481 )
1482} 1522}
1483 1523
1484sub size_allocate { 1524sub size_allocate {
1485 my ($self, $w, $h) = @_; 1525 my ($self, $w, $h) = @_;
1486 1526
1487 delete $self->{texture}; 1527 delete $self->{texture}
1528 ;#d#
1488} 1529}
1489 1530
1490sub set_fontsize { 1531sub set_fontsize {
1491 my ($self, $fontsize) = @_; 1532 my ($self, $fontsize) = @_;
1492 1533
1493 $self->{fontsize} = $fontsize; 1534 $self->{fontsize} = $fontsize;
1494 delete $self->{texture}; 1535 delete $self->{texture};
1495 1536
1496 $self->update; 1537 $self->realloc;
1497 $self->check_size;
1498} 1538}
1499 1539
1500sub _draw { 1540sub _draw {
1501 my ($self) = @_; 1541 my ($self) = @_;
1502 1542
1510 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise}); 1550 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1511 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 1551 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1512 1552
1513 my $tex = new_from_layout CFClient::Texture $self->{layout}; 1553 my $tex = new_from_layout CFClient::Texture $self->{layout};
1514 1554
1515 $self->{ox} = int ($self->{align} < 0 ? $self->{padding} 1555 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x}
1516 : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding} 1556 : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding_x}
1517 : ($self->{w} - $tex->{w}) * 0.5); 1557 : ($self->{w} - $tex->{w}) * 0.5);
1518 1558
1519 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding} 1559 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y}
1520 : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding} 1560 : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding_y}
1521 : ($self->{h} - $tex->{h}) * 0.5); 1561 : ($self->{h} - $tex->{h}) * 0.5);
1522 1562
1523 $tex 1563 $tex
1524 }; 1564 };
1525 1565
1582sub set_text { 1622sub set_text {
1583 my ($self, $text) = @_; 1623 my ($self, $text) = @_;
1584 1624
1585 $self->{cursor} = length $text; 1625 $self->{cursor} = length $text;
1586 $self->_set_text ($text); 1626 $self->_set_text ($text);
1587 $self->update; 1627
1588 $self->check_size; 1628 $self->realloc;
1589} 1629}
1590 1630
1591sub get_text { 1631sub get_text {
1592 $_[0]{text} 1632 $_[0]{text}
1593} 1633}
1626 } elsif ($uni) { 1666 } elsif ($uni) {
1627 substr $text, $self->{cursor}++, 0, chr $uni; 1667 substr $text, $self->{cursor}++, 0, chr $uni;
1628 } 1668 }
1629 1669
1630 $self->_set_text ($text); 1670 $self->_set_text ($text);
1631 $self->update; 1671
1632 $self->check_size; 1672 $self->realloc;
1633} 1673}
1634 1674
1635sub focus_in { 1675sub focus_in {
1636 my ($self) = @_; 1676 my ($self) = @_;
1637 1677
1764 1804
1765sub new { 1805sub new {
1766 my $class = shift; 1806 my $class = shift;
1767 1807
1768 $class->SUPER::new ( 1808 $class->SUPER::new (
1769 padding => 4, 1809 padding_x => 4,
1810 padding_y => 4,
1770 fg => [1, 1, 1], 1811 fg => [1, 1, 1],
1771 active_fg => [0, 0, 1], 1812 active_fg => [0, 0, 1],
1772 can_hover => 1, 1813 can_hover => 1,
1773 align => 0, 1814 align => 0,
1774 valign => 0, 1815 valign => 0,
1821 1862
1822sub new { 1863sub new {
1823 my $class = shift; 1864 my $class = shift;
1824 1865
1825 $class->SUPER::new ( 1866 $class->SUPER::new (
1826 padding => 2, 1867 padding_x => 2,
1868 padding_y => 2,
1827 fg => [1, 1, 1], 1869 fg => [1, 1, 1],
1828 active_fg => [1, 1, 0], 1870 active_fg => [1, 1, 0],
1829 bg => [0, 0, 0, 0.2], 1871 bg => [0, 0, 0, 0.2],
1830 active_bg => [1, 1, 1, 0.5], 1872 active_bg => [1, 1, 1, 0.5],
1831 state => 0, 1873 state => 0,
1835} 1877}
1836 1878
1837sub size_request { 1879sub size_request {
1838 my ($self) = @_; 1880 my ($self) = @_;
1839 1881
1840 ($self->{padding} * 2 + 6) x 2 1882 (6) x 2
1841} 1883}
1842 1884
1843sub button_down { 1885sub button_down {
1844 my ($self, $ev, $x, $y) = @_; 1886 my ($self, $ev, $x, $y) = @_;
1845 1887
1846 if ($x >= $self->{padding} && $x < $self->{w} - $self->{padding} 1888 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
1847 && $y >= $self->{padding} && $y < $self->{h} - $self->{padding}) { 1889 && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
1848 $self->{state} = !$self->{state}; 1890 $self->{state} = !$self->{state};
1849 $self->_emit (changed => $self->{state}); 1891 $self->_emit (changed => $self->{state});
1850 } 1892 }
1851} 1893}
1852 1894
1853sub _draw { 1895sub _draw {
1854 my ($self) = @_; 1896 my ($self) = @_;
1855 1897
1856 $self->SUPER::_draw; 1898 $self->SUPER::_draw;
1857 1899
1858 glTranslate $self->{padding} + 0.375, $self->{padding} + 0.375, 0; 1900 glTranslate $self->{padding_x} + 0.375, $self->{padding_y} + 0.375, 0;
1859 1901
1860 my $s = (List::Util::min @$self{qw(w h)}) - $self->{padding} * 2; 1902 my ($w, $h) = @$self{qw(w h)};
1903
1904 my $s = List::Util::min $w - $self->{padding_x} * 2, $h - $self->{padding_y} * 2;
1861 1905
1862 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} }; 1906 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };
1863 1907
1864 my $tex = $self->{state} ? $tex[1] : $tex[0]; 1908 my $tex = $self->{state} ? $tex[1] : $tex[0];
1865 1909
2130 fg => [1, 1, 1], 2174 fg => [1, 1, 1],
2131 active_fg => [0, 0, 0], 2175 active_fg => [0, 0, 0],
2132 bg => [0, 0, 0, 0.2], 2176 bg => [0, 0, 0, 0.2],
2133 active_bg => [1, 1, 1, 0.5], 2177 active_bg => [1, 1, 1, 0.5],
2134 range => [0, 0, 100, 10, 0], 2178 range => [0, 0, 100, 10, 0],
2135 req_w => $::WIDTH / 80, 2179 min_w => $::WIDTH / 80,
2136 req_h => $::WIDTH / 80, 2180 min_h => $::WIDTH / 80,
2137 vertical => 0, 2181 vertical => 0,
2138 can_hover => 1, 2182 can_hover => 1,
2139 inner_pad => 0.02, 2183 inner_pad => 0.02,
2140 @_ 2184 @_
2141 ); 2185 );
2144 $self->update; 2188 $self->update;
2145 2189
2146 $self 2190 $self
2147} 2191}
2148 2192
2193sub changed { }
2194
2149sub set_range { 2195sub set_range {
2150 my ($self, $range) = @_; 2196 my ($self, $range) = @_;
2151 2197
2152 ($range, $self->{range}) = ($self->{range}, $range); 2198 ($range, $self->{range}) = ($self->{range}, $range);
2153 2199
2179} 2225}
2180 2226
2181sub size_request { 2227sub size_request {
2182 my ($self) = @_; 2228 my ($self) = @_;
2183 2229
2184 my $w = $self->{req_w}; 2230 ($self->{req_w}, $self->{req_h})
2185 my $h = $self->{req_h};
2186
2187 $self->{vertical} ? ($h, $w) : ($w, $h)
2188} 2231}
2189 2232
2190sub button_down { 2233sub button_down {
2191 my ($self, $ev, $x, $y) = @_; 2234 my ($self, $ev, $x, $y) = @_;
2192 2235
2543 2586
2544sub new { 2587sub new {
2545 my $class = shift; 2588 my $class = shift;
2546 2589
2547 my $self = $class->SUPER::new ( 2590 my $self = $class->SUPER::new (
2548 state => 0, 2591 state => 0,
2549 connect_activate => \&toggle_flopper, 2592 on_activate => \&toggle_flopper,
2550 @_ 2593 @_
2551 ); 2594 );
2552 2595
2553 if ($self->{state}) {
2554 $self->{state} = 0;
2555 $self->toggle_flopper;
2556 }
2557
2558 $self 2596 $self
2559} 2597}
2560 2598
2561sub toggle_flopper { 2599sub toggle_flopper {
2562 my ($self) = @_; 2600 my ($self) = @_;
2563 2601
2564 # TODO: use animation 2602 $self->{other}->toggle_visibility;
2565 if ($self->{state} = !$self->{state}) {
2566 $CFClient::UI::ROOT->add ($self->{other});
2567 $self->{other}->move ($self->coord2global (0, $self->{h}));
2568 $self->_emit ("open");
2569 } else {
2570 $CFClient::UI::ROOT->remove ($self->{other});
2571 $self->_emit ("close");
2572 }
2573
2574 $self->_emit (changed => $self->{state});
2575} 2603}
2576 2604
2577############################################################################# 2605#############################################################################
2578 2606
2579package CFClient::UI::Tooltip; 2607package CFClient::UI::Tooltip;
2592} 2620}
2593 2621
2594sub set_tooltip_from { 2622sub set_tooltip_from {
2595 my ($self, $widget) = @_; 2623 my ($self, $widget) = @_;
2596 2624
2625 my $tooltip = $widget->{tooltip};
2626
2627 if ($ENV{CFPLUS_DEBUG} & 2) {
2628 $tooltip .= "\n\n" . (ref $widget) . "\n"
2629 . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n"
2630 . "req $widget->{req_w} $widget->{req_h}\n"
2631 . "visible $widget->{visible}";
2632 }
2633
2597 $self->add (new CFClient::UI::Label 2634 $self->add (new CFClient::UI::Label
2598 markup => $widget->{tooltip}, 2635 markup => $tooltip,
2599 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH, 2636 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH,
2600 fontsize => 0.8, 2637 fontsize => 0.8,
2601 fg => [0, 0, 0, 1], 2638 fg => [0, 0, 0, 1],
2602 ellipsise => 0, 2639 ellipsise => 0,
2603 font => ($widget->{tooltip_font} || $::FONT_PROP), 2640 font => ($widget->{tooltip_font} || $::FONT_PROP),
2614 2651
2615sub size_allocate { 2652sub size_allocate {
2616 my ($self, $w, $h) = @_; 2653 my ($self, $w, $h) = @_;
2617 2654
2618 $self->SUPER::size_allocate ($w - 4, $h - 4); 2655 $self->SUPER::size_allocate ($w - 4, $h - 4);
2656}
2657
2658sub visibility_change {
2659 my ($self, $visible) = @_;
2660
2661 return unless $visible;
2662
2663 $self->{root}->on_post_alloc ("move_$self" => sub {
2664 my $widget = $self->{owner}
2665 or return;
2666
2667 my ($x, $y) = $widget->coord2global ($widget->{w}, 0);
2668
2669 ($x, $y) = $widget->coord2global (-$self->{w}, 0)
2670 if $x + $self->{w} > $::WIDTH;
2671
2672 $self->move_abs ($x, $y);
2673 });
2619} 2674}
2620 2675
2621sub _draw { 2676sub _draw {
2622 my ($self) = @_; 2677 my ($self) = @_;
2623 2678
2640 glVertex $w, $h; 2695 glVertex $w, $h;
2641 glVertex $w, 0; 2696 glVertex $w, 0;
2642 glEnd; 2697 glEnd;
2643 2698
2644 glTranslate 2 - 0.375, 2 - 0.375; 2699 glTranslate 2 - 0.375, 2 - 0.375;
2700
2645 $self->SUPER::_draw; 2701 $self->SUPER::_draw;
2646} 2702}
2647 2703
2648############################################################################# 2704#############################################################################
2649 2705
2725 $self->SUPER::DESTROY; 2781 $self->SUPER::DESTROY;
2726} 2782}
2727 2783
2728############################################################################# 2784#############################################################################
2729 2785
2730package CFClient::UI::Inventory;
2731
2732our @ISA = CFClient::UI::ScrolledWindow::;
2733
2734sub new {
2735 my $class = shift;
2736
2737 my $self = $class->SUPER::new (
2738 scrolled => (new CFClient::UI::Table col_expand => [0, 1, 0]),
2739 @_,
2740 );
2741
2742 $self
2743}
2744
2745sub set_items {
2746 my ($self, $items) = @_;
2747
2748 $self->{scrolled}->clear;
2749 return unless $items;
2750
2751 my @items = sort {
2752 ($a->{type} <=> $b->{type})
2753 or ($a->{name} cmp $b->{name})
2754 } @$items;
2755
2756 $self->{real_items} = \@items;
2757
2758 my $row = 0;
2759 for my $item (@items) {
2760 CFClient::Item::update_widgets $item;
2761
2762 $self->{scrolled}->add (0, $row, $item->{face_widget});
2763 $self->{scrolled}->add (1, $row, $item->{desc_widget});
2764 $self->{scrolled}->add (2, $row, $item->{weight_widget});
2765
2766 $row++;
2767 }
2768}
2769
2770sub size_request {
2771 my ($self) = @_;
2772 ($self->{req_w}, $self->{req_h});
2773}
2774
2775#############################################################################
2776
2777package CFClient::UI::Menu; 2786package CFClient::UI::Menu;
2778 2787
2779our @ISA = CFClient::UI::FancyFrame::; 2788our @ISA = CFClient::UI::FancyFrame::;
2780 2789
2781use CFClient::OpenGL; 2790use CFClient::OpenGL;
2819 # maybe save $GRAB? must be careful about events... 2828 # maybe save $GRAB? must be careful about events...
2820 $GRAB = $self; 2829 $GRAB = $self;
2821 $self->{button} = $ev->{button}; 2830 $self->{button} = $ev->{button};
2822 2831
2823 $self->show; 2832 $self->show;
2824 $self->move ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5); 2833 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5);
2825} 2834}
2826 2835
2827sub mouse_motion { 2836sub mouse_motion {
2828 my ($self, $ev, $x, $y) = @_; 2837 my ($self, $ev, $x, $y) = @_;
2829 2838
2954 $self->SUPER::reconfigure; 2963 $self->SUPER::reconfigure;
2955} 2964}
2956 2965
2957############################################################################# 2966#############################################################################
2958 2967
2959package CFClient::UI::Root; 2968package CFClient::UI::Inventory;
2960 2969
2961our @ISA = CFClient::UI::Container::; 2970our @ISA = CFClient::UI::ScrolledWindow::;
2962
2963use CFClient::OpenGL;
2964 2971
2965sub new { 2972sub new {
2966 my $class = shift; 2973 my $class = shift;
2967 2974
2968 $class->SUPER::new ( 2975 my $self = $class->SUPER::new (
2976 scrolled => (new CFClient::UI::Table col_expand => [0, 1, 0]),
2969 @_, 2977 @_,
2970 ) 2978 );
2971}
2972 2979
2973sub configure { 2980 $self
2981}
2982
2983sub set_items {
2984 my ($self, $items) = @_;
2985
2986 $self->{scrolled}->clear;
2987 return unless $items;
2988
2989 my @items = sort {
2990 ($a->{type} <=> $b->{type})
2991 or ($a->{name} cmp $b->{name})
2992 } @$items;
2993
2994 $self->{real_items} = \@items;
2995
2996 my $row = 0;
2997 for my $item (@items) {
2998 CFClient::Item::update_widgets $item;
2999
3000 $self->{scrolled}->add (0, $row, $item->{face_widget});
3001 $self->{scrolled}->add (1, $row, $item->{desc_widget});
3002 $self->{scrolled}->add (2, $row, $item->{weight_widget});
3003
3004 $row++;
3005 }
3006}
3007
3008#############################################################################
3009
3010package CFClient::UI::BindEditor;
3011
3012our @ISA = CFClient::UI::FancyFrame::;
3013
3014sub new {
3015 my $class = shift;
3016
3017 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3018
3019 $self->add (my $vb = new CFClient::UI::VBox);
3020
3021
3022 $vb->add ($self->{rec_btn} = new CFClient::UI::Button
3023 text => "start recording",
3024 tooltip => "Start/Stops recording of actions."
3025 ."All subsequent actions after the recording started will be captured."
3026 ."The actions are displayed after the record was stopped."
3027 ."To bind the action you have to click on the 'Bind' button",
3028 on_activate => sub {
3029 unless ($self->{recording}) {
3030 $self->start;
3031 } else {
3032 $self->stop;
3033 }
3034 });
3035
3036 $vb->add (new CFClient::UI::Label text => "Actions:");
3037 $vb->add ($self->{cmdbox} = new CFClient::UI::VBox);
3038
3039 $vb->add (new CFClient::UI::Label text => "Bound to: ");
3040 $vb->add (my $hb = new CFClient::UI::HBox);
3041 $hb->add ($self->{keylbl} = new CFClient::UI::Label expand => 1);
3042 $hb->add (new CFClient::UI::Button
3043 text => "bind",
3044 tooltip => "This opens a query where you have to press the key combination to bind the recorded actions",
3045 on_activate => sub {
3046 $self->ask_for_bind;
3047 });
3048
3049 $vb->add (my $hb = new CFClient::UI::HBox);
3050 $hb->add (new CFClient::UI::Button
3051 text => "ok",
3052 expand => 1,
3053 tooltip => "This closes the binding editor and saves the binding",
3054 on_activate => sub {
3055 $self->hide;
3056 $self->commit;
3057 });
3058
3059 $hb->add (new CFClient::UI::Button
3060 text => "cancel",
3061 expand => 1,
3062 tooltip => "This closes the binding editor without saving",
3063 on_activate => sub {
3064 $self->hide;
3065 $self->{binding_cancel}->()
3066 if $self->{binding_cancel};
3067 });
3068
3069 $self->update_binding_widgets;
3070
3071 $self
3072}
3073
3074sub commit {
3075 my ($self) = @_;
3076 my ($mod, $sym, $cmds) = $self->get_binding;
3077 if ($sym != 0 && @$cmds > 0) {
3078 $::STATUSBOX->add ("Bound actions to '".CFClient::Binder::keycombo_to_name ($mod, $sym)
3079 ."'. Don't forget 'Save Config'!");
3080 $self->{binding_change}->($mod, $sym, $cmds)
3081 if $self->{binding_change};
3082 } else {
3083 $::STATUSBOX->add ("No action bound, no key or action specified!");
3084 $self->{binding_cancel}->()
3085 if $self->{binding_cancel};
3086 }
3087}
3088
3089sub start {
3090 my ($self) = @_;
3091
3092 $self->{rec_btn}->set_text ("stop recording");
3093 $self->{recording} = 1;
3094 $self->clear_command_list;
3095 $::CONN->start_record if $::CONN;
3096}
3097
3098sub stop {
3099 my ($self) = @_;
3100
3101 $self->{rec_btn}->set_text ("start recording");
3102 $self->{recording} = 0;
3103
3104 my $rec;
3105 $rec = $::CONN->stop_record if $::CONN;
3106 return unless ref $rec eq 'ARRAY';
3107 $self->set_command_list ($rec);
3108}
3109
3110# if $commit is true, the binding will be set after the user entered a key combo
3111sub ask_for_bind {
3112 my ($self, $commit) = @_;
3113
3114 CFClient::Binder::open_binding_dialog (sub {
3115 my ($mod, $sym) = @_;
3116 $self->{binding} = [$mod, $sym]; # XXX: how to stop that memleak?
3117 $self->update_binding_widgets;
3118 $self->commit if $commit;
3119 });
3120}
3121
3122# $mod and $sym are the modifiers and key symbol
3123# $cmds is a array ref of strings (the commands)
3124# $cb is the callback that is executed on OK
3125# $ccb is the callback that is executed on CANCEL and
3126# when the binding was unsuccessful on OK
3127sub set_binding {
2974 my ($self, $x, $y, $w, $h) = @_; 3128 my ($self, $mod, $sym, $cmds, $cb, $ccb) = @_;
2975 3129
2976 $self->{w} = $w; 3130 $self->clear_command_list;
2977 $self->{h} = $h; 3131 $self->{recording} = 0;
2978} 3132 $self->{rec_btn}->set_text ("start recording");
2979 3133
2980sub check_size { 3134 $self->{binding} = [$mod, $sym];
3135 $self->{commands} = $cmds;
3136
3137 $self->{binding_change} = $cb;
3138 $self->{binding_cancel} = $ccb;
3139
3140 $self->update_binding_widgets;
3141}
3142
3143# this is a shortcut method that asks for a binding
3144# and then just binds it.
3145sub do_quick_binding {
2981 my ($self) = @_; 3146 my ($self, $cmds) = @_;
3147 $self->set_binding (undef, undef, $cmds, sub {
3148 $::CFG->{bindings}->{$_[0]}->{$_[1]} = $_[2];
3149 });
3150 $self->ask_for_bind (1);
3151}
2982 3152
2983 $self->size_allocate ($self->{w}, $self->{h}) 3153sub update_binding_widgets {
2984 if $self->{w}; 3154 my ($self) = @_;
3155 my ($mod, $sym, $cmds) = $self->get_binding;
3156 $self->{keylbl}->set_text (CFClient::Binder::keycombo_to_name ($mod, $sym));
3157 $self->set_command_list ($cmds);
3158}
3159
3160sub get_binding {
3161 my ($self) = @_;
3162 return (
3163 $self->{binding}->[0],
3164 $self->{binding}->[1],
3165 [ grep { defined $_ } @{$self->{commands}} ]
3166 );
3167}
3168
3169sub clear_command_list {
3170 my ($self) = @_;
3171 $self->{cmdbox}->clear ();
3172}
3173
3174sub set_command_list {
3175 my ($self, $cmds) = @_;
3176
3177 $self->{cmdbox}->clear ();
3178 $self->{commands} = $cmds;
3179
3180 my $idx = 0;
3181
3182 for (@$cmds) {
3183 $self->{cmdbox}->add (my $hb = new CFClient::UI::HBox);
3184
3185 my $i = $idx;
3186 $hb->add (new CFClient::UI::Label text => $_);
3187 $hb->add (new CFClient::UI::Button
3188 text => "delete",
3189 tooltip => "Deletes the action from the record",
3190 on_activate => sub {
3191 $self->{cmdbox}->remove ($hb);
3192 $cmds->[$i] = undef;
3193 });
3194
3195
3196 $idx++
3197 }
3198}
3199
3200#############################################################################
3201
3202package CFClient::UI::SpellList;
3203
3204our @ISA = CFClient::UI::FancyFrame::;
3205
3206sub new {
3207 my $class = shift;
3208
3209 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3210
3211 $self->add (new CFClient::UI::ScrolledWindow
3212 scrolled => $self->{spellbox} = new CFClient::UI::Table);
3213
3214 $self;
3215}
3216
3217# XXX: Do sorting? Argl...
3218sub add_spell {
3219 my ($self, $spell) = @_;
3220 $self->{spells}->{$spell->{name}} = $spell;
3221
3222 $self->{spellbox}->add (0, $self->{tbl_idx}, new CFClient::UI::Face
3223 face => $spell->{face},
3224 can_hover => 1,
3225 can_events => 1,
3226 tooltip => $spell->{message});
3227
3228 $self->{spellbox}->add (1, $self->{tbl_idx}, new CFClient::UI::Label
3229 text => $spell->{name},
3230 can_hover => 1,
3231 can_events => 1,
3232 tooltip => $spell->{message},
3233 expand => 1);
3234
3235 $self->{spellbox}->add (2, $self->{tbl_idx}, new CFClient::UI::Label
3236 text => (sprintf "lvl: %2d sp: %2d dmg: %2d",
3237 $spell->{level}, ($spell->{mana} || $spell->{grace}), $spell->{damage}),
3238 expand => 1);
3239
3240 $self->{spellbox}->add (3, $self->{tbl_idx}++, new CFClient::UI::Button
3241 text => "bind to key",
3242 on_activate => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) });
3243}
3244
3245sub rebuild_spell_list {
3246 my ($self) = @_;
3247 $self->{tbl_idx} = 0;
3248 $self->add_spell ($_) for values %{$self->{spells}};
3249}
3250
3251sub remove_spell {
3252 my ($self, $spell) = @_;
3253 delete $self->{spells}->{$spell->{name}};
3254 $self->rebuild_spell_list;
3255}
3256
3257#############################################################################
3258
3259package CFClient::UI::Root;
3260
3261our @ISA = CFClient::UI::Container::;
3262
3263use CFClient::OpenGL;
3264
3265sub new {
3266 my $class = shift;
3267
3268 my $self = $class->SUPER::new (
3269 visible => 1,
3270 @_,
3271 );
3272
3273 Scalar::Util::weaken ($self->{root} = $self);
3274
3275 $self
2985} 3276}
2986 3277
2987sub size_request { 3278sub size_request {
2988 my ($self) = @_; 3279 my ($self) = @_;
2989 3280
2990 ($self->{w}, $self->{h}) 3281 ($self->{w}, $self->{h})
3282}
3283
3284sub _to_pixel {
3285 my ($coord, $size, $max) = @_;
3286
3287 $coord =
3288 $coord eq "center" ? ($max - $size) * 0.5
3289 : $coord eq "max" ? $max
3290 : $coord;
3291
3292 $coord = 0 if $coord < 0;
3293 $coord = $max - $size if $coord > $max - $size;
3294
3295 int $coord + 0.5
2991} 3296}
2992 3297
2993sub size_allocate { 3298sub size_allocate {
2994 my ($self, $w, $h) = @_; 3299 my ($self, $w, $h) = @_;
2995 3300
2996 for my $child ($self->children) { 3301 for my $child ($self->children) {
2997 my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)}; 3302 my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)};
2998 3303
2999 $X = $child->{req_x} > 0 ? $child->{req_x} : $w - $W - $child->{req_x} + 1 3304 $X = $child->{force_x} if exists $child->{force_x};
3000 if exists $child->{req_x}; 3305 $Y = $child->{force_y} if exists $child->{force_y};
3001 3306
3002 $Y = $child->{req_y} > 0 ? $child->{req_y} : $h - $H - $child->{req_y} + 1 3307 $X = _to_pixel $X, $W, $self->{w};
3003 if exists $child->{req_y}; 3308 $Y = _to_pixel $Y, $H, $self->{h};
3004
3005 $X = List::Util::max 0, List::Util::min $w - $W, int $X + 0.5;
3006 $Y = List::Util::max 0, List::Util::min $h - $H, int $Y + 0.5;
3007 3309
3008 $child->configure ($X, $Y, $W, $H); 3310 $child->configure ($X, $Y, $W, $H);
3009 } 3311 }
3010} 3312}
3011 3313
3022} 3324}
3023 3325
3024sub update { 3326sub update {
3025 my ($self) = @_; 3327 my ($self) = @_;
3026 3328
3027 $self->check_size;
3028 $::WANT_REFRESH++; 3329 $::WANT_REFRESH++;
3029} 3330}
3030 3331
3031sub add { 3332sub add {
3032 my ($self, @children) = @_; 3333 my ($self, @children) = @_;
3033 3334
3034 for (my @widgets = @children; my $w = pop @widgets; ) {
3035 push @widgets, $w->children;
3036 $w->{root} = $self;
3037 $w->{visible} = 1;
3038 }
3039
3040 for my $child (@children) {
3041 $child->{is_toplevel} = 1; 3335 $_->{is_toplevel} = 1
3042 3336 for @children;
3043 # integerise window positions
3044 $child->{x} = int $child->{x};
3045 $child->{y} = int $child->{y};
3046 }
3047 3337
3048 $self->SUPER::add (@children); 3338 $self->SUPER::add (@children);
3049} 3339}
3050 3340
3051sub remove { 3341sub remove {
3052 my ($self, @children) = @_; 3342 my ($self, @children) = @_;
3053 3343
3054 $self->SUPER::remove (@children); 3344 $self->SUPER::remove (@children);
3345
3346 delete $self->{is_toplevel}
3347 for @children;
3055 3348
3056 while (@children) { 3349 while (@children) {
3057 my $w = pop @children; 3350 my $w = pop @children;
3058 push @children, $w->children; 3351 push @children, $w->children;
3059 $w->set_invisible; 3352 $w->set_invisible;
3078 while ($self->{refresh_hook}) { 3371 while ($self->{refresh_hook}) {
3079 $_->() 3372 $_->()
3080 for values %{delete $self->{refresh_hook}}; 3373 for values %{delete $self->{refresh_hook}};
3081 } 3374 }
3082 3375
3083 if ($self->{check_size}) { 3376 if ($self->{realloc}) {
3084 my @queue = ([], []); 3377 my @queue;
3085 3378
3086 for (;;) { 3379 while () {
3087 if ($self->{check_size}) { 3380 if ($self->{realloc}) {
3088 # heuristic: check containers last 3381 #TODO use array-of-depth approach
3089 push @{ $queue[ ! ! $_->isa ("CFClient::UI::Container") ] }, $_ 3382
3090 for values %{delete $self->{check_size}} 3383 use sort 'stable';
3384
3385 @queue = sort { $a->{visible} <=> $b->{visible} }
3386 @queue, values %{delete $self->{realloc}};
3091 } 3387 }
3092 3388
3093 my $widget = (pop @{ $queue[0] }) || (pop @{ $queue[1] }) || last; 3389 my $widget = pop @queue || last;
3094 3390
3095 my ($w, $h) = $widget->{user_w} && $widget->{user_h} 3391 $widget->{visible} or last; # do not resize invisible widgets
3096 ? @$widget{qw(user_w user_h)} 3392
3097 : $widget->size_request; 3393 my ($w, $h) = $widget->size_request;
3098 3394
3395 $w = List::Util::max $widget->{min_w}, $w + $widget->{padding_x} * 2;
3396 $h = List::Util::max $widget->{min_h}, $h + $widget->{padding_y} * 2;
3397
3398 $w = $widget->{force_w} if exists $widget->{force_w};
3399 $h = $widget->{force_h} if exists $widget->{force_h};
3400
3401 if ($widget->{req_w} != $w || $widget->{req_h} != $h
3099 if (delete $widget->{force_alloc} 3402 || delete $widget->{force_realloc}) {
3100 or $w != $widget->{req_w} or $h != $widget->{req_h}) {
3101 Carp::confess "$widget: size_request is negative" if $w < 0 || $h < 0;#d#
3102
3103 $widget->{req_w} = $w; 3403 $widget->{req_w} = $w;
3104 $widget->{req_h} = $h; 3404 $widget->{req_h} = $h;
3105 3405
3106 $self->{size_alloc}{$widget} = [$widget, $widget->{w} || $w, $widget->{h} || $h]; 3406 $self->{size_alloc}{$widget+0} = $widget;
3107 3407
3108 $widget->{parent}->check_size
3109 if $widget->{parent}; 3408 if (my $parent = $widget->{parent}) {
3409 $self->{realloc}{$parent+0} = $parent;
3410 #unshift @queue, $parent;
3411 $parent->{force_size_alloc} = 1;
3412 $self->{size_alloc}{$parent+0} = $parent;
3413 }
3110 } 3414 }
3415
3416 delete $self->{realloc}{$widget+0};
3111 } 3417 }
3112 } 3418 }
3113 3419
3114 while ($self->{size_alloc}) { 3420 while (my $size_alloc = delete $self->{size_alloc}) {
3115 for (values %{delete $self->{size_alloc}}) { 3421 my @queue = sort { $b->{visible} <=> $a->{visible} }
3116 my ($widget, $w, $h) = @$_; 3422 values %$size_alloc;
3423
3424 while () {
3425 my $widget = pop @queue || last;
3426
3427 my ($w, $h) = @$widget{qw(alloc_w alloc_h)};
3117 3428
3118 $w = 0 if $w < 0; 3429 $w = 0 if $w < 0;
3119 $h = 0 if $h < 0; 3430 $h = 0 if $h < 0;
3120 3431
3432 $w = int $w + 0.5;
3433 $h = int $h + 0.5;
3434
3435 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) {
3121 $widget->{w} = $w; 3436 $widget->{w} = $w;
3122 $widget->{h} = $h; 3437 $widget->{h} = $h;
3438
3123 $widget->emit (size_allocate => $w, $h); 3439 $widget->emit (size_allocate => $w, $h);
3440 }
3124 } 3441 }
3125 } 3442 }
3126 3443
3127 while ($self->{post_alloc_hook}) { 3444 while ($self->{post_alloc_hook}) {
3128 $_->() 3445 $_->()
3129 for values %{delete $self->{post_alloc_hook}}; 3446 for values %{delete $self->{post_alloc_hook}};
3130 } 3447 }
3448
3131 3449
3132 glViewport 0, 0, $::WIDTH, $::HEIGHT; 3450 glViewport 0, 0, $::WIDTH, $::HEIGHT;
3133 glClearColor +($::CFG->{fow_intensity}) x 3, 1; 3451 glClearColor +($::CFG->{fow_intensity}) x 3, 1;
3134 glClear GL_COLOR_BUFFER_BIT; 3452 glClear GL_COLOR_BUFFER_BIT;
3135 3453

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines