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.267 by root, Thu Jun 1 04:10:29 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 ();
7use Event;
8 8
9use CFClient; 9use CFPlus;
10use CFPlus::Pod;
10use CFClient::Texture; 11use CFPlus::Texture;
11 12
12our ($FOCUS, $HOVER, $GRAB); # various widgets 13our ($FOCUS, $HOVER, $GRAB); # various widgets
13 14
14our $LAYOUT; 15our $LAYOUT;
15our $ROOT; 16our $ROOT;
16our $TOOLTIP; 17our $TOOLTIP;
17our $BUTTON_STATE; 18our $BUTTON_STATE;
18 19
19our %WIDGET; # all widgets, weak-referenced 20our %WIDGET; # all widgets, weak-referenced
21
22our $TOOLTIP_WATCHER = Event->idle (min => 1/60, cb => sub {
23 if (!$GRAB) {
24 for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) {
25 if (length $widget->{tooltip}) {
26 if ($TOOLTIP->{owner} != $widget) {
27 $TOOLTIP->{owner}->emit ("tooltip_hide") if $TOOLTIP->{owner};
28 $TOOLTIP->hide;
29
30 $TOOLTIP->{owner} = $widget;
31 $TOOLTIP->{owner}->emit ("tooltip_show") if $TOOLTIP->{owner};
32
33 return if $ENV{CFPLUS_DEBUG} & 8;
34
35 my $tip = $widget->{tooltip};
36
37 $tip = $tip->($widget) if CODE:: eq ref $tip;
38
39 $TOOLTIP->set_tooltip_from ($widget);
40 $TOOLTIP->show;
41 }
42
43 return;
44 }
45 }
46 }
47
48 $TOOLTIP->hide;
49 $TOOLTIP->{owner}->emit ("tooltip_hide") if $TOOLTIP->{owner};
50 delete $TOOLTIP->{owner};
51});
20 52
21sub get_layout { 53sub get_layout {
22 my $layout; 54 my $layout;
23 55
24 for (grep { $_->{name} } values %WIDGET) { 56 for (grep { $_->{name} } values %WIDGET) {
39 my ($layout) = @_; 71 my ($layout) = @_;
40 72
41 $LAYOUT = $layout; 73 $LAYOUT = $layout;
42} 74}
43 75
44sub check_tooltip {
45 return if $ENV{CFPLUS_DEBUG} & 8;
46
47 if (!$GRAB) {
48 for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) {
49 if (length $widget->{tooltip}) {
50 if ($TOOLTIP->{owner} != $widget) {
51 $TOOLTIP->hide;
52
53 $TOOLTIP->{owner} = $widget;
54
55 my $tip = $widget->{tooltip};
56
57 $tip = $tip->($widget) if CODE:: eq ref $tip;
58
59 $TOOLTIP->set_tooltip_from ($widget);
60 $TOOLTIP->show;
61 }
62
63 return;
64 }
65 }
66 }
67
68 $TOOLTIP->hide;
69 delete $TOOLTIP->{owner};
70}
71
72# class methods for events 76# class methods for events
73sub feed_sdl_key_down_event { 77sub feed_sdl_key_down_event {
74 $FOCUS->emit (key_down => $_[0]) 78 $FOCUS->emit (key_down => $_[0])
75 if $FOCUS; 79 if $FOCUS;
76} 80}
78sub feed_sdl_key_up_event { 82sub feed_sdl_key_up_event {
79 $FOCUS->emit (key_up => $_[0]) 83 $FOCUS->emit (key_up => $_[0])
80 if $FOCUS; 84 if $FOCUS;
81} 85}
82 86
87sub check_hover {
88 my ($widget) = @_;
89
90 if ($widget != $HOVER) {
91 my $hover = $HOVER; $HOVER = $widget;
92
93 $hover->update if $hover && $hover->{can_hover};
94 $HOVER->update if $HOVER && $HOVER->{can_hover};
95
96 $TOOLTIP_WATCHER->start;
97 }
98}
99
83sub feed_sdl_button_down_event { 100sub feed_sdl_button_down_event {
84 my ($ev) = @_; 101 my ($ev) = @_;
85 my ($x, $y) = ($ev->{x}, $ev->{y}); 102 my ($x, $y) = ($ev->{x}, $ev->{y});
86 103
87 if (!$BUTTON_STATE) { 104 $BUTTON_STATE |= 1 << ($ev->{button} - 1);
105
106 unless ($GRAB) {
88 my $widget = $ROOT->find_widget ($x, $y); 107 my $widget = $ROOT->find_widget ($x, $y);
89 108
90 $GRAB = $widget; 109 $GRAB = $widget;
91 $GRAB->update if $GRAB; 110 $GRAB->update if $GRAB;
92 111
93 check_tooltip; 112 $TOOLTIP_WATCHER->cb->();
94 } 113 }
95 114
96 $BUTTON_STATE |= 1 << ($ev->{button} - 1); 115 if ($GRAB) {
97 116 if ($ev->{button} == 4 || $ev->{button} == 5) {
98 $GRAB->emit (button_down => $ev, $GRAB->coord2local ($x, $y)) 117 # mousewheel
99 if $GRAB; 118 $ev->{dx} = 0;
119 $ev->{dy} = $ev->{button} * 2 - 9;
120 $GRAB->emit (mouse_wheel => $ev);
121 } else {
122 $GRAB->emit (button_down => $ev)
123 }
124 }
100} 125}
101 126
102sub feed_sdl_button_up_event { 127sub feed_sdl_button_up_event {
103 my ($ev) = @_; 128 my ($ev) = @_;
104 my ($x, $y) = ($ev->{x}, $ev->{y});
105 129
106 my $widget = $GRAB || $ROOT->find_widget ($x, $y); 130 my $widget = $GRAB || $ROOT->find_widget ($ev->{x}, $ev->{y});
107 131
108 $BUTTON_STATE &= ~(1 << ($ev->{button} - 1)); 132 $BUTTON_STATE &= ~(1 << ($ev->{button} - 1));
109 133
110 $GRAB->emit (button_up => $ev, $GRAB->coord2local ($x, $y)) 134 $GRAB->emit (button_up => $ev)
111 if $GRAB; 135 if $GRAB && $ev->{button} != 4 && $ev->{button} != 5;
112 136
113 if (!$BUTTON_STATE) { 137 unless ($BUTTON_STATE) {
114 my $grab = $GRAB; undef $GRAB; 138 my $grab = $GRAB; undef $GRAB;
115 $grab->update if $grab; 139 $grab->update if $grab;
116 $GRAB->update if $GRAB; 140 $GRAB->update if $GRAB;
117 141
118 check_tooltip; 142 check_hover $widget;
143 $TOOLTIP_WATCHER->cb->();
119 } 144 }
120} 145}
121 146
122sub feed_sdl_motion_event { 147sub feed_sdl_motion_event {
123 my ($ev) = @_; 148 my ($ev) = @_;
124 my ($x, $y) = ($ev->{x}, $ev->{y}); 149 my ($x, $y) = ($ev->{x}, $ev->{y});
125 150
126 my $widget = $GRAB || $ROOT->find_widget ($x, $y); 151 my $widget = $GRAB || $ROOT->find_widget ($x, $y);
127 152
128 if ($widget != $HOVER) { 153 check_hover $widget;
129 my $hover = $HOVER; $HOVER = $widget;
130 154
131 $hover->update if $hover && $hover->{can_hover}; 155 $HOVER->emit (mouse_motion => $ev)
132 $HOVER->update if $HOVER && $HOVER->{can_hover};
133
134 check_tooltip;
135 }
136
137 $HOVER->emit (mouse_motion => $ev, $HOVER->coord2local ($x, $y))
138 if $HOVER; 156 if $HOVER;
139} 157}
140 158
141# convert position array to integers 159# convert position array to integers
142sub harmonize { 160sub harmonize {
171sub rescale_widgets { 189sub rescale_widgets {
172 my ($sx, $sy) = @_; 190 my ($sx, $sy) = @_;
173 191
174 for my $widget (values %WIDGET) { 192 for my $widget (values %WIDGET) {
175 if ($widget->{is_toplevel}) { 193 if ($widget->{is_toplevel}) {
176 $widget->{x} += $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/; 194 $widget->{x} += int $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/;
177 $widget->{y} += $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/; 195 $widget->{y} += int $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/;
178 196
179 $widget->{x} = int 0.5 + $widget->{x} * $sx if $widget->{x} =~ /^[0-9.]+$/; 197 $widget->{x} = int 0.5 + $widget->{x} * $sx if $widget->{x} =~ /^[0-9.]+$/;
180 $widget->{w} = int 0.5 + $widget->{w} * $sx if exists $widget->{w}; 198 $widget->{w} = int 0.5 + $widget->{w} * $sx if exists $widget->{w};
181 $widget->{force_w} = int 0.5 + $widget->{force_w} * $sx if exists $widget->{force_w}; 199 $widget->{force_w} = int 0.5 + $widget->{force_w} * $sx if exists $widget->{force_w};
182 $widget->{y} = int 0.5 + $widget->{y} * $sy if $widget->{y} =~ /^[0-9.]+$/; 200 $widget->{y} = int 0.5 + $widget->{y} * $sy if $widget->{y} =~ /^[0-9.]+$/;
183 $widget->{h} = int 0.5 + $widget->{h} * $sy if exists $widget->{h}; 201 $widget->{h} = int 0.5 + $widget->{h} * $sy if exists $widget->{h};
184 $widget->{force_h} = int 0.5 + $widget->{force_h} * $sy if exists $widget->{force_h}; 202 $widget->{force_h} = int 0.5 + $widget->{force_h} * $sy if exists $widget->{force_h};
185 203
186 $widget->{x} -= $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/; 204 $widget->{x} -= int $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/;
187 $widget->{y} -= $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/; 205 $widget->{y} -= int $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/;
188 206
189 } 207 }
190 } 208 }
191 209
192 reconfigure_widgets; 210 reconfigure_widgets;
193} 211}
194 212
195############################################################################# 213#############################################################################
196 214
215package CFPlus::UI::Event;
216
217sub xy {
218 $_[1]->coord2local ($_[0]{x}, $_[0]{y})
219}
220
221#############################################################################
222
197package CFClient::UI::Base; 223package CFPlus::UI::Base;
198 224
199use strict; 225use strict;
200 226
201use CFClient::OpenGL; 227use CFPlus::OpenGL;
202 228
203sub new { 229sub new {
204 my $class = shift; 230 my $class = shift;
205 231
206 my $self = bless { 232 my $self = bless {
211 h => undef, 237 h => undef,
212 can_events => 1, 238 can_events => 1,
213 @_ 239 @_
214 }, $class; 240 }, $class;
215 241
216 Scalar::Util::weaken ($CFClient::UI::WIDGET{$self+0} = $self); 242 CFPlus::weaken ($CFPlus::UI::WIDGET{$self+0} = $self);
217 243
218 for (keys %$self) { 244 for (keys %$self) {
219 if (/^on_(.*)$/) { 245 if (/^on_(.*)$/) {
220 $self->connect ($1 => delete $self->{$_}); 246 $self->connect ($1 => delete $self->{$_});
221 } 247 }
222 } 248 }
223 249
224 if (my $layout = $CFClient::UI::LAYOUT->{$self->{name}}) { 250 if (my $layout = $CFPlus::UI::LAYOUT->{$self->{name}}) {
225 $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};
226 $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};
227 $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};
228 $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};
229 255
230 $self->{x} -= $self->{force_w} * 0.5 if exists $layout->{x}; 256 $self->{x} -= $self->{force_w} * 0.5 if exists $layout->{x};
231 $self->{y} -= $self->{force_h} * 0.5 if exists $layout->{y}; 257 $self->{y} -= $self->{force_h} * 0.5 if exists $layout->{y};
232 258
233 $self->show if $layout->{show}; 259 $self->show if $layout->{show};
238 264
239sub destroy { 265sub destroy {
240 my ($self) = @_; 266 my ($self) = @_;
241 267
242 $self->hide; 268 $self->hide;
269 $self->emit ("destroy");
243 %$self = (); 270 %$self = ();
244} 271}
245 272
273sub TO_JSON {
274 { __widget_ref__ => $_[0]{s_id} }
275}
276
246sub show { 277sub show {
247 my ($self) = @_; 278 my ($self) = @_;
248 279
249 return if $self->{parent}; 280 return if $self->{parent};
250 281
251 $CFClient::UI::ROOT->add ($self); 282 $CFPlus::UI::ROOT->add ($self);
252} 283}
253 284
254sub set_visible { 285sub set_visible {
255 my ($self) = @_; 286 my ($self) = @_;
256 287
271 302
272 return unless $self->{visible}; 303 return unless $self->{visible};
273 304
274 $_->set_invisible for $self->children; 305 $_->set_invisible for $self->children;
275 306
307 delete $self->{visible};
276 delete $self->{root}; 308 delete $self->{root};
277 delete $self->{visible};
278 309
279 undef $GRAB if $GRAB == $self; 310 undef $GRAB if $GRAB == $self;
280 undef $HOVER if $HOVER == $self; 311 undef $HOVER if $HOVER == $self;
281 312
282 CFClient::UI::check_tooltip 313 $CFPlus::UI::TOOLTIP_WATCHER->cb->()
283 if $TOOLTIP->{owner} == $self; 314 if $TOOLTIP->{owner} == $self;
284 315
285 $self->focus_out; 316 $self->emit ("focus_out");
286
287 $self->emit (visibility_change => 0); 317 $self->emit (visibility_change => 0);
288} 318}
289 319
290sub set_visibility { 320sub set_visibility {
291 my ($self, $visible) = @_; 321 my ($self, $visible) = @_;
292 322
293 return if $self->{visible} == $visible; 323 return if $self->{visible} == $visible;
294 324
295 $visible ? $self->hide 325 $visible ? $self->show
296 : $self->show; 326 : $self->hide;
297} 327}
298 328
299sub toggle_visibility { 329sub toggle_visibility {
300 my ($self) = @_; 330 my ($self) = @_;
301 331
314} 344}
315 345
316sub move_abs { 346sub move_abs {
317 my ($self, $x, $y, $z) = @_; 347 my ($self, $x, $y, $z) = @_;
318 348
319 $self->{x} = List::Util::max 0, int $x; 349 $self->{x} = List::Util::max 0, List::Util::min $self->{root}{w} - $self->{w}, int $x;
320 $self->{y} = List::Util::max 0, int $y; 350 $self->{y} = List::Util::max 0, List::Util::min $self->{root}{h} - $self->{h}, int $y;
321 $self->{z} = $z if defined $z; 351 $self->{z} = $z if defined $z;
322 352
323 $self->update; 353 $self->update;
324} 354}
325 355
335sub size_request { 365sub size_request {
336 require Carp; 366 require Carp;
337 Carp::confess "size_request is abstract"; 367 Carp::confess "size_request is abstract";
338} 368}
339 369
370sub baseline_shift {
371 0
372}
373
340sub configure { 374sub configure {
341 my ($self, $x, $y, $w, $h) = @_; 375 my ($self, $x, $y, $w, $h) = @_;
342 376
343 if ($self->{aspect}) { 377 if ($self->{aspect}) {
344 my ($ow, $oh) = ($w, $h); 378 my ($ow, $oh) = ($w, $h);
345 379
346 $w = List::Util::min $w, int $h * $self->{aspect}; 380 $w = List::Util::min $w, CFPlus::ceil $h * $self->{aspect};
347 $h = List::Util::min $h, int $w / $self->{aspect}; 381 $h = List::Util::min $h, CFPlus::ceil $w / $self->{aspect};
348 382
349 # use alignment to adjust x, y 383 # use alignment to adjust x, y
350 384
351 $x += int 0.5 * ($ow - $w); 385 $x += int 0.5 * ($ow - $w);
352 $y += int 0.5 * ($oh - $h); 386 $y += int 0.5 * ($oh - $h);
366 400
367 $self->{root}{size_alloc}{$self+0} = $self; 401 $self->{root}{size_alloc}{$self+0} = $self;
368 } 402 }
369} 403}
370 404
371sub size_allocate {
372 # nothing to be done
373}
374
375sub children { 405sub children {
406 # nop
407}
408
409sub visible_children {
410 $_[0]->children
376} 411}
377 412
378sub set_max_size { 413sub set_max_size {
379 my ($self, $w, $h) = @_; 414 my ($self, $w, $h) = @_;
380 415
381 delete $self->{max_w}; $self->{max_w} = $w if $w; 416 $self->{max_w} = int $w if defined $w;
382 delete $self->{max_h}; $self->{max_h} = $h if $h; 417 $self->{max_h} = int $h if defined $h;
418
419 $self->realloc;
383} 420}
384 421
385sub set_tooltip { 422sub set_tooltip {
386 my ($self, $tooltip) = @_; 423 my ($self, $tooltip) = @_;
387 424
390 427
391 return if $self->{tooltip} eq $tooltip; 428 return if $self->{tooltip} eq $tooltip;
392 429
393 $self->{tooltip} = $tooltip; 430 $self->{tooltip} = $tooltip;
394 431
395 if ($CFClient::UI::TOOLTIP->{owner} == $self) { 432 if ($CFPlus::UI::TOOLTIP->{owner} == $self) {
396 delete $CFClient::UI::TOOLTIP->{owner}; 433 delete $CFPlus::UI::TOOLTIP->{owner};
397 CFClient::UI::check_tooltip; 434 $CFPlus::UI::TOOLTIP_WATCHER->cb->();
398 } 435 }
399} 436}
400 437
401# translate global coordinates to local coordinate system 438# translate global coordinates to local coordinate system
402sub coord2local { 439sub coord2local {
403 my ($self, $x, $y) = @_; 440 my ($self, $x, $y) = @_;
404 441
442 Carp::confess unless $self->{parent};#d#
443
405 $self->{parent}->coord2local ($x - $self->{x}, $y - $self->{y}) 444 $self->{parent}->coord2local ($x - $self->{x}, $y - $self->{y})
406} 445}
407 446
408# translate local coordinates to global coordinate system 447# translate local coordinates to global coordinate system
409sub coord2global { 448sub coord2global {
410 my ($self, $x, $y) = @_; 449 my ($self, $x, $y) = @_;
411 450
451 Carp::confess unless $self->{parent};#d#
452
412 $self->{parent}->coord2global ($x + $self->{x}, $y + $self->{y}) 453 $self->{parent}->coord2global ($x + $self->{x}, $y + $self->{y})
413} 454}
414 455
415sub focus_in { 456sub invoke_focus_in {
416 my ($self) = @_; 457 my ($self) = @_;
417 458
418 return if $FOCUS == $self; 459 return if $FOCUS == $self;
419 return unless $self->{can_focus}; 460 return unless $self->{can_focus};
420 461
421 my $focus = $FOCUS; $FOCUS = $self; 462 $FOCUS = $self;
422 463
423 $self->_emit (focus_in => $focus); 464 $self->update;
424 465
425 $focus->update if $focus; 466 0
426 $FOCUS->update;
427} 467}
428 468
429sub focus_out { 469sub invoke_focus_out {
430 my ($self) = @_; 470 my ($self) = @_;
431 471
432 return unless $FOCUS == $self; 472 return unless $FOCUS == $self;
433 473
434 my $focus = $FOCUS; undef $FOCUS; 474 undef $FOCUS;
435 475
436 $self->_emit (focus_out => $focus); 476 $self->update;
437 477
438 $focus->update if $focus; #?
439
440 $::MAPWIDGET->focus_in #d# focus mapwidget if no other widget has focus 478 $::MAPWIDGET->grab_focus #d# focus mapwidget if no other widget has focus
441 unless $FOCUS; 479 unless $FOCUS;
442}
443 480
481 0
482}
483
484sub grab_focus {
485 my ($self) = @_;
486
487 $FOCUS->emit ("focus_out") if $FOCUS;
488 $self->emit ("focus_in");
489}
490
444sub mouse_motion { } 491sub invoke_mouse_motion { 0 }
445sub button_up { } 492sub invoke_button_up { 0 }
446sub key_down { } 493sub invoke_key_down { 0 }
447sub key_up { } 494sub invoke_key_up { 0 }
495sub invoke_mouse_wheel { 0 }
448 496
449sub button_down { 497sub invoke_button_down {
450 my ($self, $ev, $x, $y) = @_; 498 my ($self, $ev, $x, $y) = @_;
451 499
452 $self->focus_in; 500 $self->grab_focus;
501
502 0
503}
504
505sub connect {
506 my ($self, $signal, $cb) = @_;
507
508 push @{ $self->{signal_cb}{$signal} }, $cb;
509
510 defined wantarray and CFPlus::guard {
511 @{ $self->{signal_cb}{$signal} } = grep $_ != $cb,
512 @{ $self->{signal_cb}{$signal} };
513 }
514}
515
516sub disconnect_all {
517 my ($self, $signal) = @_;
518
519 delete $self->{signal_cb}{$signal};
520}
521
522my %has_coords = (
523 button_down => 1,
524 button_up => 1,
525 mouse_motion => 1,
526 mouse_wheel => 1,
527);
528
529sub emit {
530 my ($self, $signal, @args) = @_;
531
532 # I do not really like this solution, but I do not like duplication
533 # and needlessly verbose code, either.
534 my @append
535 = $has_coords{$signal}
536 ? $args[0]->xy ($self)
537 : ();
538
539 #warn +(caller(1))[3] . "emit $signal on $self (parent $self->{parent})\n";#d#
540
541 for my $cb (
542 @{$self->{signal_cb}{$signal} || []}, # before
543 ($self->can ("invoke_$signal") || sub { 1 }), # closure
544 ) {
545 return $cb->($self, @args, @append) || next;
546 }
547
548 # parent
549 $self->{parent} && $self->{parent}->emit ($signal, @args)
453} 550}
454 551
455sub find_widget { 552sub find_widget {
456 my ($self, $x, $y) = @_; 553 my ($self, $x, $y) = @_;
457 554
465} 562}
466 563
467sub set_parent { 564sub set_parent {
468 my ($self, $parent) = @_; 565 my ($self, $parent) = @_;
469 566
470 Scalar::Util::weaken ($self->{parent} = $parent); 567 CFPlus::weaken ($self->{parent} = $parent);
471 $self->set_visible if $parent->{visible}; 568 $self->set_visible if $parent->{visible};
472}
473
474sub connect {
475 my ($self, $signal, $cb) = @_;
476
477 push @{ $self->{signal_cb}{$signal} }, $cb;
478}
479
480sub _emit {
481 my ($self, $signal, @args) = @_;
482
483 List::Util::sum map $_->($self, @args), @{$self->{signal_cb}{$signal} || []}
484}
485
486sub emit {
487 my ($self, $signal, @args) = @_;
488
489 $self->_emit ($signal, @args)
490 || $self->$signal (@args);
491}
492
493sub visibility_change {
494 #my ($self, $visible) = @_;
495} 569}
496 570
497sub realloc { 571sub realloc {
498 my ($self) = @_; 572 my ($self) = @_;
499 573
529sub draw { 603sub draw {
530 my ($self) = @_; 604 my ($self) = @_;
531 605
532 return unless $self->{h} && $self->{w}; 606 return unless $self->{h} && $self->{w};
533 607
608 # update screen rectangle
534 local $draw_x = $draw_x + $self->{x}; 609 local $draw_x = $draw_x + $self->{x};
535 local $draw_y = $draw_y + $self->{y}; 610 local $draw_y = $draw_y + $self->{y};
536 611
537 return if $draw_x + $self->{w} < 0; 612 # skip widgets that are entirely outside the drawing area
538 return if $draw_x >= $draw_w; 613 return if ($draw_x + $self->{w} < 0) || ($draw_x >= $draw_w)
539 return if $draw_y + $self->{h} < 0; 614 || ($draw_y + $self->{h} < 0) || ($draw_y >= $draw_h);
540 return if $draw_y >= $draw_h;
541 615
542 glPushMatrix; 616 glPushMatrix;
543 glTranslate $self->{x}, $self->{y}, 0; 617 glTranslate $self->{x}, $self->{y}, 0;
544 $self->_draw;
545 glPopMatrix;
546 618
547 if ($self == $HOVER && $self->{can_hover}) { 619 if ($self == $HOVER && $self->{can_hover}) {
548 my ($x, $y) = @$self{qw(x y)};
549
550 glColor 1, 0.8, 0.5, 0.2; 620 glColor 1*0.2, 0.8*0.2, 0.5*0.2, 0.2;
551 glEnable GL_BLEND; 621 glEnable GL_BLEND;
552 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 622 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
553 glBegin GL_QUADS; 623 glBegin GL_QUADS;
554 glVertex $x , $y; 624 glVertex 0 , 0;
555 glVertex $x + $self->{w}, $y; 625 glVertex $self->{w}, 0;
556 glVertex $x + $self->{w}, $y + $self->{h}; 626 glVertex $self->{w}, $self->{h};
557 glVertex $x , $y + $self->{h}; 627 glVertex 0 , $self->{h};
558 glEnd; 628 glEnd;
559 glDisable GL_BLEND; 629 glDisable GL_BLEND;
560 } 630 }
561 631
562 if ($ENV{CFPLUS_DEBUG} & 1) { 632 if ($ENV{CFPLUS_DEBUG} & 1) {
563 glPushMatrix; 633 glPushMatrix;
564 glColor 1, 1, 0, 1; 634 glColor 1, 1, 0, 1;
565 glTranslate $self->{x} + 0.375, $self->{y} + 0.375; 635 glTranslate 0.375, 0.375;
566 glBegin GL_LINE_LOOP; 636 glBegin GL_LINE_LOOP;
567 glVertex 0 , 0; 637 glVertex 0 , 0;
568 glVertex $self->{w} - 1, 0; 638 glVertex $self->{w} - 1, 0;
569 glVertex $self->{w} - 1, $self->{h} - 1; 639 glVertex $self->{w} - 1, $self->{h} - 1;
570 glVertex 0 , $self->{h} - 1; 640 glVertex 0 , $self->{h} - 1;
571 glEnd; 641 glEnd;
572 glPopMatrix; 642 glPopMatrix;
573 #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;
574 } 644 }
645
646 $self->_draw;
647 glPopMatrix;
575} 648}
576 649
577sub _draw { 650sub _draw {
578 my ($self) = @_; 651 my ($self) = @_;
579 652
580 warn "no draw defined for $self\n"; 653 warn "no draw defined for $self\n";
581} 654}
582 655
656my $cntx;#d#
583sub DESTROY { 657sub DESTROY {
584 my ($self) = @_; 658 my ($self) = @_;
585 659
660 return if CFPlus::in_destruct;
661
662 eval { $self->destroy };
663 warn "exception during widget destruction: $@" if $@ & $@ != /during global destruction/;
664
586 delete $WIDGET{$self+0}; 665 delete $WIDGET{$self+0};
587 #$self->deactivate;
588} 666}
589 667
590############################################################################# 668#############################################################################
591 669
592package CFClient::UI::DrawBG; 670package CFPlus::UI::DrawBG;
593 671
594our @ISA = CFClient::UI::Base::; 672our @ISA = CFPlus::UI::Base::;
595 673
596use strict; 674use strict;
597use CFClient::OpenGL; 675use CFPlus::OpenGL;
598 676
599sub new { 677sub new {
600 my $class = shift; 678 my $class = shift;
601 679
602 # range [value, low, high, page] 680 # range [value, low, high, page]
617 695
618 if ($color && (@$color < 4 || $color->[3])) { 696 if ($color && (@$color < 4 || $color->[3])) {
619 my ($w, $h) = @$self{qw(w h)}; 697 my ($w, $h) = @$self{qw(w h)};
620 698
621 glEnable GL_BLEND; 699 glEnable GL_BLEND;
622 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 700 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
623 glColor @$color; 701 glColor_premultiply @$color;
624 702
625 glBegin GL_QUADS; 703 glBegin GL_QUADS;
626 glVertex 0 , 0; 704 glVertex 0 , 0;
627 glVertex 0 , $h; 705 glVertex 0 , $h;
628 glVertex $w, $h; 706 glVertex $w, $h;
633 } 711 }
634} 712}
635 713
636############################################################################# 714#############################################################################
637 715
638package CFClient::UI::Empty; 716package CFPlus::UI::Empty;
639 717
640our @ISA = CFClient::UI::Base::; 718our @ISA = CFPlus::UI::Base::;
641 719
642sub new { 720sub new {
643 my ($class, %arg) = @_; 721 my ($class, %arg) = @_;
644 $class->SUPER::new (can_events => 0, %arg); 722 $class->SUPER::new (can_events => 0, %arg);
645} 723}
652 730
653sub draw { } 731sub draw { }
654 732
655############################################################################# 733#############################################################################
656 734
657package CFClient::UI::Container; 735package CFPlus::UI::Container;
658 736
659our @ISA = CFClient::UI::Base::; 737our @ISA = CFPlus::UI::Base::;
660 738
661sub new { 739sub new {
662 my ($class, %arg) = @_; 740 my ($class, %arg) = @_;
663 741
664 my $children = delete $arg{children} || []; 742 my $children = delete $arg{children};
665 743
666 my $self = $class->SUPER::new ( 744 my $self = $class->SUPER::new (
667 children => [], 745 children => [],
668 can_events => 0, 746 can_events => 0,
669 %arg, 747 %arg,
670 ); 748 );
749
671 $self->add ($_) for @$children; 750 $self->add (@$children)
751 if $children;
672 752
673 $self 753 $self
754}
755
756sub realloc {
757 my ($self) = @_;
758
759 $self->{force_realloc} = 1;
760 $self->{force_size_alloc} = 1;
761 $self->SUPER::realloc;
674} 762}
675 763
676sub add { 764sub add {
677 my ($self, @widgets) = @_; 765 my ($self, @widgets) = @_;
678 766
724 $x -= $self->{x}; 812 $x -= $self->{x};
725 $y -= $self->{y}; 813 $y -= $self->{y};
726 814
727 my $res; 815 my $res;
728 816
729 for (reverse @{ $self->{children} }) { 817 for (reverse $self->visible_children) {
730 $res = $_->find_widget ($x, $y) 818 $res = $_->find_widget ($x, $y)
731 and return $res; 819 and return $res;
732 } 820 }
733 821
734 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y}) 822 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
740 $_->draw for @{$self->{children}}; 828 $_->draw for @{$self->{children}};
741} 829}
742 830
743############################################################################# 831#############################################################################
744 832
745package CFClient::UI::Bin; 833package CFPlus::UI::Bin;
746 834
747our @ISA = CFClient::UI::Container::; 835our @ISA = CFPlus::UI::Container::;
748 836
749sub new { 837sub new {
750 my ($class, %arg) = @_; 838 my ($class, %arg) = @_;
751 839
752 my $child = (delete $arg{child}) || new CFClient::UI::Empty::; 840 my $child = (delete $arg{child}) || new CFPlus::UI::Empty::;
753 841
754 $class->SUPER::new (children => [$child], %arg) 842 $class->SUPER::new (children => [$child], %arg)
755} 843}
756 844
757sub add { 845sub add {
758 my ($self, $child) = @_; 846 my ($self, $child) = @_;
759 847
760 $self->{children} = []; 848 $self->SUPER::remove ($_) for @{ $self->{children} };
761
762 $self->SUPER::add ($child); 849 $self->SUPER::add ($child);
763} 850}
764 851
765sub remove { 852sub remove {
766 my ($self, $widget) = @_; 853 my ($self, $widget) = @_;
767 854
768 $self->SUPER::remove ($widget); 855 $self->SUPER::remove ($widget);
769 856
770 $self->{children} = [new CFClient::UI::Empty] 857 $self->{children} = [new CFPlus::UI::Empty]
771 unless @{$self->{children}}; 858 unless @{$self->{children}};
772} 859}
773 860
774sub child { $_[0]->{children}[0] } 861sub child { $_[0]->{children}[0] }
775 862
776sub size_request { 863sub size_request {
777 $_[0]{children}[0]->size_request 864 $_[0]{children}[0]->size_request
778} 865}
779 866
780sub size_allocate { 867sub invoke_size_allocate {
781 my ($self, $w, $h) = @_; 868 my ($self, $w, $h) = @_;
782 869
783 $self->{children}[0]->configure (0, 0, $w, $h); 870 $self->{children}[0]->configure (0, 0, $w, $h);
871
872 1
784} 873}
785 874
786############################################################################# 875#############################################################################
787 876
877# back-buffered drawing area
878
788package CFClient::UI::Window; 879package CFPlus::UI::Window;
789 880
790our @ISA = CFClient::UI::Bin::; 881our @ISA = CFPlus::UI::Bin::;
791 882
792use CFClient::OpenGL; 883use CFPlus::OpenGL;
793 884
794sub new { 885sub new {
795 my ($class, %arg) = @_; 886 my ($class, %arg) = @_;
796 887
797 my $self = $class->SUPER::new (%arg); 888 my $self = $class->SUPER::new (%arg);
802 893
803 $ROOT->on_post_alloc ($self => sub { $self->render_child }); 894 $ROOT->on_post_alloc ($self => sub { $self->render_child });
804 $self->SUPER::update; 895 $self->SUPER::update;
805} 896}
806 897
807sub size_allocate { 898sub invoke_size_allocate {
808 my ($self, $w, $h) = @_; 899 my ($self, $w, $h) = @_;
809 900
810 $self->SUPER::size_allocate ($w, $h);
811 $self->update; 901 $self->update;
902
903 $self->SUPER::invoke_size_allocate ($w, $h)
812} 904}
813 905
814sub _render { 906sub _render {
815 my ($self) = @_; 907 my ($self) = @_;
816 908
818} 910}
819 911
820sub render_child { 912sub render_child {
821 my ($self) = @_; 913 my ($self) = @_;
822 914
823 $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 {
824 glClearColor 0, 0, 0, 0; 916 glClearColor 0, 0, 0, 0;
825 glClear GL_COLOR_BUFFER_BIT; 917 glClear GL_COLOR_BUFFER_BIT;
826 918
827 { 919 {
828 package CFClient::UI::Base; 920 package CFPlus::UI::Base;
829 921
830 ($draw_x, $draw_y, $draw_w, $draw_h) = 922 local ($draw_x, $draw_y, $draw_w, $draw_h) =
831 (0, 0, $self->{w}, $self->{h}); 923 (0, 0, $self->{w}, $self->{h});
924
925 $self->_render;
832 } 926 }
833
834 $self->_render;
835 }; 927 };
836} 928}
837 929
838sub _draw { 930sub _draw {
839 my ($self) = @_; 931 my ($self) = @_;
840
841 my ($w, $h) = @$self{qw(w h)};
842 932
843 my $tex = $self->{texture} 933 my $tex = $self->{texture}
844 or return; 934 or return;
845 935
846 glEnable GL_TEXTURE_2D; 936 glEnable GL_TEXTURE_2D;
847 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 937 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
848 glColor 1, 1, 1, 1; 938 glColor 0, 0, 0, 1;
849 939
850 $tex->draw_quad_alpha_premultiplied (0, 0, $w, $h); 940 $tex->draw_quad_alpha_premultiplied (0, 0);
851 941
852 glDisable GL_TEXTURE_2D; 942 glDisable GL_TEXTURE_2D;
853} 943}
854 944
855############################################################################# 945#############################################################################
856 946
857package CFClient::UI::ViewPort; 947package CFPlus::UI::ViewPort;
858 948
949use List::Util qw(min max);
950
859our @ISA = CFClient::UI::Window::; 951our @ISA = CFPlus::UI::Window::;
860 952
861sub new { 953sub new {
862 my $class = shift; 954 my $class = shift;
863 955
864 $class->SUPER::new ( 956 $class->SUPER::new (
877 $h = 10 if $self->{scroll_y}; 969 $h = 10 if $self->{scroll_y};
878 970
879 ($w, $h) 971 ($w, $h)
880} 972}
881 973
882sub size_allocate { 974sub invoke_size_allocate {
883 my ($self, $w, $h) = @_; 975 my ($self, $w, $h) = @_;
884 976
885 my $child = $self->child; 977 my $child = $self->child;
886 978
887 $w = $child->{req_w} if $self->{scroll_x} && $child->{req_w}; 979 $w = $child->{req_w} if $self->{scroll_x} && $child->{req_w};
888 $h = $child->{req_h} if $self->{scroll_y} && $child->{req_h}; 980 $h = $child->{req_h} if $self->{scroll_y} && $child->{req_h};
889 981
890 $self->child->configure (0, 0, $w, $h); 982 $self->child->configure (0, 0, $w, $h);
891 $self->update; 983 $self->update;
984
985 1
892} 986}
893 987
894sub set_offset { 988sub set_offset {
895 my ($self, $x, $y) = @_; 989 my ($self, $x, $y) = @_;
896 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}) {
897 $self->{view_x} = int $x; 995 $self->{view_x} = $x;
898 $self->{view_y} = int $y; 996 $self->{view_y} = $y;
899 997
998 $self->emit (changed => $x, $y);
900 $self->update; 999 $self->update;
1000 }
901} 1001}
902 1002
903# 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
904sub coord2local { 1004sub coord2local {
905 my ($self, $x, $y) = @_; 1005 my ($self, $x, $y) = @_;
922 if ( $x >= $self->{x} && $x < $self->{x} + $self->{w} 1022 if ( $x >= $self->{x} && $x < $self->{x} + $self->{w}
923 && $y >= $self->{y} && $y < $self->{y} + $self->{h} 1023 && $y >= $self->{y} && $y < $self->{y} + $self->{h}
924 ) { 1024 ) {
925 $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})
926 } else { 1026 } else {
927 $self->CFClient::UI::Base::find_widget ($x, $y) 1027 $self->CFPlus::UI::Base::find_widget ($x, $y)
928 } 1028 }
929} 1029}
930 1030
931sub _render { 1031sub _render {
932 my ($self) = @_; 1032 my ($self) = @_;
933 1033
934 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};
935 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};
936 1036
937 CFClient::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y}; 1037 CFPlus::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y};
938 1038
939 $self->SUPER::_render; 1039 $self->SUPER::_render;
940} 1040}
941 1041
942############################################################################# 1042#############################################################################
943 1043
944package CFClient::UI::ScrolledWindow; 1044package CFPlus::UI::ScrolledWindow;
945 1045
946our @ISA = CFClient::UI::HBox::; 1046our @ISA = CFPlus::UI::Table::;
947 1047
948sub new { 1048sub new {
949 my $class = shift; 1049 my ($class, %arg) = @_;
1050
1051 my $child = delete $arg{child};
950 1052
951 my $self; 1053 my $self;
952 1054
953 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
954 vertical => 1, 1065 vertical => 1,
955 range => [0, 0, 1, 0.01], # HACK fix 1066 range => [0, 0, 1, 0.01], # HACK fix
956 on_changed => sub { 1067 on_changed => sub {
957 $self->{vp}->set_offset (0, $_[1]); 1068 $self->{vpos} = $_[1];
1069 $self->{vp}->set_offset ($self->{hpos}, $self->{vpos});
958 }, 1070 },
959 ; 1071 ;
960 1072
961 $self = $class->SUPER::new ( 1073 $self = $class->SUPER::new (
962 vp => (new CFClient::UI::ViewPort expand => 1), 1074 scroll_x => 0,
1075 scroll_y => 1,
1076 can_events => 1,
963 slider => $slider, 1077 hslider => $hslider,
1078 vslider => $vslider,
1079 col_expand => [1, 0],
1080 row_expand => [1, 0],
1081 %arg,
1082 );
1083
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
964 @_, 1095 },
965 ); 1096 ;
966 1097
967 $self->{vp}->add ($self->{scrolled});
968 $self->add ($self->{vp}); 1098 $self->SUPER::add_at (0, 0, $self->{vp});
969 $self->add ($self->{slider}); 1099
1100 $self->add ($child) if $child;
970 1101
971 $self 1102 $self
972} 1103}
973 1104
1105#TODO# update range on size_allocate depending on child
1106
1107sub add {
1108 my ($self, $widget) = @_;
1109
1110 $self->{vp}->add ($self->{child} = $widget);
1111}
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
974sub update { 3513sub update {
975 my ($self) = @_; 3514 my ($self) = @_;
976 3515
977 $self->SUPER::update; 3516 $self->SUPER::update;
978 3517 $self->update_slider;
979 # todo: overwrite size_allocate of child
980 my $child = $self->{vp}->child;
981 $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $child->{h}, $self->{vp}{h}, 1]);
982} 3518}
983 3519
3520sub invoke_mouse_wheel {
3521 my ($self, $ev) = @_;
3522
3523 return 0 unless $ev->{dy}; # only vertical movements for now
3524
3525 $self->{vslider}->emit (mouse_wheel => $ev);
3526
3527 1
3528}
3529
3530sub invoke_button_down {
3531 my ($self, $ev, $x, $y) = @_;
3532
3533 if ($ev->{button} == 2) {
3534 $self->grab_focus;
3535
3536 my $ox = $self->{vp}{view_x} + $ev->{x};
3537 my $oy = $self->{vp}{view_y} + $ev->{y};
3538
3539 $self->{motion} = sub {
3540 my ($ev, $x, $y) = @_;
3541
3542 $self->{vp}->set_offset ($ox - $ev->{x}, $oy - $ev->{y});
3543 $self->update;
3544 };
3545
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
3571}
3572
984sub size_allocate { 3573sub invoke_size_allocate {
985 my ($self, $w, $h) = @_; 3574 my ($self, $w, $h) = @_;
986 3575
3576 $self->update_slider;
987 $self->SUPER::size_allocate ($w, $h); 3577 $self->SUPER::invoke_size_allocate ($w, $h)
988
989 my $child = $self->{vp}->child;
990 $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $child->{h}, $self->{vp}{h}, 1]);
991} 3578}
992
993#TODO# update range on size_allocate depending on child
994# update viewport offset on scroll
995 3579
996############################################################################# 3580#############################################################################
997 3581
998package CFClient::UI::Frame; 3582package CFPlus::UI::Frame;
999 3583
1000our @ISA = CFClient::UI::Bin::; 3584our @ISA = CFPlus::UI::Bin::;
1001 3585
1002use CFClient::OpenGL; 3586use CFPlus::OpenGL;
1003 3587
1004sub new { 3588sub new {
1005 my $class = shift; 3589 my $class = shift;
1006 3590
1007 $class->SUPER::new ( 3591 $class->SUPER::new (
1015 3599
1016 if ($self->{bg}) { 3600 if ($self->{bg}) {
1017 my ($w, $h) = @$self{qw(w h)}; 3601 my ($w, $h) = @$self{qw(w h)};
1018 3602
1019 glEnable GL_BLEND; 3603 glEnable GL_BLEND;
1020 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 3604 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
1021 glColor @{ $self->{bg} }; 3605 glColor_premultiply @{ $self->{bg} };
1022 3606
1023 glBegin GL_QUADS; 3607 glBegin GL_QUADS;
1024 glVertex 0 , 0; 3608 glVertex 0 , 0;
1025 glVertex 0 , $h; 3609 glVertex 0 , $h;
1026 glVertex $w, $h; 3610 glVertex $w, $h;
1033 $self->SUPER::_draw; 3617 $self->SUPER::_draw;
1034} 3618}
1035 3619
1036############################################################################# 3620#############################################################################
1037 3621
1038package CFClient::UI::FancyFrame; 3622package CFPlus::UI::FancyFrame;
1039 3623
1040our @ISA = CFClient::UI::Bin::; 3624our @ISA = CFPlus::UI::Bin::;
1041 3625
1042use 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;
1043 3727
1044my $bg = 3728my $bg =
1045 new_from_file CFClient::Texture CFClient::find_rcfile "d1_bg.png", 3729 new_from_file CFPlus::Texture CFPlus::find_rcfile "d1_bg.png",
1046 mipmap => 1, wrap => 1; 3730 mipmap => 1, wrap => 1;
1047 3731
1048my @border = 3732my @border =
1049 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 3733 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1050 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);
1051 3735
3736my @icon =
3737 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
3738 qw(x1_move.png x1_resize.png);
3739
1052sub new { 3740sub new {
1053 my $class = shift; 3741 my ($class, %arg) = @_;
1054 3742
1055 my $self = $class->SUPER::new ( 3743 my $self = $class->SUPER::new (
1056 bg => [1, 1, 1, 1], 3744 bg => [1, 1, 1, 1],
1057 border_bg => [1, 1, 1, 1], 3745 border_bg => [1, 1, 1, 1],
1058 border => 0.6, 3746 border => 0.6,
1059 can_events => 1, 3747 can_events => 1,
1060 min_w => 16, 3748 min_w => 64,
1061 min_h => 16, 3749 min_h => 32,
1062 @_ 3750 %arg,
1063 ); 3751 );
1064 3752
1065 $self->{title} &&= new CFClient::UI::Label 3753 $self->{title_widget} = new CFPlus::UI::Label
1066 align => 0, 3754 align => 0,
1067 valign => 1, 3755 valign => 1,
1068 text => $self->{title}, 3756 text => $self->{title},
1069 fontsize => $self->{border}; 3757 fontsize => $self->{border},
3758 if exists $self->{title};
3759
3760 if ($self->{has_close_button}) {
3761 $self->{close_button} =
3762 new CFPlus::UI::ImageButton
3763 path => 'x1_close.png',
3764 on_activate => sub { $self->emit ("delete") };
3765
3766 $self->CFPlus::UI::Container::add ($self->{close_button});
3767 }
1070 3768
1071 $self 3769 $self
3770}
3771
3772sub add {
3773 my ($self, @widgets) = @_;
3774
3775 $self->SUPER::add (@widgets);
3776 $self->CFPlus::UI::Container::add ($self->{close_button}) if $self->{close_button};
3777 $self->CFPlus::UI::Container::add ($self->{title_widget}) if $self->{title_widget};
1072} 3778}
1073 3779
1074sub border { 3780sub border {
1075 int $_[0]{border} * $::FONTSIZE 3781 int $_[0]{border} * $::FONTSIZE
1076} 3782}
1077 3783
1078sub size_request { 3784sub size_request {
1079 my ($self) = @_; 3785 my ($self) = @_;
3786
3787 $self->{title_widget}->size_request
3788 if $self->{title_widget};
3789
3790 $self->{close_button}->size_request
3791 if $self->{close_button};
1080 3792
1081 my ($w, $h) = $self->SUPER::size_request; 3793 my ($w, $h) = $self->SUPER::size_request;
1082 3794
1083 ( 3795 (
1084 $w + $self->border * 2, 3796 $w + $self->border * 2,
1085 $h + $self->border * 2, 3797 $h + $self->border * 2,
1086 ) 3798 )
1087} 3799}
1088 3800
1089sub size_allocate { 3801sub invoke_size_allocate {
1090 my ($self, $w, $h) = @_; 3802 my ($self, $w, $h) = @_;
1091 3803
3804 if ($self->{title_widget}) {
3805 $self->{title_widget}{w} = $w;
3806 $self->{title_widget}{h} = $h;
3807 $self->{title_widget}->invoke_size_allocate ($w, $h);
3808 }
3809
3810 my $border = $self->border;
3811
1092 $h -= List::Util::max 0, $self->border * 2; 3812 $h -= List::Util::max 0, $border * 2;
1093 $w -= List::Util::max 0, $self->border * 2; 3813 $w -= List::Util::max 0, $border * 2;
1094 3814
1095 $self->{title}->configure ($self->border, int $self->border - $::FONTSIZE * 2, $w, int $::FONTSIZE * 2)
1096 if $self->{title};
1097
1098 $self->child->configure ($self->border, $self->border, $w, $h); 3815 $self->child->configure ($border, $border, $w, $h);
1099}
1100 3816
3817 $self->{close_button}->configure ($self->{w} - $border, 0, $border, $border)
3818 if $self->{close_button};
3819
3820 1
3821}
3822
3823sub invoke_delete {
3824 my ($self) = @_;
3825
3826 $self->hide;
3827
3828 1
3829}
3830
1101sub button_down { 3831sub invoke_button_down {
1102 my ($self, $ev, $x, $y) = @_; 3832 my ($self, $ev, $x, $y) = @_;
1103 3833
1104 my ($w, $h) = @$self{qw(w h)}; 3834 my ($w, $h) = @$self{qw(w h)};
1105 my $border = $self->border; 3835 my $border = $self->border;
1106 3836
1122 my $dy = $ev->{y} - $oy; 3852 my $dy = $ev->{y} - $oy;
1123 3853
1124 $self->{force_w} = $bw + $dx * ($mx ? -1 : 1); 3854 $self->{force_w} = $bw + $dx * ($mx ? -1 : 1);
1125 $self->{force_h} = $bh + $dy * ($my ? -1 : 1); 3855 $self->{force_h} = $bh + $dy * ($my ? -1 : 1);
1126 3856
3857 $self->move_abs ($wx + $dx * $mx, $wy + $dy * $my);
1127 $self->realloc; 3858 $self->realloc;
1128 $self->move_abs ($wx + $dx * $mx, $wy + $dy * $my);
1129 }; 3859 };
1130 3860
1131 } elsif ($lr ^ $td) { 3861 } elsif ($lr ^ $td) {
1132 my ($ox, $oy) = ($ev->{x}, $ev->{y}); 3862 my ($ox, $oy) = ($ev->{x}, $ev->{y});
1133 my ($bx, $by) = ($self->{x}, $self->{y}); 3863 my ($bx, $by) = ($self->{x}, $self->{y});
1136 my ($ev, $x, $y) = @_; 3866 my ($ev, $x, $y) = @_;
1137 3867
1138 ($x, $y) = ($ev->{x}, $ev->{y}); 3868 ($x, $y) = ($ev->{x}, $ev->{y});
1139 3869
1140 $self->move_abs ($bx + $x - $ox, $by + $y - $oy); 3870 $self->move_abs ($bx + $x - $ox, $by + $y - $oy);
3871 # HACK: the next line is required to enforce placement
3872 $self->{parent}->invoke_size_allocate ($self->{parent}{w}, $self->{parent}{h});
1141 }; 3873 };
3874 } else {
3875 return 0;
3876 }
3877
1142 } 3878 1
1143} 3879}
1144 3880
1145sub button_up { 3881sub invoke_button_up {
1146 my ($self, $ev, $x, $y) = @_; 3882 my ($self, $ev, $x, $y) = @_;
1147 3883
1148 delete $self->{motion}; 3884 ! ! delete $self->{motion}
1149} 3885}
1150 3886
1151sub mouse_motion { 3887sub invoke_mouse_motion {
1152 my ($self, $ev, $x, $y) = @_; 3888 my ($self, $ev, $x, $y) = @_;
1153 3889
1154 $self->{motion}->($ev, $x, $y) if $self->{motion}; 3890 $self->{motion}->($ev, $x, $y) if $self->{motion};
3891
3892 ! ! $self->{motion}
3893}
3894
3895sub invoke_visibility_change {
3896 my ($self, $visible) = @_;
3897
3898 delete $self->{motion} unless $visible;
3899
3900 0
1155} 3901}
1156 3902
1157sub _draw { 3903sub _draw {
1158 my ($self) = @_; 3904 my ($self) = @_;
1159 3905
3906 my $child = $self->{children}[0];
3907
1160 my ($w, $h ) = ($self->{w}, $self->{h}); 3908 my ($w, $h ) = ($self->{w}, $self->{h});
1161 my ($cw, $ch) = ($self->child->{w}, $self->child->{h}); 3909 my ($cw, $ch) = ($child->{w}, $child->{h});
1162 3910
1163 glEnable GL_TEXTURE_2D; 3911 glEnable GL_TEXTURE_2D;
1164 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE; 3912 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
1165 3913
1166 my $border = $self->border; 3914 my $border = $self->border;
1167 3915
1168 glColor @{ $self->{border_bg} }; 3916 glColor @{ $self->{border_bg} };
1169 $border[0]->draw_quad_alpha (0, 0, $w, $border); 3917 $border[0]->draw_quad_alpha ( 0, 0, $w, $border);
1170 $border[1]->draw_quad_alpha (0, $border, $border, $ch); 3918 $border[1]->draw_quad_alpha ( 0, $border, $border, $ch);
1171 $border[2]->draw_quad_alpha ($w - $border, $border, $border, $ch); 3919 $border[2]->draw_quad_alpha ($w - $border, $border, $border, $ch);
1172 $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);
1173 3935
1174 if (@{$self->{bg}} < 4 || $self->{bg}[3]) { 3936 if (@{$self->{bg}} < 4 || $self->{bg}[3]) {
1175 glColor @{ $self->{bg} }; 3937 glColor @{ $self->{bg} };
1176 3938
1177 # TODO: repeat texture not scale 3939 # TODO: repeat texture not scale
1181 $bg->draw_quad_alpha ($border, $border, $cw, $ch); 3943 $bg->draw_quad_alpha ($border, $border, $cw, $ch);
1182 } 3944 }
1183 3945
1184 glDisable GL_TEXTURE_2D; 3946 glDisable GL_TEXTURE_2D;
1185 3947
1186 $self->{title}->draw if $self->{title};
1187
1188 $self->child->draw; 3948 $child->draw;
3949
3950 if ($self->{title_widget}) {
3951 glTranslate 0, $border - $self->{h};
3952 $self->{title_widget}->_draw;
3953
3954 glTranslate 0, - ($border - $self->{h});
3955 }
3956
3957 $self->{close_button}->draw
3958 if $self->{close_button};
1189} 3959}
1190 3960
1191############################################################################# 3961#############################################################################
1192 3962
1193package CFClient::UI::Table; 3963package CFPlus::UI::Table;
1194 3964
1195our @ISA = CFClient::UI::Base::; 3965our @ISA = CFPlus::UI::Base::;
1196 3966
1197use List::Util qw(max sum); 3967use List::Util qw(max sum);
1198 3968
1199use CFClient::OpenGL; 3969use CFPlus::OpenGL;
1200 3970
1201sub new { 3971sub new {
1202 my $class = shift; 3972 my $class = shift;
1203 3973
1204 $class->SUPER::new ( 3974 $class->SUPER::new (
3975 children => [],
1205 col_expand => [], 3976 col_expand => [],
3977 row_expand => [],
1206 @_, 3978 @_,
1207 ) 3979 )
1208} 3980}
1209 3981
1210sub children { 3982sub children {
1211 grep $_, map @$_, grep $_, @{ $_[0]{children} } 3983 grep $_, map @$_, grep $_, @{ $_[0]{children} }
1212} 3984}
1213 3985
3986# TODO: store row/col info in child widget and use standard add/del
1214sub add { 3987sub add {
1215 my ($self, $x, $y, $child) = @_; 3988 my $self = shift;
1216 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;
3996
3997 while (@_) {
3998 my ($col, $row, $child) = splice @_, 0, 3, ();
3999
1217 $child->set_parent ($self); 4000 $child->set_parent ($self);
1218 $self->{children}[$y][$x] = $child; 4001 $self->{children}[$row][$col] = $child;
4002 }
1219 4003
4004 $self->{force_realloc} = 1;
4005 $self->{force_size_alloc} = 1;
1220 $self->realloc; 4006 $self->realloc;
4007}
4008
4009sub remove {
4010 my ($self, $child) = @_;
4011
4012 for (@{ $self->{children} }) {
4013 for (@{ $_ || [] }) {
4014 $_ = undef if $_ == $child;
4015 }
4016 }
1221} 4017}
1222 4018
1223# 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?
1224sub clear { 4020sub clear {
1225 my ($self) = @_; 4021 my ($self) = @_;
1266 (sum @$ws), 4062 (sum @$ws),
1267 (sum @$hs), 4063 (sum @$hs),
1268 ) 4064 )
1269} 4065}
1270 4066
1271sub size_allocate { 4067sub invoke_size_allocate {
1272 my ($self, $w, $h) = @_; 4068 my ($self, $w, $h) = @_;
1273 4069
1274 my ($ws, $hs) = $self->get_wh; 4070 my ($ws, $hs) = $self->get_wh;
1275 4071
1276 my $req_w = (sum @$ws) || 1; 4072 my $req_w = (sum @$ws) || 1;
1277 my $req_h = (sum @$hs) || 1; 4073 my $req_h = (sum @$hs) || 1;
1278 4074
1279 # TODO: nicer code && do row_expand 4075 # TODO: nicer code
1280 my @col_expand = @{$self->{col_expand}}; 4076 my @col_expand = @{$self->{col_expand}};
1281 @col_expand = (1) x @$ws unless @col_expand; 4077 @col_expand = (1) x @$ws unless @col_expand;
1282 my $col_expand = (sum @col_expand) || 1; 4078 my $col_expand = (sum @col_expand) || 1;
1283 4079
1284 # linearly scale sizes
1285 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws; 4080 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws;
1286 $hs->[$_] *= 1 * $h / $req_h for 0 .. $#$hs;
1287 4081
1288 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
1289 CFClient::UI::harmonize $hs; 4090 CFPlus::UI::harmonize $hs;
1290 4091
1291 my $y; 4092 my $y;
1292 4093
1293 for my $r (0 .. $#{$self->{children}}) { 4094 for my $r (0 .. $#{$self->{children}}) {
1294 my $row = $self->{children}[$r] 4095 my $row = $self->{children}[$r]
1308 } 4109 }
1309 4110
1310 $y += $row_h; 4111 $y += $row_h;
1311 } 4112 }
1312 4113
4114 1
1313} 4115}
1314 4116
1315sub find_widget { 4117sub find_widget {
1316 my ($self, $x, $y) = @_; 4118 my ($self, $x, $y) = @_;
1317 4119
1336 } 4138 }
1337} 4139}
1338 4140
1339############################################################################# 4141#############################################################################
1340 4142
1341package CFClient::UI::Box; 4143package CFPlus::UI::Fixed;
1342 4144
4145use List::Util qw(min max);
4146
1343our @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::;
1344 4228
1345sub size_request { 4229sub size_request {
1346 my ($self) = @_; 4230 my ($self) = @_;
1347 4231
1348 $self->{vertical} 4232 $self->{vertical}
1354 (List::Util::sum map $_->{req_w}, @{$self->{children}}), 4238 (List::Util::sum map $_->{req_w}, @{$self->{children}}),
1355 (List::Util::max map $_->{req_h}, @{$self->{children}}), 4239 (List::Util::max map $_->{req_h}, @{$self->{children}}),
1356 ) 4240 )
1357} 4241}
1358 4242
1359sub size_allocate { 4243sub invoke_size_allocate {
1360 my ($self, $w, $h) = @_; 4244 my ($self, $w, $h) = @_;
1361 4245
1362 my $space = $self->{vertical} ? $h : $w; 4246 my $space = $self->{vertical} ? $h : $w;
1363 my $children = $self->{children}; 4247 my @children = $self->visible_children;
1364 4248
1365 my @req; 4249 my @req;
1366 4250
1367 if ($self->{homogeneous}) { 4251 if ($self->{homogeneous}) {
1368 @req = ($space / (@$children || 1)) x @$children; 4252 @req = ($space / (@children || 1)) x @children;
1369 } else { 4253 } else {
1370 @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @$children; 4254 @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @children;
1371 my $req = List::Util::sum @req; 4255 my $req = List::Util::sum @req;
1372 4256
1373 if ($req > $space) { 4257 if ($req > $space) {
1374 # ah well, not enough space 4258 # ah well, not enough space
1375 $_ *= $space / $req for @req; 4259 $_ *= $space / $req for @req;
1376 } else { 4260 } else {
1377 my $expand = (List::Util::sum map $_->{expand}, @$children) || 1; 4261 my $expand = (List::Util::sum map $_->{expand}, @children) || 1;
1378 4262
1379 $space = ($space - $req) / $expand; # remaining space to give away 4263 $space = ($space - $req) / $expand; # remaining space to give away
1380 4264
1381 $req[$_] += $space * $children->[$_]{expand} 4265 $req[$_] += $space * $children[$_]{expand}
1382 for 0 .. $#$children; 4266 for 0 .. $#children;
1383 } 4267 }
1384 } 4268 }
1385 4269
1386 CFClient::UI::harmonize \@req; 4270 CFPlus::UI::harmonize \@req;
1387 4271
1388 my $pos = 0; 4272 my $pos = 0;
1389 for (0 .. $#$children) { 4273 for (0 .. $#children) {
1390 my $alloc = $req[$_]; 4274 my $alloc = $req[$_];
1391 $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));
1392 4276
1393 $pos += $alloc; 4277 $pos += $alloc;
1394 } 4278 }
1395 4279
1396 1 4280 1
1397} 4281}
1398 4282
1399############################################################################# 4283#############################################################################
1400 4284
1401package CFClient::UI::HBox; 4285package CFPlus::UI::HBox;
1402 4286
1403our @ISA = CFClient::UI::Box::; 4287our @ISA = CFPlus::UI::Box::;
1404 4288
1405sub new { 4289sub new {
1406 my $class = shift; 4290 my $class = shift;
1407 4291
1408 $class->SUPER::new ( 4292 $class->SUPER::new (
1411 ) 4295 )
1412} 4296}
1413 4297
1414############################################################################# 4298#############################################################################
1415 4299
1416package CFClient::UI::VBox; 4300package CFPlus::UI::VBox;
1417 4301
1418our @ISA = CFClient::UI::Box::; 4302our @ISA = CFPlus::UI::Box::;
1419 4303
1420sub new { 4304sub new {
1421 my $class = shift; 4305 my $class = shift;
1422 4306
1423 $class->SUPER::new ( 4307 $class->SUPER::new (
1426 ) 4310 )
1427} 4311}
1428 4312
1429############################################################################# 4313#############################################################################
1430 4314
1431package CFClient::UI::Label; 4315package CFPlus::UI::Label;
1432 4316
1433our @ISA = CFClient::UI::DrawBG::; 4317our @ISA = CFPlus::UI::DrawBG::;
1434 4318
1435use CFClient::OpenGL; 4319use CFPlus::OpenGL;
1436 4320
1437sub new { 4321sub new {
1438 my ($class, %arg) = @_; 4322 my ($class, %arg) = @_;
1439 4323
1440 my $self = $class->SUPER::new ( 4324 my $self = $class->SUPER::new (
1443 #active_bg => none 4327 #active_bg => none
1444 #font => default_font 4328 #font => default_font
1445 #text => initial text 4329 #text => initial text
1446 #markup => initial narkup 4330 #markup => initial narkup
1447 #max_w => maximum pixel width 4331 #max_w => maximum pixel width
4332 #style => 0, # render flags
1448 ellipsise => 3, # end 4333 ellipsise => 3, # end
1449 layout => (new CFClient::Layout), 4334 layout => (new CFPlus::Layout),
1450 fontsize => 1, 4335 fontsize => 1,
1451 align => -1, 4336 align => -1,
1452 valign => -1, 4337 valign => -1,
1453 padding_x => 2, 4338 padding_x => 2,
1454 padding_y => 2, 4339 padding_y => 2,
1455 can_events => 0, 4340 can_events => 0,
1456 %arg 4341 %arg
1457 ); 4342 );
1458 4343
1459 if (exists $self->{template}) { 4344 if (exists $self->{template}) {
1460 my $layout = new CFClient::Layout; 4345 my $layout = new CFPlus::Layout;
1461 $layout->set_text (delete $self->{template}); 4346 $layout->set_text (delete $self->{template});
1462 $self->{template} = $layout; 4347 $self->{template} = $layout;
1463 } 4348 }
1464 4349
1465 if (exists $self->{markup}) { 4350 if (exists $self->{markup}) {
1469 } 4354 }
1470 4355
1471 $self 4356 $self
1472} 4357}
1473 4358
1474sub escape($) {
1475 local $_ = $_[0];
1476
1477 s/&/&amp;/g;
1478 s/>/&gt;/g;
1479 s/</&lt;/g;
1480
1481 $_
1482}
1483
1484sub update { 4359sub update {
1485 my ($self) = @_; 4360 my ($self) = @_;
1486 4361
1487 delete $self->{texture}; 4362 delete $self->{texture};
1488 $self->SUPER::update; 4363 $self->SUPER::update;
1489} 4364}
1490 4365
4366sub realloc {
4367 my ($self) = @_;
4368
4369 delete $self->{ox};
4370 $self->SUPER::realloc;
4371}
4372
1491sub set_text { 4373sub set_text {
1492 my ($self, $text) = @_; 4374 my ($self, $text) = @_;
1493 4375
1494 return if $self->{text} eq "T$text"; 4376 return if $self->{text} eq "T$text";
1495 $self->{text} = "T$text"; 4377 $self->{text} = "T$text";
1496 4378
1497 $self->{layout} = new CFClient::Layout if $self->{layout}->is_rgba;
1498 $self->{layout}->set_text ($text); 4379 $self->{layout}->set_text ($text);
1499 4380
4381 delete $self->{size_req};
1500 $self->realloc; 4382 $self->realloc;
1501 $self->update; 4383 $self->update;
1502} 4384}
1503 4385
1504sub set_markup { 4386sub set_markup {
1507 return if $self->{text} eq "M$markup"; 4389 return if $self->{text} eq "M$markup";
1508 $self->{text} = "M$markup"; 4390 $self->{text} = "M$markup";
1509 4391
1510 my $rgba = $markup =~ /span.*(?:foreground|background)/; 4392 my $rgba = $markup =~ /span.*(?:foreground|background)/;
1511 4393
1512 $self->{layout} = new CFClient::Layout $rgba if $self->{layout}->is_rgba != $rgba;
1513 $self->{layout}->set_markup ($markup); 4394 $self->{layout}->set_markup ($markup);
1514 4395
4396 delete $self->{size_req};
1515 $self->realloc; 4397 $self->realloc;
1516 $self->update; 4398 $self->update;
1517} 4399}
1518 4400
1519sub size_request { 4401sub size_request {
1520 my ($self) = @_; 4402 my ($self) = @_;
1521 4403
4404 $self->{size_req} ||= do {
1522 $self->{layout}->set_font ($self->{font}) if $self->{font}; 4405 $self->{layout}->set_font ($self->{font}) if $self->{font};
1523 $self->{layout}->set_width ($self->{max_w} || -1); 4406 $self->{layout}->set_width ($self->{max_w} || -1);
1524 $self->{layout}->set_ellipsise ($self->{ellipsise}); 4407 $self->{layout}->set_ellipsise ($self->{ellipsise});
1525 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise}); 4408 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1526 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 4409 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1527 4410
1528 my ($w, $h) = $self->{layout}->size; 4411 my ($w, $h) = $self->{layout}->size;
1529 4412
1530 if (exists $self->{template}) { 4413 if (exists $self->{template}) {
1531 $self->{template}->set_font ($self->{font}) if $self->{font}; 4414 $self->{template}->set_font ($self->{font}) if $self->{font};
4415 $self->{template}->set_width ($self->{max_w} || -1);
1532 $self->{template}->set_height ($self->{fontsize} * $::FONTSIZE); 4416 $self->{template}->set_height ($self->{fontsize} * $::FONTSIZE);
1533 4417
1534 my ($w2, $h2) = $self->{template}->size; 4418 my ($w2, $h2) = $self->{template}->size;
1535 4419
1536 $w = List::Util::max $w, $w2; 4420 $w = List::Util::max $w, $w2;
1537 $h = List::Util::max $h, $h2; 4421 $h = List::Util::max $h, $h2;
4422 }
4423
4424 [$w, $h]
1538 } 4425 };
1539 4426
1540 ($w, $h) 4427 @{ $self->{size_req} }
1541} 4428}
1542 4429
4430sub baseline_shift {
4431 $_[0]{layout}->descent
4432}
4433
1543sub size_allocate { 4434sub invoke_size_allocate {
1544 my ($self, $w, $h) = @_; 4435 my ($self, $w, $h) = @_;
4436
4437 delete $self->{ox};
1545 4438
1546 delete $self->{texture} 4439 delete $self->{texture}
1547 unless $w >= $self->{req_w} && $self->{old_w} >= $self->{req_w}; 4440 unless $w >= $self->{req_w} && $self->{old_w} >= $self->{req_w};
4441
4442 1
1548} 4443}
1549 4444
1550sub set_fontsize { 4445sub set_fontsize {
1551 my ($self, $fontsize) = @_; 4446 my ($self, $fontsize) = @_;
1552 4447
1553 $self->{fontsize} = $fontsize; 4448 $self->{fontsize} = $fontsize;
4449 delete $self->{size_req};
1554 delete $self->{texture}; 4450 delete $self->{texture};
1555 4451
1556 $self->realloc; 4452 $self->realloc;
1557} 4453}
1558 4454
4455sub reconfigure {
4456 my ($self) = @_;
4457
4458 delete $self->{size_req};
4459 delete $self->{texture};
4460
4461 $self->SUPER::reconfigure;
4462}
4463
1559sub _draw { 4464sub _draw {
1560 my ($self) = @_; 4465 my ($self) = @_;
1561 4466
1562 $self->SUPER::_draw; # draw background, if applicable 4467 $self->SUPER::_draw; # draw background, if applicable
1563 4468
1564 my $tex = $self->{texture} ||= do { 4469 my $size = $self->{texture} ||= do {
1565 $self->{layout}->set_foreground (@{$self->{fg}}); 4470 $self->{layout}->set_foreground (@{$self->{fg}});
1566 $self->{layout}->set_font ($self->{font}) if $self->{font}; 4471 $self->{layout}->set_font ($self->{font}) if $self->{font};
1567 $self->{layout}->set_width ($self->{w}); 4472 $self->{layout}->set_width ($self->{w});
1568 $self->{layout}->set_ellipsise ($self->{ellipsise}); 4473 $self->{layout}->set_ellipsise ($self->{ellipsise});
1569 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise}); 4474 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1570 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 4475 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1571 4476
1572 my $tex = new_from_layout CFClient::Texture $self->{layout}; 4477 [$self->{layout}->size]
4478 };
1573 4479
4480 unless (exists $self->{ox}) {
1574 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x} 4481 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x}
1575 : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding_x} 4482 : $self->{align} > 0 ? $self->{w} - $size->[0] - $self->{padding_x}
1576 : ($self->{w} - $tex->{w}) * 0.5); 4483 : ($self->{w} - $size->[0]) * 0.5);
1577 4484
1578 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y} 4485 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y}
1579 : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding_y} 4486 : $self->{valign} > 0 ? $self->{h} - $size->[1] - $self->{padding_y}
1580 : ($self->{h} - $tex->{h}) * 0.5); 4487 : ($self->{h} - $size->[1]) * 0.5);
1581
1582 $tex
1583 }; 4488 };
1584 4489
1585 glEnable GL_TEXTURE_2D; 4490 my $w = List::Util::min $self->{w} + 4, $size->[0];
1586 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 4491 my $h = List::Util::min $self->{h} + 2, $size->[1];
1587 4492
1588 if ($tex->{format} == GL_ALPHA) { 4493 $self->{layout}->render ($self->{ox}, $self->{oy}, $self->{style});
1589 glColor @{$self->{fg}};
1590 $tex->draw_quad_alpha ($self->{ox}, $self->{oy});
1591 } else {
1592 $tex->draw_quad_alpha_premultiplied ($self->{ox}, $self->{oy});
1593 }
1594
1595 glDisable GL_TEXTURE_2D;
1596} 4494}
1597 4495
1598############################################################################# 4496#############################################################################
1599 4497
1600package CFClient::UI::EntryBase; 4498package CFPlus::UI::EntryBase;
1601 4499
1602our @ISA = CFClient::UI::Label::; 4500our @ISA = CFPlus::UI::Label::;
1603 4501
1604use CFClient::OpenGL; 4502use CFPlus::OpenGL;
1605 4503
1606sub new { 4504sub new {
1607 my $class = shift; 4505 my $class = shift;
1608 4506
1609 $class->SUPER::new ( 4507 $class->SUPER::new (
1613 active_fg => [0, 0, 0], 4511 active_fg => [0, 0, 0],
1614 can_hover => 1, 4512 can_hover => 1,
1615 can_focus => 1, 4513 can_focus => 1,
1616 valign => 0, 4514 valign => 0,
1617 can_events => 1, 4515 can_events => 1,
4516 ellipsise => 0,
1618 #text => ... 4517 #text => ...
4518 #hidden => "*",
1619 @_ 4519 @_
1620 ) 4520 )
1621} 4521}
1622 4522
1623sub _set_text { 4523sub _set_text {
1625 4525
1626 delete $self->{cur_h}; 4526 delete $self->{cur_h};
1627 4527
1628 return if $self->{text} eq $text; 4528 return if $self->{text} eq $text;
1629 4529
1630 delete $self->{texture};
1631
1632 $self->{last_activity} = $::NOW; 4530 $self->{last_activity} = $::NOW;
1633 $self->{text} = $text; 4531 $self->{text} = $text;
1634 4532
1635 $text =~ s/./*/g if $self->{hidden}; 4533 $text =~ s/./*/g if $self->{hidden};
1636 $self->{layout}->set_text ("$text "); 4534 $self->{layout}->set_text ("$text ");
4535 delete $self->{size_req};
1637 4536
1638 $self->_emit (changed => $self->{text}); 4537 $self->emit (changed => $self->{text});
4538
4539 $self->realloc;
4540 $self->update;
1639} 4541}
1640 4542
1641sub set_text { 4543sub set_text {
1642 my ($self, $text) = @_; 4544 my ($self, $text) = @_;
1643 4545
1644 $self->{cursor} = length $text; 4546 $self->{cursor} = length $text;
1645 $self->_set_text ($text); 4547 $self->_set_text ($text);
1646
1647 $self->realloc;
1648} 4548}
1649 4549
1650sub get_text { 4550sub get_text {
1651 $_[0]{text} 4551 $_[0]{text}
1652} 4552}
1657 my ($w, $h) = $self->SUPER::size_request; 4557 my ($w, $h) = $self->SUPER::size_request;
1658 4558
1659 ($w + 1, $h) # add 1 for cursor 4559 ($w + 1, $h) # add 1 for cursor
1660} 4560}
1661 4561
1662sub key_down { 4562sub invoke_key_down {
1663 my ($self, $ev) = @_; 4563 my ($self, $ev) = @_;
1664 4564
1665 my $mod = $ev->{mod}; 4565 my $mod = $ev->{mod};
1666 my $sym = $ev->{sym}; 4566 my $sym = $ev->{sym};
1667 my $uni = $ev->{unicode}; 4567 my $uni = $ev->{unicode};
1668 4568
1669 my $text = $self->get_text; 4569 my $text = $self->get_text;
4570
4571 $self->{cursor} = List::Util::max 0, List::Util::min $self->{cursor}, length $text;
1670 4572
1671 if ($uni == 8) { 4573 if ($uni == 8) {
1672 substr $text, --$self->{cursor}, 1, "" if $self->{cursor}; 4574 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1673 } elsif ($uni == 127) { 4575 } elsif ($uni == 127) {
1674 substr $text, $self->{cursor}, 1, ""; 4576 substr $text, $self->{cursor}, 1, "";
1675 } elsif ($sym == CFClient::SDLK_LEFT) { 4577 } elsif ($sym == CFPlus::SDLK_LEFT) {
1676 --$self->{cursor} if $self->{cursor}; 4578 --$self->{cursor} if $self->{cursor};
1677 } elsif ($sym == CFClient::SDLK_RIGHT) { 4579 } elsif ($sym == CFPlus::SDLK_RIGHT) {
1678 ++$self->{cursor} if $self->{cursor} < length $self->{text}; 4580 ++$self->{cursor} if $self->{cursor} < length $self->{text};
1679 } 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 = "";
1680 $self->{cursor} = 0; 4595 $self->{cursor} = 0;
1681 } elsif ($sym == CFClient::SDLK_END) {
1682 $self->{cursor} = length $text;
1683 } elsif ($uni == 27) { 4596 } elsif ($uni == 27) {
1684 $self->_emit ('escape'); 4597 $self->emit ('escape');
1685 } elsif ($uni) { 4598 } elsif ($uni == 0x0d) {
4599 substr $text, $self->{cursor}++, 0, "\012";
4600 } elsif ($uni >= 0x20) {
1686 substr $text, $self->{cursor}++, 0, chr $uni; 4601 substr $text, $self->{cursor}++, 0, chr $uni;
4602 } else {
4603 return 0;
1687 } 4604 }
1688 4605
1689 $self->_set_text ($text); 4606 $self->_set_text ($text);
1690 4607
1691 $self->realloc; 4608 $self->realloc;
1692} 4609 $self->update;
1693 4610
4611 1
4612}
4613
1694sub focus_in { 4614sub invoke_focus_in {
1695 my ($self) = @_; 4615 my ($self) = @_;
1696 4616
1697 $self->{last_activity} = $::NOW; 4617 $self->{last_activity} = $::NOW;
1698 4618
1699 $self->SUPER::focus_in; 4619 $self->SUPER::invoke_focus_in
1700} 4620}
1701 4621
1702sub button_down { 4622sub invoke_button_down {
1703 my ($self, $ev, $x, $y) = @_; 4623 my ($self, $ev, $x, $y) = @_;
1704 4624
1705 $self->SUPER::button_down ($ev, $x, $y); 4625 $self->SUPER::invoke_button_down ($ev, $x, $y);
1706 4626
1707 my $idx = $self->{layout}->xy_to_index ($x, $y); 4627 my $idx = $self->{layout}->xy_to_index ($x, $y);
1708 4628
1709 # byte-index to char-index 4629 # byte-index to char-index
1710 my $text = $self->{text}; 4630 my $text = $self->{text};
1711 utf8::encode $text; 4631 utf8::encode $text; $text = substr $text, 0, $idx; utf8::decode $text;
1712 $self->{cursor} = length substr $text, 0, $idx; 4632 $self->{cursor} = length $text;
1713 4633
1714 $self->_set_text ($self->{text}); 4634 $self->_set_text ($self->{text});
1715 $self->update; 4635 $self->update;
4636
4637 1
1716} 4638}
1717 4639
1718sub mouse_motion { 4640sub invoke_mouse_motion {
1719 my ($self, $ev, $x, $y) = @_; 4641 my ($self, $ev, $x, $y) = @_;
1720# printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d# 4642# printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d#
4643
4644 1
1721} 4645}
1722 4646
1723sub _draw { 4647sub _draw {
1724 my ($self) = @_; 4648 my ($self) = @_;
1725 4649
1726 local $self->{fg} = $self->{fg}; 4650 local $self->{fg} = $self->{fg};
1727 4651
1728 if ($FOCUS == $self) { 4652 if ($FOCUS == $self) {
1729 glColor @{$self->{active_bg}}; 4653 glColor_premultiply @{$self->{active_bg}};
1730 $self->{fg} = $self->{active_fg}; 4654 $self->{fg} = $self->{active_fg};
1731 } else { 4655 } else {
1732 glColor @{$self->{bg}}; 4656 glColor_premultiply @{$self->{bg}};
1733 } 4657 }
1734 4658
1735 glEnable GL_BLEND; 4659 glEnable GL_BLEND;
1736 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 4660 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
1737 glBegin GL_QUADS; 4661 glBegin GL_QUADS;
1738 glVertex 0 , 0; 4662 glVertex 0 , 0;
1739 glVertex 0 , $self->{h}; 4663 glVertex 0 , $self->{h};
1740 glVertex $self->{w}, $self->{h}; 4664 glVertex $self->{w}, $self->{h};
1741 glVertex $self->{w}, 0; 4665 glVertex $self->{w}, 0;
1752 utf8::encode $text; 4676 utf8::encode $text;
1753 4677
1754 @$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)
1755 } 4679 }
1756 4680
1757 glColor @{$self->{fg}};
1758 glBegin GL_LINES; 4681 glBegin GL_LINES;
1759 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};
1760 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};
1761 glEnd; 4684 glEnd;
1762 } 4685 }
1763} 4686}
1764 4687
4688#############################################################################
4689
1765package CFClient::UI::Entry; 4690package CFPlus::UI::Entry;
1766 4691
1767our @ISA = CFClient::UI::EntryBase::; 4692our @ISA = CFPlus::UI::EntryBase::;
1768 4693
1769use CFClient::OpenGL; 4694use CFPlus::OpenGL;
1770 4695
1771sub key_down { 4696sub invoke_key_down {
1772 my ($self, $ev) = @_; 4697 my ($self, $ev) = @_;
1773 4698
1774 my $sym = $ev->{sym}; 4699 my $sym = $ev->{sym};
1775 4700
1776 if ($sym == 13) { 4701 if ($ev->{uni} == 0x0d || $sym == 13) {
1777 unshift @{$self->{history}}, 4702 unshift @{$self->{history}},
1778 my $txt = $self->get_text; 4703 my $txt = $self->get_text;
4704
1779 $self->{history_pointer} = -1; 4705 $self->{history_pointer} = -1;
1780 $self->{history_saveback} = ''; 4706 $self->{history_saveback} = '';
1781 $self->_emit (activate => $txt); 4707 $self->emit (activate => $txt);
1782 $self->update; 4708 $self->update;
1783 4709
1784 } elsif ($sym == CFClient::SDLK_UP) { 4710 } elsif ($sym == CFPlus::SDLK_UP) {
1785 if ($self->{history_pointer} < 0) { 4711 if ($self->{history_pointer} < 0) {
1786 $self->{history_saveback} = $self->get_text; 4712 $self->{history_saveback} = $self->get_text;
1787 } 4713 }
1788 if (@{$self->{history} || []} > 0) { 4714 if (@{$self->{history} || []} > 0) {
1789 $self->{history_pointer}++; 4715 $self->{history_pointer}++;
1791 $self->{history_pointer} = @{$self->{history} || []} - 1; 4717 $self->{history_pointer} = @{$self->{history} || []} - 1;
1792 } 4718 }
1793 $self->set_text ($self->{history}->[$self->{history_pointer}]); 4719 $self->set_text ($self->{history}->[$self->{history_pointer}]);
1794 } 4720 }
1795 4721
1796 } elsif ($sym == CFClient::SDLK_DOWN) { 4722 } elsif ($sym == CFPlus::SDLK_DOWN) {
1797 $self->{history_pointer}--; 4723 $self->{history_pointer}--;
1798 $self->{history_pointer} = -1 if $self->{history_pointer} < 0; 4724 $self->{history_pointer} = -1 if $self->{history_pointer} < 0;
1799 4725
1800 if ($self->{history_pointer} >= 0) { 4726 if ($self->{history_pointer} >= 0) {
1801 $self->set_text ($self->{history}->[$self->{history_pointer}]); 4727 $self->set_text ($self->{history}->[$self->{history_pointer}]);
1802 } else { 4728 } else {
1803 $self->set_text ($self->{history_saveback}); 4729 $self->set_text ($self->{history_saveback});
1804 } 4730 }
1805 4731
1806 } else { 4732 } else {
1807 $self->SUPER::key_down ($ev); 4733 return $self->SUPER::invoke_key_down ($ev)
4734 }
4735
1808 } 4736 1
1809
1810} 4737}
1811 4738
1812############################################################################# 4739#############################################################################
1813 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
1814package CFClient::UI::Button; 4780package CFPlus::UI::Button;
1815 4781
1816our @ISA = CFClient::UI::Label::; 4782our @ISA = CFPlus::UI::Label::;
1817 4783
1818use CFClient::OpenGL; 4784use CFPlus::OpenGL;
1819 4785
1820my @tex = 4786my @tex =
1821 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 4787 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1822 qw(b1_button_active.png); 4788 qw(b1_button_inactive.png b1_button_active.png);
1823 4789
1824sub new { 4790sub new {
1825 my $class = shift; 4791 my $class = shift;
1826 4792
1827 $class->SUPER::new ( 4793 $class->SUPER::new (
1828 padding_x => 4, 4794 padding_x => 4,
1829 padding_y => 4, 4795 padding_y => 4,
1830 fg => [1, 1, 1], 4796 fg => [1.0, 1.0, 1.0],
1831 active_fg => [0, 0, 1], 4797 active_fg => [0.8, 0.8, 0.8],
1832 can_hover => 1, 4798 can_hover => 1,
1833 align => 0, 4799 align => 0,
1834 valign => 0, 4800 valign => 0,
1835 can_events => 1, 4801 can_events => 1,
1836 @_ 4802 @_
1837 ) 4803 )
1838} 4804}
1839 4805
1840sub activate { }
1841
1842sub button_up { 4806sub invoke_button_up {
1843 my ($self, $ev, $x, $y) = @_; 4807 my ($self, $ev, $x, $y) = @_;
1844 4808
1845 $self->emit ("activate") 4809 $self->emit ("activate")
1846 if $x >= 0 && $x < $self->{w} 4810 if $x >= 0 && $x < $self->{w}
1847 && $y >= 0 && $y < $self->{h}; 4811 && $y >= 0 && $y < $self->{h};
4812
4813 1
1848} 4814}
1849 4815
1850sub _draw { 4816sub _draw {
1851 my ($self) = @_; 4817 my ($self) = @_;
1852 4818
1853 local $self->{fg} = $self->{fg}; 4819 local $self->{fg} = $GRAB == $self ? $self->{active_fg} : $self->{fg};
1854
1855 if ($GRAB == $self) {
1856 $self->{fg} = $self->{active_fg};
1857 }
1858 4820
1859 glEnable GL_TEXTURE_2D; 4821 glEnable GL_TEXTURE_2D;
1860 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 4822 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1861 glColor 0, 0, 0, 1; 4823 glColor 0, 0, 0, 1;
1862 4824
4825 my $tex = $tex[$GRAB == $self];
1863 $tex[0]->draw_quad_alpha (0, 0, $self->{w}, $self->{h}); 4826 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
1864 4827
1865 glDisable GL_TEXTURE_2D; 4828 glDisable GL_TEXTURE_2D;
1866 4829
1867 $self->SUPER::_draw; 4830 $self->SUPER::_draw;
1868} 4831}
1869 4832
1870############################################################################# 4833#############################################################################
1871 4834
1872package CFClient::UI::CheckBox; 4835package CFPlus::UI::CheckBox;
1873 4836
1874our @ISA = CFClient::UI::DrawBG::; 4837our @ISA = CFPlus::UI::DrawBG::;
1875 4838
1876my @tex = 4839my @tex =
1877 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 4840 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1878 qw(c1_checkbox_bg.png c1_checkbox_active.png); 4841 qw(c1_checkbox_bg.png c1_checkbox_active.png);
1879 4842
1880use CFClient::OpenGL; 4843use CFPlus::OpenGL;
1881 4844
1882sub new { 4845sub new {
1883 my $class = shift; 4846 my $class = shift;
1884 4847
1885 $class->SUPER::new ( 4848 $class->SUPER::new (
1899 my ($self) = @_; 4862 my ($self) = @_;
1900 4863
1901 (6) x 2 4864 (6) x 2
1902} 4865}
1903 4866
4867sub toggle {
4868 my ($self) = @_;
4869
4870 $self->{state} = !$self->{state};
4871 $self->emit (changed => $self->{state});
4872 $self->update;
4873}
4874
1904sub button_down { 4875sub invoke_button_down {
1905 my ($self, $ev, $x, $y) = @_; 4876 my ($self, $ev, $x, $y) = @_;
1906 4877
1907 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x} 4878 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
1908 && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) { 4879 && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
1909 $self->{state} = !$self->{state}; 4880 $self->toggle;
1910 $self->_emit (changed => $self->{state}); 4881 } else {
4882 return 0
4883 }
4884
1911 } 4885 1
1912} 4886}
1913 4887
1914sub _draw { 4888sub _draw {
1915 my ($self) = @_; 4889 my ($self) = @_;
1916 4890
1931 glDisable GL_TEXTURE_2D; 4905 glDisable GL_TEXTURE_2D;
1932} 4906}
1933 4907
1934############################################################################# 4908#############################################################################
1935 4909
1936package CFClient::UI::Image; 4910package CFPlus::UI::Image;
1937 4911
1938our @ISA = CFClient::UI::Base::; 4912our @ISA = CFPlus::UI::Base::;
1939 4913
1940use CFClient::OpenGL; 4914use CFPlus::OpenGL;
1941use Carp qw/confess/;
1942 4915
1943our %loaded_images; 4916our %texture_cache;
1944 4917
1945sub new { 4918sub new {
1946 my $class = shift; 4919 my $class = shift;
1947 4920
1948 my $self = $class->SUPER::new (can_events => 0, @_); 4921 my $self = $class->SUPER::new (
4922 can_events => 0,
4923 @_,
4924 );
1949 4925
1950 $self->{image} or confess "Image has 'image' not set. This is a fatal error!"; 4926 $self->{path} || $self->{tex}
4927 or Carp::croak "'path' or 'tex' attributes required";
1951 4928
1952 $loaded_images{$self->{image}} ||= 4929 $self->{tex} ||= $texture_cache{$self->{path}} ||=
1953 new_from_file CFClient::Texture CFClient::find_rcfile $self->{image}, mipmap => 1; 4930 new_from_file CFPlus::Texture CFPlus::find_rcfile $self->{path}, mipmap => 1;
1954 4931
1955 my $tex = $self->{tex} = $loaded_images{$self->{image}}; 4932 CFPlus::weaken $texture_cache{$self->{path}};
1956 4933
1957 Scalar::Util::weaken $loaded_images{$self->{image}}; 4934 $self->{aspect} ||= $self->{tex}{w} / $self->{tex}{h};
1958
1959 $self->{aspect} = $tex->{w} / $tex->{h};
1960 4935
1961 $self 4936 $self
1962} 4937}
1963 4938
4939sub STORABLE_freeze {
4940 my ($self, $cloning) = @_;
4941
4942 $self->{path}
4943 or die "cannot serialise CFPlus::UI::Image on non-loadable images\n";
4944
4945 $self->{path}
4946}
4947
4948sub STORABLE_attach {
4949 my ($self, $cloning, $path) = @_;
4950
4951 $self->new (path => $path)
4952}
4953
1964sub size_request { 4954sub size_request {
1965 my ($self) = @_; 4955 my ($self) = @_;
1966 4956
1967 ($self->{tex}->{w}, $self->{tex}->{h}) 4957 ($self->{tex}{w}, $self->{tex}{h})
1968} 4958}
1969 4959
1970sub _draw { 4960sub _draw {
1971 my ($self) = @_; 4961 my ($self) = @_;
1972 4962
1982 } 4972 }
1983 4973
1984 glEnable GL_TEXTURE_2D; 4974 glEnable GL_TEXTURE_2D;
1985 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 4975 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1986 4976
1987 $tex->draw_quad_alpha (0, 0, $w, $h); 4977 $tex->draw_quad (0, 0, $w, $h);
1988 4978
1989 glDisable GL_TEXTURE_2D; 4979 glDisable GL_TEXTURE_2D;
1990} 4980}
1991 4981
1992############################################################################# 4982#############################################################################
1993 4983
4984package CFPlus::UI::ImageButton;
4985
4986our @ISA = CFPlus::UI::Image::;
4987
4988use CFPlus::OpenGL;
4989
4990my %textures;
4991
4992sub new {
4993 my $class = shift;
4994
4995 my $self = $class->SUPER::new (
4996 padding_x => 4,
4997 padding_y => 4,
4998 fg => [1, 1, 1],
4999 active_fg => [0, 0, 1],
5000 can_hover => 1,
5001 align => 0,
5002 valign => 0,
5003 can_events => 1,
5004 @_
5005 );
5006}
5007
5008sub invoke_button_up {
5009 my ($self, $ev, $x, $y) = @_;
5010
5011 $self->emit ("activate")
5012 if $x >= 0 && $x < $self->{w}
5013 && $y >= 0 && $y < $self->{h};
5014
5015 1
5016}
5017
5018#############################################################################
5019
1994package CFClient::UI::VGauge; 5020package CFPlus::UI::VGauge;
1995 5021
1996our @ISA = CFClient::UI::Base::; 5022our @ISA = CFPlus::UI::Base::;
1997 5023
1998use List::Util qw(min max); 5024use List::Util qw(min max);
1999 5025
2000use CFClient::OpenGL; 5026use CFPlus::OpenGL;
2001 5027
2002my %tex = ( 5028my %tex = (
2003 food => [ 5029 food => [
2004 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 5030 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
2005 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/ 5031 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/
2006 ], 5032 ],
2007 grace => [ 5033 grace => [
2008 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 5034 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
2009 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/
2010 ], 5036 ],
2011 hp => [ 5037 hp => [
2012 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 5038 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
2013 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/ 5039 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/
2014 ], 5040 ],
2015 mana => [ 5041 mana => [
2016 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 5042 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
2017 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/
2018 ], 5044 ],
2019); 5045);
2020 5046
2021# eg. VGauge->new (gauge => 'food'), default gauge: food 5047# eg. VGauge->new (gauge => 'food'), default gauge: food
2081 my $ycut1 = max 0, min 1, $ycut; 5107 my $ycut1 = max 0, min 1, $ycut;
2082 my $ycut2 = max 0, min 1, $ycut - 1; 5108 my $ycut2 = max 0, min 1, $ycut - 1;
2083 5109
2084 my $h1 = $self->{h} * (1 - $ycut1); 5110 my $h1 = $self->{h} * (1 - $ycut1);
2085 my $h2 = $self->{h} * (1 - $ycut2); 5111 my $h2 = $self->{h} * (1 - $ycut2);
5112 my $h3 = $self->{h};
5113
5114 $_ = $_ * (284-4)/288 + 4/288 for ($h1, $h2, $h3);
2086 5115
2087 glEnable GL_BLEND; 5116 glEnable GL_BLEND;
2088 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 5117 glBlendFuncSeparate GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
5118 GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2089 glEnable GL_TEXTURE_2D; 5119 glEnable GL_TEXTURE_2D;
2090 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 5120 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2091 5121
2092 glBindTexture GL_TEXTURE_2D, $t1->{name}; 5122 glBindTexture GL_TEXTURE_2D, $t1->{name};
2093 glBegin GL_QUADS; 5123 glBegin GL_QUADS;
2108 5138
2109 if ($t3) { 5139 if ($t3) {
2110 glBindTexture GL_TEXTURE_2D, $t3->{name}; 5140 glBindTexture GL_TEXTURE_2D, $t3->{name};
2111 glBegin GL_QUADS; 5141 glBegin GL_QUADS;
2112 glTexCoord 0 , $t3->{t} * (1 - $ycut2); glVertex 0 , $h2; 5142 glTexCoord 0 , $t3->{t} * (1 - $ycut2); glVertex 0 , $h2;
2113 glTexCoord 0 , $t3->{t}; glVertex 0 , $self->{h}; 5143 glTexCoord 0 , $t3->{t}; glVertex 0 , $h3;
2114 glTexCoord $t3->{s}, $t3->{t}; glVertex $w, $self->{h}; 5144 glTexCoord $t3->{s}, $t3->{t}; glVertex $w, $h3;
2115 glTexCoord $t3->{s}, $t3->{t} * (1 - $ycut2); glVertex $w, $h2; 5145 glTexCoord $t3->{s}, $t3->{t} * (1 - $ycut2); glVertex $w, $h2;
2116 glEnd; 5146 glEnd;
2117 } 5147 }
2118 5148
2119 glDisable GL_BLEND; 5149 glDisable GL_BLEND;
2120 glDisable GL_TEXTURE_2D; 5150 glDisable GL_TEXTURE_2D;
2121} 5151}
2122 5152
2123############################################################################# 5153#############################################################################
2124 5154
2125package CFClient::UI::Gauge; 5155package CFPlus::UI::Gauge;
2126 5156
2127our @ISA = CFClient::UI::VBox::; 5157our @ISA = CFPlus::UI::VBox::;
2128 5158
2129sub new { 5159sub new {
2130 my ($class, %arg) = @_; 5160 my ($class, %arg) = @_;
2131 5161
2132 my $self = $class->SUPER::new ( 5162 my $self = $class->SUPER::new (
2134 can_hover => 1, 5164 can_hover => 1,
2135 can_events => 1, 5165 can_events => 1,
2136 %arg, 5166 %arg,
2137 ); 5167 );
2138 5168
2139 $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");
2140 $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);
2141 $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");
2142 5172
2143 $self 5173 $self
2144} 5174}
2145 5175
2146sub set_fontsize { 5176sub set_fontsize {
2167 $self->{value}->set_text ($val); 5197 $self->{value}->set_text ($val);
2168} 5198}
2169 5199
2170############################################################################# 5200#############################################################################
2171 5201
2172package CFClient::UI::Slider; 5202package CFPlus::UI::Slider;
2173 5203
2174use strict; 5204use strict;
2175 5205
2176use CFClient::OpenGL; 5206use CFPlus::OpenGL;
2177 5207
2178our @ISA = CFClient::UI::DrawBG::; 5208our @ISA = CFPlus::UI::DrawBG::;
2179 5209
2180my @tex = 5210my @tex =
2181 map { new_from_file CFClient::Texture CFClient::find_rcfile $_ } 5211 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_ }
2182 qw(s1_slider.png s1_slider_bg.png); 5212 qw(s1_slider.png s1_slider_bg.png);
2183 5213
2184sub new { 5214sub new {
2185 my $class = shift; 5215 my $class = shift;
2186 5216
2207 $self->update; 5237 $self->update;
2208 5238
2209 $self 5239 $self
2210} 5240}
2211 5241
2212sub changed { }
2213
2214sub set_range { 5242sub set_range {
2215 my ($self, $range) = @_; 5243 my ($self, $range) = @_;
2216 5244
2217 ($range, $self->{range}) = ($self->{range}, $range); 5245 ($range, $self->{range}) = ($self->{range}, $range);
2218 5246
2219 $self->update
2220 if "@$range" ne "@{$self->{range}}"; 5247 if ("@$range" ne "@{$self->{range}}") {
5248 $self->update;
5249 $self->set_value ($self->{range}[0]);
5250 }
2221} 5251}
2222 5252
2223sub set_value { 5253sub set_value {
2224 my ($self, $value) = @_; 5254 my ($self, $value) = @_;
2225 5255
2236 if $unit; 5266 if $unit;
2237 5267
2238 @{$self->{range}} = ($value, $lo, $hi, $page, $unit); 5268 @{$self->{range}} = ($value, $lo, $hi, $page, $unit);
2239 5269
2240 if ($value != $old_value) { 5270 if ($value != $old_value) {
2241 $self->_emit (changed => $value); 5271 $self->emit (changed => $value);
2242 $self->update; 5272 $self->update;
2243 } 5273 }
2244} 5274}
2245 5275
2246sub size_request { 5276sub size_request {
2247 my ($self) = @_; 5277 my ($self) = @_;
2248 5278
2249 ($self->{req_w}, $self->{req_h}) 5279 ($self->{req_w}, $self->{req_h})
2250} 5280}
2251 5281
2252sub button_down { 5282sub invoke_button_down {
2253 my ($self, $ev, $x, $y) = @_; 5283 my ($self, $ev, $x, $y) = @_;
2254 5284
2255 $self->SUPER::button_down ($ev, $x, $y); 5285 $self->SUPER::invoke_button_down ($ev, $x, $y);
2256 5286
2257 $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x]; 5287 $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x];
2258 5288
2259 $self->mouse_motion ($ev, $x, $y); 5289 $self->invoke_mouse_motion ($ev, $x, $y)
2260} 5290}
2261 5291
2262sub mouse_motion { 5292sub invoke_mouse_motion {
2263 my ($self, $ev, $x, $y) = @_; 5293 my ($self, $ev, $x, $y) = @_;
2264 5294
2265 if ($GRAB == $self) { 5295 if ($GRAB == $self) {
2266 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w}); 5296 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w});
2267 5297
2268 my (undef, $lo, $hi, $page) = @{$self->{range}}; 5298 my (undef, $lo, $hi, $page) = @{$self->{range}};
2269 5299
2270 $x = ($x - $self->{click}[1]) / ($w * $self->{scale}); 5300 $x = ($x - $self->{click}[1]) / ($w * $self->{scale});
2271 5301
2272 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo)); 5302 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo));
5303 } else {
5304 return 0;
5305 }
5306
2273 } 5307 1
5308}
5309
5310sub invoke_mouse_wheel {
5311 my ($self, $ev) = @_;
5312
5313 my $delta = $self->{vertical} ? $ev->{dy} : $ev->{dx};
5314
5315 my $pagepart = $ev->{mod} & CFPlus::KMOD_SHIFT ? 1 : 0.2;
5316
5317 $self->set_value ($self->{range}[0] + $delta * $self->{range}[3] * $pagepart);
5318
5319 ! ! $delta
2274} 5320}
2275 5321
2276sub update { 5322sub update {
2277 my ($self) = @_; 5323 my ($self) = @_;
2278 5324
2279 $CFClient::UI::ROOT->on_post_alloc ($self => sub { 5325 delete $self->{knob_w};
5326 $self->SUPER::update;
5327}
5328
5329sub _draw {
5330 my ($self) = @_;
5331
5332 unless ($self->{knob_w}) {
2280 $self->set_value ($self->{range}[0]); 5333 $self->set_value ($self->{range}[0]);
2281 5334
2282 my ($value, $lo, $hi, $page) = @{$self->{range}}; 5335 my ($value, $lo, $hi, $page) = @{$self->{range}};
2283 my $range = ($hi - $page - $lo) || 1e-100; 5336 my $range = ($hi - $page - $lo) || 1e-100;
2284 5337
2290 $value = ($value - $lo) / $range; 5343 $value = ($value - $lo) / $range;
2291 $value = $value * $self->{scale} + $self->{offset}; 5344 $value = $value * $self->{scale} + $self->{offset};
2292 5345
2293 $self->{knob_x} = $value - $knob_w * 0.5; 5346 $self->{knob_x} = $value - $knob_w * 0.5;
2294 $self->{knob_w} = $knob_w; 5347 $self->{knob_w} = $knob_w;
2295 }); 5348 }
2296
2297 $self->SUPER::update;
2298}
2299
2300sub _draw {
2301 my ($self) = @_;
2302 5349
2303 $self->SUPER::_draw (); 5350 $self->SUPER::_draw ();
2304 5351
2305 glScale $self->{w}, $self->{h}; 5352 glScale $self->{w}, $self->{h};
2306 5353
2326 glDisable GL_TEXTURE_2D; 5373 glDisable GL_TEXTURE_2D;
2327} 5374}
2328 5375
2329############################################################################# 5376#############################################################################
2330 5377
2331package CFClient::UI::ValSlider; 5378package CFPlus::UI::ValSlider;
2332 5379
2333our @ISA = CFClient::UI::HBox::; 5380our @ISA = CFPlus::UI::HBox::;
2334 5381
2335sub new { 5382sub new {
2336 my ($class, %arg) = @_; 5383 my ($class, %arg) = @_;
2337 5384
2338 my $range = delete $arg{range}; 5385 my $range = delete $arg{range};
2339 5386
2340 my $self = $class->SUPER::new ( 5387 my $self = $class->SUPER::new (
2341 slider => (new CFClient::UI::Slider expand => 1, range => $range), 5388 slider => (new CFPlus::UI::Slider expand => 1, range => $range),
2342 entry => (new CFClient::UI::Label text => "", template => delete $arg{template}), 5389 entry => (new CFPlus::UI::Label text => "", template => delete $arg{template}),
2343 to_value => sub { shift }, 5390 to_value => sub { shift },
2344 from_value => sub { shift }, 5391 from_value => sub { shift },
2345 %arg, 5392 %arg,
2346 ); 5393 );
2347 5394
2367sub set_range { shift->{slider}->set_range (@_) } 5414sub set_range { shift->{slider}->set_range (@_) }
2368sub set_value { shift->{slider}->set_value (@_) } 5415sub set_value { shift->{slider}->set_value (@_) }
2369 5416
2370############################################################################# 5417#############################################################################
2371 5418
2372package CFClient::UI::TextView; 5419package CFPlus::UI::TextScroller;
2373 5420
2374our @ISA = CFClient::UI::HBox::; 5421our @ISA = CFPlus::UI::HBox::;
2375 5422
2376use CFClient::OpenGL; 5423use CFPlus::OpenGL;
2377 5424
2378sub new { 5425sub new {
2379 my $class = shift; 5426 my $class = shift;
2380 5427
2381 my $self = $class->SUPER::new ( 5428 my $self = $class->SUPER::new (
2382 fontsize => 1, 5429 fontsize => 1,
2383 can_events => 0, 5430 can_events => 1,
5431 indent => 0,
2384 #font => default_font 5432 #font => default_font
2385 @_, 5433 @_,
2386 5434
2387 layout => (new CFClient::Layout 1), 5435 layout => (new CFPlus::Layout),
2388 par => [], 5436 par => [],
5437 max_par => 0,
2389 height => 0, 5438 height => 0,
2390 children => [ 5439 children => [
2391 (new CFClient::UI::Empty expand => 1), 5440 (new CFPlus::UI::Empty expand => 1),
2392 (new CFClient::UI::Slider vertical => 1), 5441 (new CFPlus::UI::Slider vertical => 1),
2393 ], 5442 ],
2394 ); 5443 );
2395 5444
2396 $self->{children}[1]->connect (changed => sub { $self->update }); 5445 $self->{children}[1]->connect (changed => sub { $self->update });
2397 5446
2403 5452
2404 $self->{fontsize} = $fontsize; 5453 $self->{fontsize} = $fontsize;
2405 $self->reflow; 5454 $self->reflow;
2406} 5455}
2407 5456
5457sub size_request {
5458 my ($self) = @_;
5459
5460 my ($empty, $slider) = @{ $self->{children} };
5461
5462 local $self->{children} = [$empty, $slider];
5463 $self->SUPER::size_request
5464}
5465
2408sub size_allocate { 5466sub invoke_size_allocate {
2409 my ($self, $w, $h) = @_; 5467 my ($self, $w, $h) = @_;
2410 5468
2411 $self->SUPER::size_allocate ($w, $h); 5469 my ($empty, $slider, @other) = @{ $self->{children} };
5470 $_->configure (@$_{qw(x y req_w req_h)}) for @other;
2412 5471
2413 $self->{layout}->set_font ($self->{font}) if $self->{font}; 5472 $self->{layout}->set_font ($self->{font}) if $self->{font};
2414 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 5473 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
2415 $self->{layout}->set_width ($self->{children}[0]{w}); 5474 $self->{layout}->set_width ($empty->{w});
5475 $self->{layout}->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent});
2416 5476
2417 $self->reflow; 5477 $self->reflow;
2418}
2419 5478
2420sub text_size { 5479 local $self->{children} = [$empty, $slider];
5480 $self->SUPER::invoke_size_allocate ($w, $h)
5481}
5482
5483sub invoke_mouse_wheel {
2421 my ($self, $text, $indent) = @_; 5484 my ($self, $ev) = @_;
5485
5486 return 0 unless $ev->{dy}; # only vertical movements
5487
5488 $self->{children}[1]->emit (mouse_wheel => $ev);
5489
5490 1
5491}
5492
5493sub get_layout {
5494 my ($self, $para) = @_;
2422 5495
2423 my $layout = $self->{layout}; 5496 my $layout = $self->{layout};
2424 5497
5498 $layout->set_font ($self->{font}) if $self->{font};
5499 $layout->set_foreground (@{$para->{fg}});
2425 $layout->set_height ($self->{fontsize} * $::FONTSIZE); 5500 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2426 $layout->set_width ($self->{children}[0]{w} - $indent); 5501 $layout->set_width ($self->{children}[0]{w} - $para->{indent});
5502 $layout->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent});
2427 $layout->set_markup ($text); 5503 $layout->set_markup ($para->{markup});
5504
5505 $layout->set_shapes (
5506 map
5507 +(0, $_->baseline_shift +$_->{padding_y} - $_->{h}, $_->{w}, $_->{h}),
5508 @{$para->{widget}}
2428 5509 );
5510
2429 $layout->size 5511 $layout
2430} 5512}
2431 5513
2432sub reflow { 5514sub reflow {
2433 my ($self) = @_; 5515 my ($self) = @_;
2434 5516
2441 5523
2442 # todo: base offset on lines or so, not on pixels 5524 # todo: base offset on lines or so, not on pixels
2443 $self->{children}[1]->set_value ($offset); 5525 $self->{children}[1]->set_value ($offset);
2444} 5526}
2445 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
2446sub clear { 5543sub clear {
2447 my ($self) = @_; 5544 my ($self) = @_;
5545
5546 my (undef, undef, @other) = @{ $self->{children} };
5547 $self->remove ($_) for @other;
2448 5548
2449 $self->{par} = []; 5549 $self->{par} = [];
2450 $self->{height} = 0; 5550 $self->{height} = 0;
2451 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]); 5551 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]);
2452} 5552}
2453 5553
2454sub add_paragraph { 5554sub add_paragraph {
2455 my ($self, $color, $text, $indent) = @_; 5555 my $self = shift;
2456 5556
2457 for my $line (split /\n/, $text) { 5557 for my $para (@_) {
2458 my ($w, $h) = $self->text_size ($line); 5558 $para = {
5559 fg => [1, 1, 1, 1],
5560 indent => 0,
5561 markup => "",
5562 widget => [],
5563 ref $para ? %$para : (markup => $para),
5564 w => 1e10,
5565 wrapped => 1,
5566 };
5567
5568 $self->add (@{ $para->{widget} }) if @{ $para->{widget} };
5569 push @{$self->{par}}, $para;
5570 }
5571
5572 if (my $max = $self->{max_par}) {
5573 shift @{$self->{par}} while @{$self->{par}} > $max;
5574 }
5575
5576 $self->{need_reflow}++;
5577 $self->update;
5578}
5579
5580sub scroll_to_bottom {
5581 my ($self) = @_;
5582
5583 $self->{scroll_to} = $#{$self->{par}};
5584 $self->update;
5585}
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
2459 $self->{height} += $h; 5609 $self->{height} = $height;
2460 push @{$self->{par}}, [$w + $indent, $h, $color, $indent, $line]; 5610 $self->{children}[1]->set_range ([$self->{children}[1]{range}[0], 0, $height, $H, 1]);
2461 }
2462 5611
2463 $self->{children}[1]->set_range ([$self->{height}, 0, $self->{height}, $self->{h}, 1]); 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 }
2464} 5618}
2465 5619
2466sub update { 5620sub update {
2467 my ($self) = @_; 5621 my ($self) = @_;
2468 5622
2470 5624
2471 return unless $self->{h} > 0; 5625 return unless $self->{h} > 0;
2472 5626
2473 delete $self->{texture}; 5627 delete $self->{texture};
2474 5628
2475 $ROOT->on_post_alloc ($self, sub { 5629 $ROOT->on_post_alloc ($self => sub {
5630 $self->force_uptodate;
5631
2476 my ($W, $H) = @{$self->{children}[0]}{qw(w h)}; 5632 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
2477 5633
2478 if (delete $self->{need_reflow}) {
2479 my $height = 0;
2480
2481 my $layout = $self->{layout};
2482
2483 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2484
2485 for (@{$self->{par}}) {
2486 if (1 || $_->[0] >= $W) { # TODO: works,but needs reconfigure etc. support
2487 $layout->set_width ($W - $_->[3]);
2488 $layout->set_markup ($_->[4]);
2489 my ($w, $h) = $layout->size;
2490 $_->[0] = $w + $_->[3];
2491 $_->[1] = $h;
2492 }
2493
2494 $height += $_->[1];
2495 }
2496
2497 $self->{height} = $height;
2498
2499 $self->{children}[1]->set_range ([$height, 0, $height, $H, 1]);
2500
2501 delete $self->{texture};
2502 }
2503
2504 $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub { 5634 $self->{texture} ||= new_from_opengl CFPlus::Texture $W, $H, sub {
2505 glClearColor 0.5, 0.5, 0.5, 0; 5635 glClearColor 0, 0, 0, 0;
2506 glClear GL_COLOR_BUFFER_BIT; 5636 glClear GL_COLOR_BUFFER_BIT;
2507 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;
2508 my $top = int $self->{children}[1]{range}[0]; 5646 my $top = int $self->{children}[1]{range}[0];
2509 5647
2510 my $y0 = $top; 5648 my $y0 = $top;
2511 my $y1 = $top + $H; 5649 my $y1 = $top + $H;
2512 5650
2513 my $y = 0;
2514
2515 my $layout = $self->{layout};
2516
2517 $layout->set_font ($self->{font}) if $self->{font};
2518
2519 glEnable GL_BLEND;
2520 #TODO# not correct in windows where rgba is forced off
2521 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2522
2523 for my $par (@{$self->{par}}) { 5651 for my $para (@{$self->{par}}) {
2524 my $h = $par->[1]; 5652 my $h = $para->{h};
5653 my $y = $para->{y};
2525 5654
2526 if ($y0 < $y + $h && $y < $y1) { 5655 if ($y0 < $y + $h && $y < $y1) {
2527 $layout->set_foreground (@{ $par->[2] }); 5656 my $layout = $self->get_layout ($para);
2528 $layout->set_width ($W - $par->[3]);
2529 $layout->set_markup ($par->[4]);
2530 5657
2531 my ($w, $h, $data, $format, $internalformat) = $layout->render; 5658 $layout->render ($para->{indent}, $y - $y0);
2532 5659
2533 glRasterPos $par->[3], $y - $y0; 5660 if (my @w = @{ $para->{widget} }) {
2534 glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data; 5661 my @s = $layout->get_shapes;
5662
5663 for (@w) {
5664 my ($dx, $dy) = splice @s, 0, 2, ();
5665
5666 $_->{x} = $dx + $para->{indent};
5667 $_->{y} = $dy + $y - $y0;
5668
5669 $_->draw;
5670 }
5671 }
2535 } 5672 }
2536 5673
2537 $y += $h; 5674 $paridx++;
5675 $top_paragraph ||= $paridx if $y >= $top;
2538 } 5676 }
2539 5677
2540 glDisable GL_BLEND; 5678 $self->{top_paragraph} = $top_paragraph;
2541 }; 5679 };
2542 }); 5680 });
2543} 5681}
2544 5682
5683sub reconfigure {
5684 my ($self) = @_;
5685
5686 $self->SUPER::reconfigure;
5687
5688 $_->{w} = 1e10 for @{ $self->{par} };
5689 $self->reflow;
5690}
5691
2545sub _draw { 5692sub _draw {
2546 my ($self) = @_; 5693 my ($self) = @_;
2547 5694
2548 glEnable GL_TEXTURE_2D; 5695 glEnable GL_TEXTURE_2D;
2549 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 5696 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2550 glColor 1, 1, 1, 1; 5697 glColor 0, 0, 0, 1;
2551 $self->{texture}->draw_quad_alpha (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h}); 5698 $self->{texture}->draw_quad_alpha_premultiplied (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h});
2552 glDisable GL_TEXTURE_2D; 5699 glDisable GL_TEXTURE_2D;
2553 5700
2554 $self->{children}[1]->draw; 5701 $self->{children}[1]->draw;
2555
2556} 5702}
2557 5703
2558############################################################################# 5704#############################################################################
2559 5705
2560package CFClient::UI::Animator; 5706package CFPlus::UI::Animator;
2561 5707
2562use CFClient::OpenGL; 5708use CFPlus::OpenGL;
2563 5709
2564our @ISA = CFClient::UI::Bin::; 5710our @ISA = CFPlus::UI::Bin::;
2565 5711
2566sub moveto { 5712sub moveto {
2567 my ($self, $x, $y) = @_; 5713 my ($self, $x, $y) = @_;
2568 5714
2569 $self->{moveto} = [$self->{x}, $self->{y}, $x, $y]; 5715 $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
2597 glPopMatrix; 5743 glPopMatrix;
2598} 5744}
2599 5745
2600############################################################################# 5746#############################################################################
2601 5747
2602package CFClient::UI::Flopper; 5748package CFPlus::UI::Flopper;
2603 5749
2604our @ISA = CFClient::UI::Button::; 5750our @ISA = CFPlus::UI::Button::;
2605 5751
2606sub new { 5752sub new {
2607 my $class = shift; 5753 my $class = shift;
2608 5754
2609 my $self = $class->SUPER::new ( 5755 my $self = $class->SUPER::new (
2621 $self->{other}->toggle_visibility; 5767 $self->{other}->toggle_visibility;
2622} 5768}
2623 5769
2624############################################################################# 5770#############################################################################
2625 5771
2626package CFClient::UI::Tooltip; 5772package CFPlus::UI::Tooltip;
2627 5773
2628our @ISA = CFClient::UI::Bin::; 5774our @ISA = CFPlus::UI::Bin::;
2629 5775
2630use CFClient::OpenGL; 5776use CFPlus::OpenGL;
2631 5777
2632sub new { 5778sub new {
2633 my $class = shift; 5779 my $class = shift;
2634 5780
2635 $class->SUPER::new ( 5781 $class->SUPER::new (
2638 ) 5784 )
2639} 5785}
2640 5786
2641sub set_tooltip_from { 5787sub set_tooltip_from {
2642 my ($self, $widget) = @_; 5788 my ($self, $widget) = @_;
5789
5790 $widget->{tooltip} = CFPlus::Pod::section_label tooltip => $1
5791 if $widget->{tooltip} =~ /^#(.*)$/;
2643 5792
2644 my $tooltip = $widget->{tooltip}; 5793 my $tooltip = $widget->{tooltip};
2645 5794
2646 if ($ENV{CFPLUS_DEBUG} & 2) { 5795 if ($ENV{CFPLUS_DEBUG} & 2) {
2647 $tooltip .= "\n\n" . (ref $widget) . "\n" 5796 $tooltip .= "\n\n" . (ref $widget) . "\n"
2648 . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n" 5797 . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n"
2649 . "req $widget->{req_w} $widget->{req_h}\n" 5798 . "req $widget->{req_w} $widget->{req_h}\n"
2650 . "visible $widget->{visible}"; 5799 . "visible $widget->{visible}";
2651 } 5800 }
2652 5801
5802 $tooltip =~ s/^\n+//;
5803 $tooltip =~ s/\n+$//;
5804
2653 $self->add (new CFClient::UI::Label 5805 $self->add (new CFPlus::UI::Label
2654 markup => $tooltip, 5806 markup => $tooltip,
2655 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH, 5807 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH,
2656 fontsize => 0.8, 5808 fontsize => 0.8,
2657 fg => [0, 0, 0, 1], 5809 style => 1, # FLAG_INVERSE
2658 ellipsise => 0, 5810 ellipsise => 0,
2659 font => ($widget->{tooltip_font} || $::FONT_PROP), 5811 font => ($widget->{tooltip_font} || $::FONT_PROP),
2660 ); 5812 );
2661} 5813}
2662 5814
2666 my ($w, $h) = @{$self->child}{qw(req_w req_h)}; 5818 my ($w, $h) = @{$self->child}{qw(req_w req_h)};
2667 5819
2668 ($w + 4, $h + 4) 5820 ($w + 4, $h + 4)
2669} 5821}
2670 5822
2671sub size_allocate { 5823sub invoke_size_allocate {
2672 my ($self, $w, $h) = @_; 5824 my ($self, $w, $h) = @_;
2673 5825
2674 $self->SUPER::size_allocate ($w - 4, $h - 4); 5826 $self->SUPER::invoke_size_allocate ($w - 4, $h - 4)
2675} 5827}
2676 5828
2677sub visibility_change { 5829sub invoke_visibility_change {
2678 my ($self, $visible) = @_; 5830 my ($self, $visible) = @_;
2679 5831
2680 return unless $visible; 5832 return unless $visible;
2681 5833
2682 $self->{root}->on_post_alloc ("move_$self" => sub { 5834 $self->{root}->on_post_alloc ("move_$self" => sub {
2683 my $widget = $self->{owner} 5835 my $widget = $self->{owner}
2684 or return; 5836 or return;
2685 5837
5838 if ($widget->{visible}) {
2686 my ($x, $y) = $widget->coord2global ($widget->{w}, 0); 5839 my ($x, $y) = $widget->coord2global ($widget->{w}, 0);
2687 5840
2688 ($x, $y) = $widget->coord2global (-$self->{w}, 0) 5841 ($x, $y) = $widget->coord2global (-$self->{w}, 0)
2689 if $x + $self->{w} > $::WIDTH; 5842 if $x + $self->{w} > $self->{root}{w};
2690 5843
2691 $self->move_abs ($x, $y); 5844 $self->move_abs ($x, $y);
5845 } else {
5846 $self->hide;
5847 }
2692 }); 5848 });
2693} 5849}
2694 5850
2695sub _draw { 5851sub _draw {
2696 my ($self) = @_; 5852 my ($self) = @_;
2720 $self->SUPER::_draw; 5876 $self->SUPER::_draw;
2721} 5877}
2722 5878
2723############################################################################# 5879#############################################################################
2724 5880
2725package CFClient::UI::Face; 5881package CFPlus::UI::Face;
2726 5882
2727our @ISA = CFClient::UI::Base::; 5883our @ISA = CFPlus::UI::DrawBG::;
2728 5884
2729use CFClient::OpenGL; 5885use CFPlus::OpenGL;
2730 5886
2731sub new { 5887sub new {
2732 my $class = shift; 5888 my $class = shift;
2733 5889
2734 my $self = $class->SUPER::new ( 5890 my $self = $class->SUPER::new (
5891 size_w => 32,
5892 size_h => 8,
2735 aspect => 1, 5893 aspect => 1,
2736 can_events => 0, 5894 can_events => 0,
2737 @_, 5895 @_,
2738 ); 5896 );
2739 5897
2740 if ($self->{anim} && $self->{animspeed}) { 5898 if ($self->{anim} && $self->{animspeed}) {
2741 Scalar::Util::weaken (my $widget = $self); 5899 CFPlus::weaken (my $widget = $self);
2742 5900
5901 $widget->{animspeed} = List::Util::max 0.05, $widget->{animspeed};
5902 $widget->{anim_start} = $self->{animspeed} * Event::time / $self->{animspeed};
2743 $self->{timer} = Event->timer ( 5903 $self->{timer} = Event->timer (
2744 at => $self->{animspeed} * int $::NOW / $self->{animspeed},
2745 hard => 1, 5904 parked => 1,
2746 interval => $self->{animspeed},
2747 cb => sub { 5905 cb => sub {
5906 return unless $::CONN && $widget;
5907
2748 ++$widget->{frame}; 5908 ++$widget->{frame};
5909 $widget->update_face;
2749 $widget->update; 5910 $widget->update;
5911
5912 $widget->update_timer;
2750 }, 5913 },
2751 ); 5914 );
5915
5916 $self->update_face;
5917 $self->update_timer;
2752 } 5918 }
2753 5919
2754 $self 5920 $self
2755} 5921}
2756 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
2757sub size_request { 5953sub size_request {
2758 (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)
2759} 5969}
2760 5970
2761sub update { 5971sub update {
2762 my ($self) = @_; 5972 my ($self) = @_;
2763 5973
2764 return unless $self->{visible}; 5974 return unless $self->{visible};
2765 5975
2766 $self->SUPER::update; 5976 $self->SUPER::update;
2767} 5977}
2768 5978
5979sub invoke_visibility_change {
5980 my ($self) = @_;
5981
5982 $self->update_timer;
5983
5984 0
5985}
5986
2769sub _draw { 5987sub _draw {
2770 my ($self) = @_; 5988 my ($self) = @_;
2771 5989
2772 return unless $::CONN; 5990 return unless $::CONN;
2773 5991
2774 my $face; 5992 $self->SUPER::_draw;
2775 5993
2776 if ($self->{frame}) {
2777 my $anim = $::CONN->{anim}[$self->{anim}]; 5994 my $faceid = $::CONN->{faceid}[$self->{face}]
2778 5995 or return;
2779 $face = $anim->[ $self->{frame} % @$anim ]
2780 if $anim && @$anim;
2781 }
2782 5996
2783 my $tex = $::CONN->{texture}[$::CONN->{faceid}[$face || $self->{face}]]; 5997 my $tex = $::CONN->{texture}[$faceid];
2784 5998
2785 if ($tex) { 5999 if ($tex) {
2786 glEnable GL_TEXTURE_2D; 6000 glEnable GL_TEXTURE_2D;
2787 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 6001 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2788 glColor 1, 1, 1, 1; 6002 glColor 0, 0, 0, 1;
2789 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h}); 6003 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2790 glDisable GL_TEXTURE_2D; 6004 glDisable GL_TEXTURE_2D;
2791 } 6005 }
2792} 6006}
2793 6007
2794sub DESTROY { 6008sub destroy {
2795 my ($self) = @_; 6009 my ($self) = @_;
2796 6010
2797 $self->{timer}->cancel 6011 (delete $self->{timer})->cancel
2798 if $self->{timer}; 6012 if $self->{timer};
2799 6013
2800 $self->SUPER::DESTROY; 6014 $self->SUPER::destroy;
2801} 6015}
2802 6016
2803############################################################################# 6017#############################################################################
2804 6018
6019package CFPlus::UI::Buttonbar;
6020
6021our @ISA = CFPlus::UI::HBox::;
6022
6023# TODO: should actually wrap buttons and other goodies.
6024
6025#############################################################################
6026
2805package CFClient::UI::Menu; 6027package CFPlus::UI::Menu;
2806 6028
2807our @ISA = CFClient::UI::FancyFrame::; 6029our @ISA = CFPlus::UI::Toplevel::;
2808 6030
2809use CFClient::OpenGL; 6031use CFPlus::OpenGL;
2810 6032
2811sub new { 6033sub new {
2812 my $class = shift; 6034 my $class = shift;
2813 6035
2814 my $self = $class->SUPER::new ( 6036 my $self = $class->SUPER::new (
2815 items => [], 6037 items => [],
2816 z => 100, 6038 z => 100,
2817 @_, 6039 @_,
2818 ); 6040 );
2819 6041
2820 $self->add ($self->{vbox} = new CFClient::UI::VBox); 6042 $self->add ($self->{vbox} = new CFPlus::UI::VBox);
2821 6043
2822 for my $item (@{ $self->{items} }) { 6044 for my $item (@{ $self->{items} }) {
2823 my ($widget, $cb) = @$item; 6045 my ($widget, $cb, $tooltip) = @$item;
2824 6046
2825 # handle various types of items, only text for now 6047 # handle various types of items, only text for now
2826 if (!ref $widget) { 6048 if (!ref $widget) {
6049 if ($widget =~ /\t/) {
6050 my ($left, $right) = split /\t/, $widget, 2;
6051
2827 $widget = new CFClient::UI::Label 6052 $widget = new CFPlus::UI::HBox
2828 can_hover => 1, 6053 can_hover => 1,
2829 can_events => 1, 6054 can_events => 1,
6055 tooltip => $tooltip,
6056 children => [
6057 (new CFPlus::UI::Label markup => $left, expand => 1),
6058 (new CFPlus::UI::Label markup => $right, align => +1),
6059 ],
6060 ;
6061
6062 } else {
6063 $widget = new CFPlus::UI::Label
6064 can_hover => 1,
6065 can_events => 1,
2830 text => $widget; 6066 markup => $widget,
6067 tooltip => $tooltip;
6068 }
2831 } 6069 }
2832 6070
2833 $self->{item}{$widget} = $item; 6071 $self->{item}{$widget} = $item;
2834 6072
2835 $self->{vbox}->add ($widget); 6073 $self->{vbox}->add ($widget);
2840 6078
2841# popup given the event (must be a mouse button down event currently) 6079# popup given the event (must be a mouse button down event currently)
2842sub popup { 6080sub popup {
2843 my ($self, $ev) = @_; 6081 my ($self, $ev) = @_;
2844 6082
2845 $self->_emit ("popdown"); 6083 $self->emit ("popdown");
2846 6084
2847 # maybe save $GRAB? must be careful about events... 6085 # maybe save $GRAB? must be careful about events...
2848 $GRAB = $self; 6086 $GRAB = $self;
2849 $self->{button} = $ev->{button}; 6087 $self->{button} = $ev->{button};
2850 6088
2851 $self->show; 6089 $self->show;
2852 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5); 6090 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5);
2853} 6091}
2854 6092
2855sub mouse_motion { 6093sub invoke_mouse_motion {
2856 my ($self, $ev, $x, $y) = @_; 6094 my ($self, $ev, $x, $y) = @_;
2857 6095
2858 # TODO: should use vbox->find_widget or so 6096 # TODO: should use vbox->find_widget or so
2859 $HOVER = $ROOT->find_widget ($ev->{x}, $ev->{y}); 6097 $HOVER = $ROOT->find_widget ($ev->{x}, $ev->{y});
2860 $self->{hover} = $self->{item}{$HOVER}; 6098 $self->{hover} = $self->{item}{$HOVER};
2861}
2862 6099
6100 0
6101}
6102
2863sub button_up { 6103sub invoke_button_up {
2864 my ($self, $ev, $x, $y) = @_; 6104 my ($self, $ev, $x, $y) = @_;
2865 6105
2866 if ($ev->{button} == $self->{button}) { 6106 if ($ev->{button} == $self->{button}) {
2867 undef $GRAB; 6107 undef $GRAB;
2868 $self->hide; 6108 $self->hide;
2869 6109
2870 $self->_emit ("popdown"); 6110 $self->emit ("popdown");
2871 $self->{hover}[1]->() if $self->{hover}; 6111 $self->{hover}[1]->() if $self->{hover};
6112 } else {
6113 return 0
6114 }
6115
2872 } 6116 1
2873} 6117}
2874 6118
2875############################################################################# 6119#############################################################################
2876 6120
2877package CFClient::UI::Statusbox; 6121package CFPlus::UI::Multiplexer;
2878 6122
2879our @ISA = CFClient::UI::VBox::; 6123our @ISA = CFPlus::UI::Container::;
2880 6124
2881sub new { 6125sub new {
2882 my $class = shift; 6126 my $class = shift;
2883 6127
2884 $class->SUPER::new ( 6128 my $self = $class->SUPER::new (
6129 @_,
6130 );
6131
6132 $self->{current} = $self->{children}[0]
6133 if @{ $self->{children} };
6134
6135 $self
6136}
6137
6138sub add {
6139 my ($self, @widgets) = @_;
6140
6141 $self->SUPER::add (@widgets);
6142
6143 $self->{current} = $self->{children}[0]
6144 if @{ $self->{children} };
6145}
6146
6147sub get_current_page {
6148 my ($self) = @_;
6149
6150 $self->{current}
6151}
6152
6153sub set_current_page {
6154 my ($self, $page_or_widget) = @_;
6155
6156 my $widget = ref $page_or_widget
6157 ? $page_or_widget
6158 : $self->{children}[$page_or_widget];
6159
6160 $self->{current} = $widget;
6161 $self->{current}->configure (0, 0, $self->{w}, $self->{h});
6162
6163 $self->emit (page_changed => $self->{current});
6164
6165 $self->realloc;
6166}
6167
6168sub visible_children {
6169 $_[0]{current}
6170}
6171
6172sub size_request {
6173 my ($self) = @_;
6174
6175 $self->{current}->size_request
6176}
6177
6178sub invoke_size_allocate {
6179 my ($self, $w, $h) = @_;
6180
6181 $self->{current}->configure (0, 0, $w, $h);
6182
6183 1
6184}
6185
6186sub _draw {
6187 my ($self) = @_;
6188
6189 $self->{current}->draw;
6190}
6191
6192#############################################################################
6193
6194package CFPlus::UI::Notebook;
6195
6196our @ISA = CFPlus::UI::VBox::;
6197
6198sub new {
6199 my $class = shift;
6200
6201 my $self = $class->SUPER::new (
6202 buttonbar => (new CFPlus::UI::Buttonbar),
6203 multiplexer => (new CFPlus::UI::Multiplexer expand => 1),
6204 # filter => # will be put between multiplexer and $self
6205 @_,
6206 );
6207
6208 $self->{filter}->add ($self->{multiplexer}) if $self->{filter};
6209 $self->SUPER::add ($self->{buttonbar}, $self->{filter} || $self->{multiplexer});
6210
6211 $self
6212}
6213
6214sub add {
6215 my ($self, $title, $widget, $tooltip) = @_;
6216
6217 CFPlus::weaken $self;
6218
6219 $self->{buttonbar}->add (new CFPlus::UI::Button
6220 markup => $title,
6221 tooltip => $tooltip,
6222 on_activate => sub { $self->set_current_page ($widget) },
6223 );
6224
6225 $self->{multiplexer}->add ($widget);
6226}
6227
6228sub get_current_page {
6229 my ($self) = @_;
6230
6231 $self->{multiplexer}->get_current_page
6232}
6233
6234sub set_current_page {
6235 my ($self, $page) = @_;
6236
6237 $self->{multiplexer}->set_current_page ($page);
6238 $self->emit (page_changed => $self->{multiplexer}{current});
6239}
6240
6241#############################################################################
6242
6243package CFPlus::UI::Selector;
6244
6245use utf8;
6246
6247our @ISA = CFPlus::UI::Button::;
6248
6249sub new {
6250 my $class = shift;
6251
6252 my $self = $class->SUPER::new (
6253 options => [], # [value, title, longdesc], ...
6254 value => undef,
6255 @_,
6256 );
6257
6258 $self->_set_value ($self->{value});
6259
6260 $self
6261}
6262
6263sub invoke_button_down {
6264 my ($self, $ev) = @_;
6265
6266 my @menu_items;
6267
6268 for (@{ $self->{options} }) {
6269 my ($value, $title, $tooltip) = @$_;
6270
6271 push @menu_items, [$tooltip || $title, sub { $self->set_value ($value) }];
6272 }
6273
6274 CFPlus::UI::Menu->new (items => \@menu_items)->popup ($ev);
6275}
6276
6277sub _set_value {
6278 my ($self, $value) = @_;
6279
6280 my ($item) = grep $_->[0] eq $value, @{ $self->{options} }
6281 or return;
6282
6283 $self->{value} = $item->[0];
6284 $self->set_markup ("$item->[1] ⇓");
6285 $self->set_tooltip ($item->[2]);
6286}
6287
6288sub set_value {
6289 my ($self, $value) = @_;
6290
6291 return unless $self->{value} ne $value;
6292
6293 $self->_set_value ($value);
6294 $self->emit (changed => $value);
6295}
6296
6297#############################################################################
6298
6299package CFPlus::UI::Statusbox;
6300
6301our @ISA = CFPlus::UI::VBox::;
6302
6303sub new {
6304 my $class = shift;
6305
6306 my $self = $class->SUPER::new (
2885 fontsize => 0.8, 6307 fontsize => 0.8,
2886 @_, 6308 @_,
2887 ) 6309 );
6310
6311 CFPlus::weaken (my $this = $self);
6312
6313 $self->{timer} = Event->timer (after => 1, interval => 1, cb => sub { $this->reorder });
6314
6315 $self
2888} 6316}
2889 6317
2890sub reorder { 6318sub reorder {
2891 my ($self) = @_; 6319 my ($self) = @_;
2892 my $NOW = time; 6320 my $NOW = Time::HiRes::time;
6321
6322 # freeze display when hovering over any label
6323 return if $CFPlus::UI::TOOLTIP->{owner}
6324 && grep $CFPlus::UI::TOOLTIP->{owner} == $_->{label},
6325 values %{ $self->{item} };
2893 6326
2894 while (my ($k, $v) = each %{ $self->{item} }) { 6327 while (my ($k, $v) = each %{ $self->{item} }) {
2895 delete $self->{item}{$k} if $v->{timeout} < $NOW; 6328 delete $self->{item}{$k} if $v->{timeout} < $NOW;
2896 } 6329 }
2897 6330
2900 my @items = sort { 6333 my @items = sort {
2901 $a->{pri} <=> $b->{pri} 6334 $a->{pri} <=> $b->{pri}
2902 or $b->{id} <=> $a->{id} 6335 or $b->{id} <=> $a->{id}
2903 } values %{ $self->{item} }; 6336 } values %{ $self->{item} };
2904 6337
6338 $self->{timer}->interval (1);
6339
2905 my $count = 10 + 1; 6340 my $count = 10 + 1;
2906 for my $item (@items) { 6341 for my $item (@items) {
2907 last unless --$count; 6342 last unless --$count;
2908 6343
2909 push @widgets, $item->{label} ||= do { 6344 my $label = $item->{label} ||= do {
2910 # TODO: doesn't handle markup well (read as: at all) 6345 # TODO: doesn't handle markup well (read as: at all)
2911 my $short = $item->{count} > 1 6346 my $short = $item->{count} > 1
2912 ? "<b>$item->{count} ×</b> $item->{text}" 6347 ? "<b>$item->{count} ×</b> $item->{text}"
2913 : $item->{text}; 6348 : $item->{text};
2914 6349
2915 for ($short) { 6350 for ($short) {
2916 s/^\s+//; 6351 s/^\s+//;
2917 s/\s+/ /g; 6352 s/\s+/ /g;
2918 } 6353 }
2919 6354
2920 new CFClient::UI::Label 6355 new CFPlus::UI::Label
2921 markup => $short, 6356 markup => $short,
2922 tooltip => $item->{tooltip}, 6357 tooltip => $item->{tooltip},
2923 tooltip_font => $::FONT_PROP, 6358 tooltip_font => $::FONT_PROP,
2924 tooltip_width => 0.67, 6359 tooltip_width => 0.67,
2925 fontsize => $item->{fontsize} || $self->{fontsize}, 6360 fontsize => $item->{fontsize} || $self->{fontsize},
2926 max_w => $::WIDTH * 0.44, 6361 max_w => $::WIDTH * 0.44,
2927 fg => $item->{fg}, 6362 fg => [@{ $item->{fg} }],
2928 can_events => 1, 6363 can_events => 1,
2929 can_hover => 1 6364 can_hover => 1
2930 }; 6365 };
6366
6367 if ((my $diff = $item->{timeout} - $NOW) < 2) {
6368 $label->{fg}[3] = ($item->{fg}[3] || 1) * $diff / 2;
6369 $label->update;
6370 $label->set_max_size (undef, $label->{req_h} * $diff)
6371 if $diff < 1;
6372 $self->{timer}->interval (1/30);
6373 } else {
6374 $label->{fg}[3] = $item->{fg}[3] || 1;
6375 }
6376
6377 push @widgets, $label;
2931 } 6378 }
2932 6379
2933 $self->clear; 6380 $self->clear;
2934 $self->SUPER::add (reverse @widgets); 6381 $self->SUPER::add (reverse @widgets);
2935} 6382}
2940 $text =~ s/^\s+//; 6387 $text =~ s/^\s+//;
2941 $text =~ s/\s+$//; 6388 $text =~ s/\s+$//;
2942 6389
2943 return unless $text; 6390 return unless $text;
2944 6391
2945 my $timeout = time + ((delete $arg{timeout}) || 60); 6392 my $timeout = (int time) + ((delete $arg{timeout}) || 60);
2946 6393
2947 my $group = exists $arg{group} ? $arg{group} : ++$self->{id}; 6394 my $group = exists $arg{group} ? $arg{group} : ++$self->{id};
2948 6395
2949 if (my $item = $self->{item}{$group}) { 6396 if (my $item = $self->{item}{$group}) {
2950 if ($item->{text} eq $text) { 6397 if ($item->{text} eq $text) {
2951 $item->{count}++; 6398 $item->{count}++;
2952 } else { 6399 } else {
2953 $item->{count} = 1; 6400 $item->{count} = 1;
2954 $item->{text} = $item->{tooltip} = $text; 6401 $item->{text} = $item->{tooltip} = $text;
2955 } 6402 }
2956 $item->{id} = ++$self->{id}; 6403 $item->{id} += 0.2;#d#
2957 $item->{timeout} = $timeout; 6404 $item->{timeout} = $timeout;
2958 delete $item->{label}; 6405 delete $item->{label};
2959 } else { 6406 } else {
2960 $self->{item}{$group} = { 6407 $self->{item}{$group} = {
2961 id => ++$self->{id}, 6408 id => ++$self->{id},
2967 count => 1, 6414 count => 1,
2968 %arg, 6415 %arg,
2969 }; 6416 };
2970 } 6417 }
2971 6418
6419 $ROOT->on_refresh (reorder => sub {
2972 $self->reorder; 6420 $self->reorder;
6421 });
2973} 6422}
2974 6423
2975sub reconfigure { 6424sub reconfigure {
2976 my ($self) = @_; 6425 my ($self) = @_;
2977 6426
2980 6429
2981 $self->reorder; 6430 $self->reorder;
2982 $self->SUPER::reconfigure; 6431 $self->SUPER::reconfigure;
2983} 6432}
2984 6433
6434sub destroy {
6435 my ($self) = @_;
6436
6437 $self->{timer}->cancel;
6438
6439 $self->SUPER::destroy;
6440}
6441
2985############################################################################# 6442#############################################################################
2986 6443
2987package CFClient::UI::Inventory;
2988
2989our @ISA = CFClient::UI::ScrolledWindow::;
2990
2991sub new {
2992 my $class = shift;
2993
2994 my $self = $class->SUPER::new (
2995 scrolled => (new CFClient::UI::Table col_expand => [0, 1, 0]),
2996 @_,
2997 );
2998
2999 $self
3000}
3001
3002sub set_items {
3003 my ($self, $items) = @_;
3004
3005 $self->{scrolled}->clear;
3006 return unless $items;
3007
3008 my @items = sort {
3009 ($a->{type} <=> $b->{type})
3010 or ($a->{name} cmp $b->{name})
3011 } @$items;
3012
3013 $self->{real_items} = \@items;
3014
3015 my $row = 0;
3016 for my $item (@items) {
3017 CFClient::Item::update_widgets $item;
3018
3019 $self->{scrolled}->add (0, $row, $item->{face_widget});
3020 $self->{scrolled}->add (1, $row, $item->{desc_widget});
3021 $self->{scrolled}->add (2, $row, $item->{weight_widget});
3022
3023 $row++;
3024 }
3025}
3026
3027#############################################################################
3028
3029package CFClient::UI::BindEditor;
3030
3031our @ISA = CFClient::UI::FancyFrame::;
3032
3033sub new {
3034 my $class = shift;
3035
3036 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3037
3038 $self->add (my $vb = new CFClient::UI::VBox);
3039
3040
3041 $vb->add ($self->{rec_btn} = new CFClient::UI::Button
3042 text => "start recording",
3043 tooltip => "Start/Stops recording of actions."
3044 ."All subsequent actions after the recording started will be captured."
3045 ."The actions are displayed after the record was stopped."
3046 ."To bind the action you have to click on the 'Bind' button",
3047 on_activate => sub {
3048 unless ($self->{recording}) {
3049 $self->start;
3050 } else {
3051 $self->stop;
3052 }
3053 });
3054
3055 $vb->add (new CFClient::UI::Label text => "Actions:");
3056 $vb->add ($self->{cmdbox} = new CFClient::UI::VBox);
3057
3058 $vb->add (new CFClient::UI::Label text => "Bound to: ");
3059 $vb->add (my $hb = new CFClient::UI::HBox);
3060 $hb->add ($self->{keylbl} = new CFClient::UI::Label expand => 1);
3061 $hb->add (new CFClient::UI::Button
3062 text => "bind",
3063 tooltip => "This opens a query where you have to press the key combination to bind the recorded actions",
3064 on_activate => sub {
3065 $self->ask_for_bind;
3066 });
3067
3068 $vb->add (my $hb = new CFClient::UI::HBox);
3069 $hb->add (new CFClient::UI::Button
3070 text => "ok",
3071 expand => 1,
3072 tooltip => "This closes the binding editor and saves the binding",
3073 on_activate => sub {
3074 $self->hide;
3075 $self->commit;
3076 });
3077
3078 $hb->add (new CFClient::UI::Button
3079 text => "cancel",
3080 expand => 1,
3081 tooltip => "This closes the binding editor without saving",
3082 on_activate => sub {
3083 $self->hide;
3084 $self->{binding_cancel}->()
3085 if $self->{binding_cancel};
3086 });
3087
3088 $self->update_binding_widgets;
3089
3090 $self
3091}
3092
3093sub commit {
3094 my ($self) = @_;
3095 my ($mod, $sym, $cmds) = $self->get_binding;
3096 if ($sym != 0 && @$cmds > 0) {
3097 $::STATUSBOX->add ("Bound actions to '".CFClient::Binder::keycombo_to_name ($mod, $sym)
3098 ."'. Don't forget 'Save Config'!");
3099 $self->{binding_change}->($mod, $sym, $cmds)
3100 if $self->{binding_change};
3101 } else {
3102 $::STATUSBOX->add ("No action bound, no key or action specified!");
3103 $self->{binding_cancel}->()
3104 if $self->{binding_cancel};
3105 }
3106}
3107
3108sub start {
3109 my ($self) = @_;
3110
3111 $self->{rec_btn}->set_text ("stop recording");
3112 $self->{recording} = 1;
3113 $self->clear_command_list;
3114 $::CONN->start_record if $::CONN;
3115}
3116
3117sub stop {
3118 my ($self) = @_;
3119
3120 $self->{rec_btn}->set_text ("start recording");
3121 $self->{recording} = 0;
3122
3123 my $rec;
3124 $rec = $::CONN->stop_record if $::CONN;
3125 return unless ref $rec eq 'ARRAY';
3126 $self->set_command_list ($rec);
3127}
3128
3129# if $commit is true, the binding will be set after the user entered a key combo
3130sub ask_for_bind {
3131 my ($self, $commit) = @_;
3132
3133 CFClient::Binder::open_binding_dialog (sub {
3134 my ($mod, $sym) = @_;
3135 $self->{binding} = [$mod, $sym]; # XXX: how to stop that memleak?
3136 $self->update_binding_widgets;
3137 $self->commit if $commit;
3138 });
3139}
3140
3141# $mod and $sym are the modifiers and key symbol
3142# $cmds is a array ref of strings (the commands)
3143# $cb is the callback that is executed on OK
3144# $ccb is the callback that is executed on CANCEL and
3145# when the binding was unsuccessful on OK
3146sub set_binding {
3147 my ($self, $mod, $sym, $cmds, $cb, $ccb) = @_;
3148
3149 $self->clear_command_list;
3150 $self->{recording} = 0;
3151 $self->{rec_btn}->set_text ("start recording");
3152
3153 $self->{binding} = [$mod, $sym];
3154 $self->{commands} = $cmds;
3155
3156 $self->{binding_change} = $cb;
3157 $self->{binding_cancel} = $ccb;
3158
3159 $self->update_binding_widgets;
3160}
3161
3162# this is a shortcut method that asks for a binding
3163# and then just binds it.
3164sub do_quick_binding {
3165 my ($self, $cmds) = @_;
3166 $self->set_binding (undef, undef, $cmds, sub {
3167 $::CFG->{bindings}->{$_[0]}->{$_[1]} = $_[2];
3168 });
3169 $self->ask_for_bind (1);
3170}
3171
3172sub update_binding_widgets {
3173 my ($self) = @_;
3174 my ($mod, $sym, $cmds) = $self->get_binding;
3175 $self->{keylbl}->set_text (CFClient::Binder::keycombo_to_name ($mod, $sym));
3176 $self->set_command_list ($cmds);
3177}
3178
3179sub get_binding {
3180 my ($self) = @_;
3181 return (
3182 $self->{binding}->[0],
3183 $self->{binding}->[1],
3184 [ grep { defined $_ } @{$self->{commands}} ]
3185 );
3186}
3187
3188sub clear_command_list {
3189 my ($self) = @_;
3190 $self->{cmdbox}->clear ();
3191}
3192
3193sub set_command_list {
3194 my ($self, $cmds) = @_;
3195
3196 $self->{cmdbox}->clear ();
3197 $self->{commands} = $cmds;
3198
3199 my $idx = 0;
3200
3201 for (@$cmds) {
3202 $self->{cmdbox}->add (my $hb = new CFClient::UI::HBox);
3203
3204 my $i = $idx;
3205 $hb->add (new CFClient::UI::Label text => $_);
3206 $hb->add (new CFClient::UI::Button
3207 text => "delete",
3208 tooltip => "Deletes the action from the record",
3209 on_activate => sub {
3210 $self->{cmdbox}->remove ($hb);
3211 $cmds->[$i] = undef;
3212 });
3213
3214
3215 $idx++
3216 }
3217}
3218
3219#############################################################################
3220
3221package CFClient::UI::SpellList;
3222
3223our @ISA = CFClient::UI::FancyFrame::;
3224
3225sub new {
3226 my $class = shift;
3227
3228 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3229
3230 $self->add (new CFClient::UI::ScrolledWindow
3231 scrolled => $self->{spellbox} = new CFClient::UI::Table);
3232
3233 $self;
3234}
3235
3236# XXX: Do sorting? Argl...
3237sub add_spell {
3238 my ($self, $spell) = @_;
3239 $self->{spells}->{$spell->{name}} = $spell;
3240
3241 $self->{spellbox}->add (0, $self->{tbl_idx}, new CFClient::UI::Face
3242 face => $spell->{face},
3243 can_hover => 1,
3244 can_events => 1,
3245 tooltip => $spell->{message});
3246
3247 $self->{spellbox}->add (1, $self->{tbl_idx}, new CFClient::UI::Label
3248 text => $spell->{name},
3249 can_hover => 1,
3250 can_events => 1,
3251 tooltip => $spell->{message},
3252 expand => 1);
3253
3254 $self->{spellbox}->add (2, $self->{tbl_idx}, new CFClient::UI::Label
3255 text => (sprintf "lvl: %2d sp: %2d dmg: %2d",
3256 $spell->{level}, ($spell->{mana} || $spell->{grace}), $spell->{damage}),
3257 expand => 1);
3258
3259 $self->{spellbox}->add (3, $self->{tbl_idx}++, new CFClient::UI::Button
3260 text => "bind to key",
3261 on_activate => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) });
3262}
3263
3264sub rebuild_spell_list {
3265 my ($self) = @_;
3266 $self->{tbl_idx} = 0;
3267 $self->add_spell ($_) for values %{$self->{spells}};
3268}
3269
3270sub remove_spell {
3271 my ($self, $spell) = @_;
3272 delete $self->{spells}->{$spell->{name}};
3273 $self->rebuild_spell_list;
3274}
3275
3276#############################################################################
3277
3278package CFClient::UI::Root; 6444package CFPlus::UI::Root;
3279 6445
3280our @ISA = CFClient::UI::Container::; 6446our @ISA = CFPlus::UI::Container::;
3281 6447
6448use List::Util qw(min max);
6449
3282use CFClient::OpenGL; 6450use CFPlus::OpenGL;
3283 6451
3284sub new { 6452sub new {
3285 my $class = shift; 6453 my $class = shift;
3286 6454
3287 my $self = $class->SUPER::new ( 6455 my $self = $class->SUPER::new (
3288 visible => 1, 6456 visible => 1,
3289 @_, 6457 @_,
3290 ); 6458 );
3291 6459
3292 Scalar::Util::weaken ($self->{root} = $self); 6460 CFPlus::weaken ($self->{root} = $self);
3293 6461
3294 $self 6462 $self
3295} 6463}
3296 6464
3297sub size_request { 6465sub size_request {
3312 $coord = $max - $size if $coord > $max - $size; 6480 $coord = $max - $size if $coord > $max - $size;
3313 6481
3314 int $coord + 0.5 6482 int $coord + 0.5
3315} 6483}
3316 6484
3317sub size_allocate { 6485sub invoke_size_allocate {
3318 my ($self, $w, $h) = @_; 6486 my ($self, $w, $h) = @_;
3319 6487
3320 for my $child ($self->children) { 6488 for my $child ($self->children) {
3321 my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)}; 6489 my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)};
3322 6490
3326 $X = _to_pixel $X, $W, $self->{w}; 6494 $X = _to_pixel $X, $W, $self->{w};
3327 $Y = _to_pixel $Y, $H, $self->{h}; 6495 $Y = _to_pixel $Y, $H, $self->{h};
3328 6496
3329 $child->configure ($X, $Y, $W, $H); 6497 $child->configure ($X, $Y, $W, $H);
3330 } 6498 }
6499
6500 1
3331} 6501}
3332 6502
3333sub coord2local { 6503sub coord2local {
3334 my ($self, $x, $y) = @_; 6504 my ($self, $x, $y) = @_;
3335 6505
3420 6590
3421 delete $queue{$widget+0}; 6591 delete $queue{$widget+0};
3422 6592
3423 my ($w, $h) = $widget->size_request; 6593 my ($w, $h) = $widget->size_request;
3424 6594
3425 $w = List::Util::max $widget->{min_w}, $w + $widget->{padding_x} * 2; 6595 $w = max $widget->{min_w}, $w + $widget->{padding_x} * 2;
3426 $h = List::Util::max $widget->{min_h}, $h + $widget->{padding_y} * 2; 6596 $h = max $widget->{min_h}, $h + $widget->{padding_y} * 2;
6597
6598 $w = min $widget->{max_w}, $w if exists $widget->{max_w};
6599 $h = min $widget->{max_h}, $h if exists $widget->{max_h};
3427 6600
3428 $w = $widget->{force_w} if exists $widget->{force_w}; 6601 $w = $widget->{force_w} if exists $widget->{force_w};
3429 $h = $widget->{force_h} if exists $widget->{force_h}; 6602 $h = $widget->{force_h} if exists $widget->{force_h};
3430 6603
3431 if ($widget->{req_w} != $w || $widget->{req_h} != $h 6604 if ($widget->{req_w} != $w || $widget->{req_h} != $h
3458 my ($w, $h) = @$widget{qw(alloc_w alloc_h)}; 6631 my ($w, $h) = @$widget{qw(alloc_w alloc_h)};
3459 6632
3460 $w = 0 if $w < 0; 6633 $w = 0 if $w < 0;
3461 $h = 0 if $h < 0; 6634 $h = 0 if $h < 0;
3462 6635
6636 $w = max $widget->{min_w}, $w;
6637 $h = max $widget->{min_h}, $h;
6638
6639# $w = min $self->{w} - $widget->{x}, $w if $self->{w};
6640# $h = min $self->{h} - $widget->{y}, $h if $self->{h};
6641
6642 $w = min $widget->{max_w}, $w if exists $widget->{max_w};
6643 $h = min $widget->{max_h}, $h if exists $widget->{max_h};
6644
3463 $w = int $w + 0.5; 6645 $w = int $w + 0.5;
3464 $h = int $h + 0.5; 6646 $h = int $h + 0.5;
3465 6647
3466 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) { 6648 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) {
3467 $widget->{old_w} = $widget->{w}; 6649 $widget->{old_w} = $widget->{w};
3477 6659
3478 while ($self->{post_alloc_hook}) { 6660 while ($self->{post_alloc_hook}) {
3479 $_->() 6661 $_->()
3480 for values %{delete $self->{post_alloc_hook}}; 6662 for values %{delete $self->{post_alloc_hook}};
3481 } 6663 }
3482
3483 6664
3484 glViewport 0, 0, $::WIDTH, $::HEIGHT; 6665 glViewport 0, 0, $::WIDTH, $::HEIGHT;
3485 glClearColor +($::CFG->{fow_intensity}) x 3, 1; 6666 glClearColor +($::CFG->{fow_intensity}) x 3, 1;
3486 glClear GL_COLOR_BUFFER_BIT; 6667 glClear GL_COLOR_BUFFER_BIT;
3487 6668
3490 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000; 6671 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000;
3491 glMatrixMode GL_MODELVIEW; 6672 glMatrixMode GL_MODELVIEW;
3492 glLoadIdentity; 6673 glLoadIdentity;
3493 6674
3494 { 6675 {
3495 package CFClient::UI::Base; 6676 package CFPlus::UI::Base;
3496 6677
3497 ($draw_x, $draw_y, $draw_w, $draw_h) = 6678 local ($draw_x, $draw_y, $draw_w, $draw_h) =
3498 (0, 0, $self->{w}, $self->{h}); 6679 (0, 0, $self->{w}, $self->{h});
3499 }
3500 6680
3501 $self->_draw; 6681 $self->_draw;
6682 }
3502} 6683}
3503 6684
3504############################################################################# 6685#############################################################################
3505 6686
3506package CFClient::UI; 6687package CFPlus::UI;
3507 6688
3508$ROOT = new CFClient::UI::Root; 6689$ROOT = new CFPlus::UI::Root;
3509$TOOLTIP = new CFClient::UI::Tooltip z => 900; 6690$TOOLTIP = new CFPlus::UI::Tooltip z => 900;
3510 6691
35111 66921
3512 6693

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines