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.266 by root, Thu Jun 1 03:42:58 2006 UTC vs.
Revision 1.377 by root, Mon Jul 16 20:20:30 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;
453}
454 501
455sub w { $_[0]{w} = $_[1] if @_ > 1; $_[0]{w} } 502 0
456sub h { $_[0]{h} = $_[1] if @_ > 1; $_[0]{h} } 503}
457sub x { $_[0]{x} = $_[1] if @_ > 1; $_[0]{x} } 504
458sub y { $_[0]{y} = $_[1] if @_ > 1; $_[0]{y} } 505sub connect {
459sub z { $_[0]{z} = $_[1] if @_ > 1; $_[0]{z} } 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)
550}
460 551
461sub find_widget { 552sub find_widget {
462 my ($self, $x, $y) = @_; 553 my ($self, $x, $y) = @_;
463 554
464 return () unless $self->{can_events}; 555 return () unless $self->{can_events};
471} 562}
472 563
473sub set_parent { 564sub set_parent {
474 my ($self, $parent) = @_; 565 my ($self, $parent) = @_;
475 566
476 Scalar::Util::weaken ($self->{parent} = $parent); 567 CFPlus::weaken ($self->{parent} = $parent);
477 $self->set_visible if $parent->{visible}; 568 $self->set_visible if $parent->{visible};
478}
479
480sub connect {
481 my ($self, $signal, $cb) = @_;
482
483 push @{ $self->{signal_cb}{$signal} }, $cb;
484}
485
486sub _emit {
487 my ($self, $signal, @args) = @_;
488
489 List::Util::sum map $_->($self, @args), @{$self->{signal_cb}{$signal} || []}
490}
491
492sub emit {
493 my ($self, $signal, @args) = @_;
494
495 $self->_emit ($signal, @args)
496 || $self->$signal (@args);
497}
498
499sub visibility_change {
500 #my ($self, $visible) = @_;
501} 569}
502 570
503sub realloc { 571sub realloc {
504 my ($self) = @_; 572 my ($self) = @_;
505 573
526 594
527 $self->realloc; 595 $self->realloc;
528 $self->update; 596 $self->update;
529} 597}
530 598
599# using global variables seems a bit hacky, but passing through all drawing
600# functions seems pointless.
601our ($draw_x, $draw_y, $draw_w, $draw_h); # screen rectangle being drawn
602
531sub draw { 603sub draw {
532 my ($self) = @_; 604 my ($self) = @_;
533 605
534 return unless $self->{h} && $self->{w}; 606 return unless $self->{h} && $self->{w};
607
608 # update screen rectangle
609 local $draw_x = $draw_x + $self->{x};
610 local $draw_y = $draw_y + $self->{y};
611
612 # skip widgets that are entirely outside the drawing area
613 return if ($draw_x + $self->{w} < 0) || ($draw_x >= $draw_w)
614 || ($draw_y + $self->{h} < 0) || ($draw_y >= $draw_h);
535 615
536 glPushMatrix; 616 glPushMatrix;
537 glTranslate $self->{x}, $self->{y}, 0; 617 glTranslate $self->{x}, $self->{y}, 0;
538 $self->_draw;
539 glPopMatrix;
540 618
541 if ($self == $HOVER && $self->{can_hover}) { 619 if ($self == $HOVER && $self->{can_hover}) {
542 my ($x, $y) = @$self{qw(x y)};
543
544 glColor 1, 0.8, 0.5, 0.2; 620 glColor 1*0.2, 0.8*0.2, 0.5*0.2, 0.2;
545 glEnable GL_BLEND; 621 glEnable GL_BLEND;
546 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 622 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
547 glBegin GL_QUADS; 623 glBegin GL_QUADS;
548 glVertex $x , $y; 624 glVertex 0 , 0;
549 glVertex $x + $self->{w}, $y; 625 glVertex $self->{w}, 0;
550 glVertex $x + $self->{w}, $y + $self->{h}; 626 glVertex $self->{w}, $self->{h};
551 glVertex $x , $y + $self->{h}; 627 glVertex 0 , $self->{h};
552 glEnd; 628 glEnd;
553 glDisable GL_BLEND; 629 glDisable GL_BLEND;
554 } 630 }
555 631
556 if ($ENV{CFPLUS_DEBUG} & 1) { 632 if ($ENV{CFPLUS_DEBUG} & 1) {
557 glPushMatrix; 633 glPushMatrix;
558 glColor 1, 1, 0, 1; 634 glColor 1, 1, 0, 1;
559 glTranslate $self->{x} + 0.375, $self->{y} + 0.375; 635 glTranslate 0.375, 0.375;
560 glBegin GL_LINE_LOOP; 636 glBegin GL_LINE_LOOP;
561 glVertex 0 , 0; 637 glVertex 0 , 0;
562 glVertex $self->{w} - 1, 0; 638 glVertex $self->{w} - 1, 0;
563 glVertex $self->{w} - 1, $self->{h} - 1; 639 glVertex $self->{w} - 1, $self->{h} - 1;
564 glVertex 0 , $self->{h} - 1; 640 glVertex 0 , $self->{h} - 1;
565 glEnd; 641 glEnd;
566 glPopMatrix; 642 glPopMatrix;
567 #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;
568 } 644 }
645
646 $self->_draw;
647 glPopMatrix;
569} 648}
570 649
571sub _draw { 650sub _draw {
572 my ($self) = @_; 651 my ($self) = @_;
573 652
574 warn "no draw defined for $self\n"; 653 warn "no draw defined for $self\n";
575} 654}
576 655
656my $cntx;#d#
577sub DESTROY { 657sub DESTROY {
578 my ($self) = @_; 658 my ($self) = @_;
579 659
660 return if CFPlus::in_destruct;
661
662 eval { $self->destroy };
663 warn "exception during widget destruction: $@" if $@ & $@ != /during global destruction/;
664
580 delete $WIDGET{$self+0}; 665 delete $WIDGET{$self+0};
581 #$self->deactivate;
582} 666}
583 667
584############################################################################# 668#############################################################################
585 669
586package CFClient::UI::DrawBG; 670package CFPlus::UI::DrawBG;
587 671
588our @ISA = CFClient::UI::Base::; 672our @ISA = CFPlus::UI::Base::;
589 673
590use strict; 674use strict;
591use CFClient::OpenGL; 675use CFPlus::OpenGL;
592 676
593sub new { 677sub new {
594 my $class = shift; 678 my $class = shift;
595 679
596 # range [value, low, high, page] 680 # range [value, low, high, page]
611 695
612 if ($color && (@$color < 4 || $color->[3])) { 696 if ($color && (@$color < 4 || $color->[3])) {
613 my ($w, $h) = @$self{qw(w h)}; 697 my ($w, $h) = @$self{qw(w h)};
614 698
615 glEnable GL_BLEND; 699 glEnable GL_BLEND;
616 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 700 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
617 glColor @$color; 701 glColor_premultiply @$color;
618 702
619 glBegin GL_QUADS; 703 glBegin GL_QUADS;
620 glVertex 0 , 0; 704 glVertex 0 , 0;
621 glVertex 0 , $h; 705 glVertex 0 , $h;
622 glVertex $w, $h; 706 glVertex $w, $h;
627 } 711 }
628} 712}
629 713
630############################################################################# 714#############################################################################
631 715
632package CFClient::UI::Empty; 716package CFPlus::UI::Empty;
633 717
634our @ISA = CFClient::UI::Base::; 718our @ISA = CFPlus::UI::Base::;
635 719
636sub new { 720sub new {
637 my ($class, %arg) = @_; 721 my ($class, %arg) = @_;
638 $class->SUPER::new (can_events => 0, %arg); 722 $class->SUPER::new (can_events => 0, %arg);
639} 723}
646 730
647sub draw { } 731sub draw { }
648 732
649############################################################################# 733#############################################################################
650 734
651package CFClient::UI::Container; 735package CFPlus::UI::Container;
652 736
653our @ISA = CFClient::UI::Base::; 737our @ISA = CFPlus::UI::Base::;
654 738
655sub new { 739sub new {
656 my ($class, %arg) = @_; 740 my ($class, %arg) = @_;
657 741
658 my $children = delete $arg{children} || []; 742 my $children = delete $arg{children};
659 743
660 my $self = $class->SUPER::new ( 744 my $self = $class->SUPER::new (
661 children => [], 745 children => [],
662 can_events => 0, 746 can_events => 0,
663 %arg, 747 %arg,
664 ); 748 );
749
665 $self->add ($_) for @$children; 750 $self->add (@$children)
751 if $children;
666 752
667 $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;
668} 762}
669 763
670sub add { 764sub add {
671 my ($self, @widgets) = @_; 765 my ($self, @widgets) = @_;
672 766
718 $x -= $self->{x}; 812 $x -= $self->{x};
719 $y -= $self->{y}; 813 $y -= $self->{y};
720 814
721 my $res; 815 my $res;
722 816
723 for (reverse @{ $self->{children} }) { 817 for (reverse $self->visible_children) {
724 $res = $_->find_widget ($x, $y) 818 $res = $_->find_widget ($x, $y)
725 and return $res; 819 and return $res;
726 } 820 }
727 821
728 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y}) 822 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
734 $_->draw for @{$self->{children}}; 828 $_->draw for @{$self->{children}};
735} 829}
736 830
737############################################################################# 831#############################################################################
738 832
739package CFClient::UI::Bin; 833package CFPlus::UI::Bin;
740 834
741our @ISA = CFClient::UI::Container::; 835our @ISA = CFPlus::UI::Container::;
742 836
743sub new { 837sub new {
744 my ($class, %arg) = @_; 838 my ($class, %arg) = @_;
745 839
746 my $child = (delete $arg{child}) || new CFClient::UI::Empty::; 840 my $child = (delete $arg{child}) || new CFPlus::UI::Empty::;
747 841
748 $class->SUPER::new (children => [$child], %arg) 842 $class->SUPER::new (children => [$child], %arg)
749} 843}
750 844
751sub add { 845sub add {
752 my ($self, $child) = @_; 846 my ($self, $child) = @_;
753 847
754 $self->{children} = []; 848 $self->SUPER::remove ($_) for @{ $self->{children} };
755
756 $self->SUPER::add ($child); 849 $self->SUPER::add ($child);
757} 850}
758 851
759sub remove { 852sub remove {
760 my ($self, $widget) = @_; 853 my ($self, $widget) = @_;
761 854
762 $self->SUPER::remove ($widget); 855 $self->SUPER::remove ($widget);
763 856
764 $self->{children} = [new CFClient::UI::Empty] 857 $self->{children} = [new CFPlus::UI::Empty]
765 unless @{$self->{children}}; 858 unless @{$self->{children}};
766} 859}
767 860
768sub child { $_[0]->{children}[0] } 861sub child { $_[0]->{children}[0] }
769 862
770sub size_request { 863sub size_request {
771 $_[0]{children}[0]->size_request 864 $_[0]{children}[0]->size_request
772} 865}
773 866
774sub size_allocate { 867sub invoke_size_allocate {
775 my ($self, $w, $h) = @_; 868 my ($self, $w, $h) = @_;
776 869
777 $self->{children}[0]->configure (0, 0, $w, $h); 870 $self->{children}[0]->configure (0, 0, $w, $h);
871
872 1
778} 873}
779 874
780############################################################################# 875#############################################################################
781 876
877# back-buffered drawing area
878
782package CFClient::UI::Window; 879package CFPlus::UI::Window;
783 880
784our @ISA = CFClient::UI::Bin::; 881our @ISA = CFPlus::UI::Bin::;
785 882
786use CFClient::OpenGL; 883use CFPlus::OpenGL;
787 884
788sub new { 885sub new {
789 my ($class, %arg) = @_; 886 my ($class, %arg) = @_;
790 887
791 my $self = $class->SUPER::new (%arg); 888 my $self = $class->SUPER::new (%arg);
796 893
797 $ROOT->on_post_alloc ($self => sub { $self->render_child }); 894 $ROOT->on_post_alloc ($self => sub { $self->render_child });
798 $self->SUPER::update; 895 $self->SUPER::update;
799} 896}
800 897
801sub size_allocate { 898sub invoke_size_allocate {
802 my ($self, $w, $h) = @_; 899 my ($self, $w, $h) = @_;
803 900
804 $self->SUPER::size_allocate ($w, $h);
805 $self->update; 901 $self->update;
902
903 $self->SUPER::invoke_size_allocate ($w, $h)
806} 904}
807 905
808sub _render { 906sub _render {
907 my ($self) = @_;
908
809 $_[0]{children}[0]->draw; 909 $self->{children}[0]->draw;
810} 910}
811 911
812sub render_child { 912sub render_child {
813 my ($self) = @_; 913 my ($self) = @_;
814 914
815 $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 {
816 glClearColor 0, 0, 0, 0; 916 glClearColor 0, 0, 0, 0;
817 glClear GL_COLOR_BUFFER_BIT; 917 glClear GL_COLOR_BUFFER_BIT;
818 918
919 {
920 package CFPlus::UI::Base;
921
922 local ($draw_x, $draw_y, $draw_w, $draw_h) =
923 (0, 0, $self->{w}, $self->{h});
924
819 $self->_render; 925 $self->_render;
926 }
820 }; 927 };
821} 928}
822 929
823sub _draw { 930sub _draw {
824 my ($self) = @_; 931 my ($self) = @_;
825
826 my ($w, $h) = ($self->w, $self->h);
827 932
828 my $tex = $self->{texture} 933 my $tex = $self->{texture}
829 or return; 934 or return;
830 935
831 glEnable GL_TEXTURE_2D; 936 glEnable GL_TEXTURE_2D;
832 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 937 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
833 glColor 1, 1, 1, 1; 938 glColor 0, 0, 0, 1;
834 939
835 $tex->draw_quad_alpha_premultiplied (0, 0, $w, $h); 940 $tex->draw_quad_alpha_premultiplied (0, 0);
836 941
837 glDisable GL_TEXTURE_2D; 942 glDisable GL_TEXTURE_2D;
838} 943}
839 944
840############################################################################# 945#############################################################################
841 946
842package CFClient::UI::ViewPort; 947package CFPlus::UI::ViewPort;
843 948
949use List::Util qw(min max);
950
844our @ISA = CFClient::UI::Window::; 951our @ISA = CFPlus::UI::Window::;
845 952
846sub new { 953sub new {
847 my $class = shift; 954 my $class = shift;
848 955
849 $class->SUPER::new ( 956 $class->SUPER::new (
862 $h = 10 if $self->{scroll_y}; 969 $h = 10 if $self->{scroll_y};
863 970
864 ($w, $h) 971 ($w, $h)
865} 972}
866 973
867sub size_allocate { 974sub invoke_size_allocate {
868 my ($self, $w, $h) = @_; 975 my ($self, $w, $h) = @_;
869 976
870 my $child = $self->child; 977 my $child = $self->child;
871 978
872 $w = $child->{req_w} if $self->{scroll_x} && $child->{req_w}; 979 $w = $child->{req_w} if $self->{scroll_x} && $child->{req_w};
873 $h = $child->{req_h} if $self->{scroll_y} && $child->{req_h}; 980 $h = $child->{req_h} if $self->{scroll_y} && $child->{req_h};
874 981
875 $self->child->configure (0, 0, $w, $h); 982 $self->child->configure (0, 0, $w, $h);
876 $self->update; 983 $self->update;
984
985 1
877} 986}
878 987
879sub set_offset { 988sub set_offset {
880 my ($self, $x, $y) = @_; 989 my ($self, $x, $y) = @_;
881 990
882 $self->{view_x} = int $x; 991 $self->{view_x} = max 0, min $self->child->{w} - $self->{w}, int $x;
883 $self->{view_y} = int $y; 992 $self->{view_y} = max 0, min $self->child->{h} - $self->{h}, int $y;
884 993
994 $self->emit (changed => $self->{view_x}, $self->{view_y});
885 $self->update; 995 $self->update;
886} 996}
887 997
888# hmm, this does not work for topleft of $self... but we should not ask for that 998# hmm, this does not work for topleft of $self... but we should not ask for that
889sub coord2local { 999sub coord2local {
907 if ( $x >= $self->{x} && $x < $self->{x} + $self->{w} 1017 if ( $x >= $self->{x} && $x < $self->{x} + $self->{w}
908 && $y >= $self->{y} && $y < $self->{y} + $self->{h} 1018 && $y >= $self->{y} && $y < $self->{y} + $self->{h}
909 ) { 1019 ) {
910 $self->child->find_widget ($x + $self->{view_x}, $y + $self->{view_y}) 1020 $self->child->find_widget ($x + $self->{view_x}, $y + $self->{view_y})
911 } else { 1021 } else {
912 $self->CFClient::UI::Base::find_widget ($x, $y) 1022 $self->CFPlus::UI::Base::find_widget ($x, $y)
913 } 1023 }
914} 1024}
915 1025
916sub _render { 1026sub _render {
917 my ($self) = @_; 1027 my ($self) = @_;
918 1028
1029 local $CFPlus::UI::Base::draw_x = $CFPlus::UI::Base::draw_x - $self->{view_x};
1030 local $CFPlus::UI::Base::draw_y = $CFPlus::UI::Base::draw_y - $self->{view_y};
1031
919 CFClient::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y}; 1032 CFPlus::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y};
920 1033
921 $self->SUPER::_render; 1034 $self->SUPER::_render;
922} 1035}
923 1036
924############################################################################# 1037#############################################################################
925 1038
926package CFClient::UI::ScrolledWindow; 1039package CFPlus::UI::ScrolledWindow;
927 1040
928our @ISA = CFClient::UI::HBox::; 1041our @ISA = CFPlus::UI::Table::;
929 1042
930sub new { 1043sub new {
931 my $class = shift; 1044 my ($class, %arg) = @_;
1045
1046 my $child = delete $arg{child};
932 1047
933 my $self; 1048 my $self;
934 1049
935 my $slider = new CFClient::UI::Slider 1050 my $hslider = new CFPlus::UI::Slider
1051 vertical => 0,
1052 range => [0, 0, 1, 0.01], # HACK fix
1053 on_changed => sub {
1054 $self->{hpos} = $_[1];
1055 $self->{vp}->set_offset ($self->{hpos}, $self->{vpos});
1056 },
1057 ;
1058
1059 my $vslider = new CFPlus::UI::Slider
936 vertical => 1, 1060 vertical => 1,
937 range => [0, 0, 1, 0.01], # HACK fix 1061 range => [0, 0, 1, 0.01], # HACK fix
938 on_changed => sub { 1062 on_changed => sub {
939 $self->{vp}->set_offset (0, $_[1]); 1063 $self->{vpos} = $_[1];
1064 $self->{vp}->set_offset ($self->{hpos}, $self->{vpos});
940 }, 1065 },
941 ; 1066 ;
942 1067
943 $self = $class->SUPER::new ( 1068 $self = $class->SUPER::new (
944 vp => (new CFClient::UI::ViewPort expand => 1), 1069 scroll_x => 0,
1070 scroll_y => 1,
1071 can_events => 1,
945 slider => $slider, 1072 hslider => $hslider,
946 @_, 1073 vslider => $vslider,
1074 col_expand => [1, 0],
1075 row_expand => [1, 0],
1076 %arg,
947 ); 1077 );
948 1078
949 $self->{vp}->add ($self->{scrolled}); 1079 $self->{vp} = new CFPlus::UI::ViewPort
1080 expand => 1,
1081 scroll_x => $self->{scroll_x},
1082 scroll_y => $self->{scroll_y},
1083 on_changed => sub {
1084 my ($vp, $x, $y) = @_;
1085
1086 $vp->{parent}{hslider}->set_value ($x);
1087 $vp->{parent}{vslider}->set_value ($y);
1088
1089 0
1090 },
1091 ;
1092
950 $self->add ($self->{vp}); 1093 $self->SUPER::add (0, 0, $self->{vp});
951 $self->add ($self->{slider}); 1094
1095 $self->add ($child) if $child;
952 1096
953 $self 1097 $self
954} 1098}
955 1099
1100#TODO# update range on size_allocate depending on child
1101
1102sub add {
1103 my ($self, $widget) = @_;
1104
1105 $self->{vp}->add ($self->{child} = $widget);
1106}
1107
1108sub update_slider {
1109 my ($self) = @_;
1110
1111 my $child = ($self->{vp} or return)->child;
1112
1113 my ($w1, $w2) = ($child->{w}, $self->{vp}{w});
1114 $self->{hslider}->set_range ([$self->{hslider}{range}[0], 0, $w1, $w2, 1]);
1115
1116 my $visible = $w1 > $w2;
1117 if ($visible != $self->{hslider}{visible}) {
1118 $visible ? $self->SUPER::add (0, 1, $self->{hslider})
1119 : $self->{hslider}->hide;
1120 }
1121
1122 my ($h1, $h2) = ($child->{h}, $self->{vp}{h});
1123 $self->{vslider}->set_range ([$self->{vslider}{range}[0], 0, $h1, $h2, 1]);
1124
1125 my $visible = $h1 > $h2;
1126 if ($visible != $self->{vslider}{visible}) {
1127 $visible ? $self->SUPER::add (1, 0, $self->{vslider})
1128 : $self->{vslider}->hide;
1129 }
1130}
1131
956sub update { 1132sub update {
957 my ($self) = @_; 1133 my ($self) = @_;
958 1134
959 $self->SUPER::update; 1135 $self->SUPER::update;
960 1136 $self->update_slider;
961 # todo: overwrite size_allocate of child
962 my $child = $self->{vp}->child;
963 $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $child->{h}, $self->{vp}{h}, 1]);
964} 1137}
965 1138
1139sub invoke_mouse_wheel {
1140 my ($self, $ev) = @_;
1141
1142 return 0 unless $ev->{dy}; # only vertical movements for now
1143
1144 $self->{vslider}->emit (mouse_wheel => $ev);
1145
1146 1
1147}
1148
1149sub invoke_button_down {
1150 my ($self, $ev, $x, $y) = @_;
1151
1152 if ($ev->{button} == 2) {
1153 $self->grab_focus;
1154
1155 my $ox = $self->{vp}{view_x} + $ev->{x};
1156 my $oy = $self->{vp}{view_y} + $ev->{y};
1157
1158 $self->{motion} = sub {
1159 my ($ev, $x, $y) = @_;
1160
1161 $self->{vp}->set_offset ($ox - $ev->{x}, $oy - $ev->{y});
1162 $self->update;
1163 };
1164
1165 return 1;
1166 }
1167
1168 0
1169}
1170
1171sub invoke_button_up {
1172 my ($self, $ev, $x, $y) = @_;
1173
1174 if (delete $self->{motion}) {
1175 return 1;
1176 }
1177
1178 0
1179}
1180
1181sub invoke_mouse_motion {
1182 my ($self, $ev, $x, $y) = @_;
1183
1184 if ($self->{motion}) {
1185 $self->{motion}->($ev, $x, $y);
1186 return 1;
1187 }
1188
1189 0
1190}
1191
966sub size_allocate { 1192sub invoke_size_allocate {
967 my ($self, $w, $h) = @_; 1193 my ($self, $w, $h) = @_;
968 1194
1195 $self->update_slider;
969 $self->SUPER::size_allocate ($w, $h); 1196 $self->SUPER::invoke_size_allocate ($w, $h)
970
971 my $child = $self->{vp}->child;
972 $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $child->{h}, $self->{vp}{h}, 1]);
973} 1197}
974
975#TODO# update range on size_allocate depending on child
976# update viewport offset on scroll
977 1198
978############################################################################# 1199#############################################################################
979 1200
980package CFClient::UI::Frame; 1201package CFPlus::UI::Frame;
981 1202
982our @ISA = CFClient::UI::Bin::; 1203our @ISA = CFPlus::UI::Bin::;
983 1204
984use CFClient::OpenGL; 1205use CFPlus::OpenGL;
985 1206
986sub new { 1207sub new {
987 my $class = shift; 1208 my $class = shift;
988 1209
989 $class->SUPER::new ( 1210 $class->SUPER::new (
997 1218
998 if ($self->{bg}) { 1219 if ($self->{bg}) {
999 my ($w, $h) = @$self{qw(w h)}; 1220 my ($w, $h) = @$self{qw(w h)};
1000 1221
1001 glEnable GL_BLEND; 1222 glEnable GL_BLEND;
1002 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 1223 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
1003 glColor @{ $self->{bg} }; 1224 glColor_premultiply @{ $self->{bg} };
1004 1225
1005 glBegin GL_QUADS; 1226 glBegin GL_QUADS;
1006 glVertex 0 , 0; 1227 glVertex 0 , 0;
1007 glVertex 0 , $h; 1228 glVertex 0 , $h;
1008 glVertex $w, $h; 1229 glVertex $w, $h;
1015 $self->SUPER::_draw; 1236 $self->SUPER::_draw;
1016} 1237}
1017 1238
1018############################################################################# 1239#############################################################################
1019 1240
1020package CFClient::UI::FancyFrame; 1241package CFPlus::UI::FancyFrame;
1021 1242
1022our @ISA = CFClient::UI::Bin::; 1243our @ISA = CFPlus::UI::Bin::;
1023 1244
1024use CFClient::OpenGL; 1245use CFPlus::OpenGL;
1246
1247sub new {
1248 my ($class, %arg) = @_;
1249
1250 if ((exists $arg{label}) && !ref $arg{label}) {
1251 $arg{label} = new CFPlus::UI::Label
1252 align => 1,
1253 valign => 0,
1254 text => $arg{label},
1255 fontsize => ($arg{border} || 0.8) * 0.75;
1256 }
1257
1258 my $self = $class->SUPER::new (
1259 # label => "",
1260 fg => [0.6, 0.3, 0.1],
1261 border => 0.8,
1262 style => 'single',
1263 %arg,
1264 );
1265
1266 $self
1267}
1268
1269sub add {
1270 my ($self, @widgets) = @_;
1271
1272 $self->SUPER::add (@widgets);
1273 $self->CFPlus::UI::Container::add ($self->{label}) if $self->{label};
1274}
1275
1276sub border {
1277 int $_[0]{border} * $::FONTSIZE
1278}
1279
1280sub size_request {
1281 my ($self) = @_;
1282
1283 ($self->{label_w}, undef) = $self->{label}->size_request
1284 if $self->{label};
1285
1286 my ($w, $h) = $self->SUPER::size_request;
1287
1288 (
1289 $w + $self->border * 2,
1290 $h + $self->border * 2,
1291 )
1292}
1293
1294sub invoke_size_allocate {
1295 my ($self, $w, $h) = @_;
1296
1297 my $border = $self->border;
1298
1299 $w -= List::Util::max 0, $border * 2;
1300 $h -= List::Util::max 0, $border * 2;
1301
1302 if (my $label = $self->{label}) {
1303 $label->{w} = List::Util::max 0, List::Util::min $self->{label_w}, $w - $border * 2;
1304 $label->{h} = List::Util::min $h, $border;
1305 $label->invoke_size_allocate ($label->{w}, $label->{h});
1306 }
1307
1308 $self->child->configure ($border, $border, $w, $h);
1309
1310 1
1311}
1312
1313sub _draw {
1314 my ($self) = @_;
1315
1316 my $child = $self->{children}[0];
1317
1318 my $border = $self->border;
1319 my ($w, $h) = ($self->{w}, $self->{h});
1320
1321 $child->draw;
1322
1323 glColor @{$self->{fg}};
1324 glBegin GL_LINE_STRIP;
1325 glVertex $border * 1.5 , $border * 0.5 + 0.5;
1326 glVertex $border * 0.5 + 0.5, $border * 0.5 + 0.5;
1327 glVertex $border * 0.5 + 0.5, $h - $border * 0.5 + 0.5;
1328 glVertex $w - $border * 0.5 + 0.5, $h - $border * 0.5 + 0.5;
1329 glVertex $w - $border * 0.5 + 0.5, $border * 0.5 + 0.5;
1330 glVertex $self->{label} ? $border * 2 + $self->{label}{w} : $border * 1.5, $border * 0.5 + 0.5;
1331 glEnd;
1332
1333 if ($self->{label}) {
1334 glTranslate $border * 2, 0;
1335 $self->{label}->_draw;
1336 }
1337}
1338
1339#############################################################################
1340
1341package CFPlus::UI::Toplevel;
1342
1343our @ISA = CFPlus::UI::Bin::;
1344
1345use CFPlus::OpenGL;
1025 1346
1026my $bg = 1347my $bg =
1027 new_from_file CFClient::Texture CFClient::find_rcfile "d1_bg.png", 1348 new_from_file CFPlus::Texture CFPlus::find_rcfile "d1_bg.png",
1028 mipmap => 1, wrap => 1; 1349 mipmap => 1, wrap => 1;
1029 1350
1030my @border = 1351my @border =
1031 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 1352 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1032 qw(d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png); 1353 qw(d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png);
1033 1354
1034sub new { 1355sub new {
1035 my $class = shift; 1356 my ($class, %arg) = @_;
1036 1357
1037 my $self = $class->SUPER::new ( 1358 my $self = $class->SUPER::new (
1038 bg => [1, 1, 1, 1], 1359 bg => [1, 1, 1, 1],
1039 border_bg => [1, 1, 1, 1], 1360 border_bg => [1, 1, 1, 1],
1040 border => 0.6, 1361 border => 0.6,
1041 can_events => 1, 1362 can_events => 1,
1042 min_w => 16, 1363 min_w => 64,
1043 min_h => 16, 1364 min_h => 32,
1044 @_ 1365 %arg,
1045 ); 1366 );
1046 1367
1047 $self->{title} &&= new CFClient::UI::Label 1368 $self->{title_widget} = new CFPlus::UI::Label
1048 align => 0, 1369 align => 0,
1049 valign => 1, 1370 valign => 1,
1050 text => $self->{title}, 1371 text => $self->{title},
1051 fontsize => $self->{border}; 1372 fontsize => $self->{border},
1373 if exists $self->{title};
1374
1375 if ($self->{has_close_button}) {
1376 $self->{close_button} =
1377 new CFPlus::UI::ImageButton
1378 path => 'x1_close.png',
1379 on_activate => sub { $self->emit ("delete") };
1380
1381 $self->CFPlus::UI::Container::add ($self->{close_button});
1382 }
1052 1383
1053 $self 1384 $self
1385}
1386
1387sub add {
1388 my ($self, @widgets) = @_;
1389
1390 $self->SUPER::add (@widgets);
1391 $self->CFPlus::UI::Container::add ($self->{close_button}) if $self->{close_button};
1392 $self->CFPlus::UI::Container::add ($self->{title_widget}) if $self->{title_widget};
1054} 1393}
1055 1394
1056sub border { 1395sub border {
1057 int $_[0]{border} * $::FONTSIZE 1396 int $_[0]{border} * $::FONTSIZE
1058} 1397}
1059 1398
1060sub size_request { 1399sub size_request {
1061 my ($self) = @_; 1400 my ($self) = @_;
1401
1402 $self->{title_widget}->size_request
1403 if $self->{title_widget};
1404
1405 $self->{close_button}->size_request
1406 if $self->{close_button};
1062 1407
1063 my ($w, $h) = $self->SUPER::size_request; 1408 my ($w, $h) = $self->SUPER::size_request;
1064 1409
1065 ( 1410 (
1066 $w + $self->border * 2, 1411 $w + $self->border * 2,
1067 $h + $self->border * 2, 1412 $h + $self->border * 2,
1068 ) 1413 )
1069} 1414}
1070 1415
1071sub size_allocate { 1416sub invoke_size_allocate {
1072 my ($self, $w, $h) = @_; 1417 my ($self, $w, $h) = @_;
1073 1418
1419 if ($self->{title_widget}) {
1420 $self->{title_widget}{w} = $w;
1421 $self->{title_widget}{h} = $h;
1422 $self->{title_widget}->invoke_size_allocate ($w, $h);
1423 }
1424
1425 my $border = $self->border;
1426
1074 $h -= List::Util::max 0, $self->border * 2; 1427 $h -= List::Util::max 0, $border * 2;
1075 $w -= List::Util::max 0, $self->border * 2; 1428 $w -= List::Util::max 0, $border * 2;
1076 1429
1077 $self->{title}->configure ($self->border, int $self->border - $::FONTSIZE * 2, $w, int $::FONTSIZE * 2)
1078 if $self->{title};
1079
1080 $self->child->configure ($self->border, $self->border, $w, $h); 1430 $self->child->configure ($border, $border, $w, $h);
1081}
1082 1431
1432 $self->{close_button}->configure ($self->{w} - $border, 0, $border, $border)
1433 if $self->{close_button};
1434
1435 1
1436}
1437
1438sub invoke_delete {
1439 my ($self) = @_;
1440
1441 $self->hide;
1442
1443 1
1444}
1445
1083sub button_down { 1446sub invoke_button_down {
1084 my ($self, $ev, $x, $y) = @_; 1447 my ($self, $ev, $x, $y) = @_;
1085 1448
1086 my ($w, $h) = @$self{qw(w h)}; 1449 my ($w, $h) = @$self{qw(w h)};
1087 my $border = $self->border; 1450 my $border = $self->border;
1088 1451
1104 my $dy = $ev->{y} - $oy; 1467 my $dy = $ev->{y} - $oy;
1105 1468
1106 $self->{force_w} = $bw + $dx * ($mx ? -1 : 1); 1469 $self->{force_w} = $bw + $dx * ($mx ? -1 : 1);
1107 $self->{force_h} = $bh + $dy * ($my ? -1 : 1); 1470 $self->{force_h} = $bh + $dy * ($my ? -1 : 1);
1108 1471
1472 $self->move_abs ($wx + $dx * $mx, $wy + $dy * $my);
1109 $self->realloc; 1473 $self->realloc;
1110 $self->move_abs ($wx + $dx * $mx, $wy + $dy * $my);
1111 }; 1474 };
1112 1475
1113 } elsif ($lr ^ $td) { 1476 } elsif ($lr ^ $td) {
1114 my ($ox, $oy) = ($ev->{x}, $ev->{y}); 1477 my ($ox, $oy) = ($ev->{x}, $ev->{y});
1115 my ($bx, $by) = ($self->{x}, $self->{y}); 1478 my ($bx, $by) = ($self->{x}, $self->{y});
1118 my ($ev, $x, $y) = @_; 1481 my ($ev, $x, $y) = @_;
1119 1482
1120 ($x, $y) = ($ev->{x}, $ev->{y}); 1483 ($x, $y) = ($ev->{x}, $ev->{y});
1121 1484
1122 $self->move_abs ($bx + $x - $ox, $by + $y - $oy); 1485 $self->move_abs ($bx + $x - $ox, $by + $y - $oy);
1486 # HACK: the next line is required to enforce placement
1487 $self->{parent}->invoke_size_allocate ($self->{parent}{w}, $self->{parent}{h});
1123 }; 1488 };
1489 } else {
1490 return 0;
1491 }
1492
1124 } 1493 1
1125} 1494}
1126 1495
1127sub button_up { 1496sub invoke_button_up {
1128 my ($self, $ev, $x, $y) = @_; 1497 my ($self, $ev, $x, $y) = @_;
1129 1498
1130 delete $self->{motion}; 1499 ! ! delete $self->{motion}
1131} 1500}
1132 1501
1133sub mouse_motion { 1502sub invoke_mouse_motion {
1134 my ($self, $ev, $x, $y) = @_; 1503 my ($self, $ev, $x, $y) = @_;
1135 1504
1136 $self->{motion}->($ev, $x, $y) if $self->{motion}; 1505 $self->{motion}->($ev, $x, $y) if $self->{motion};
1506
1507 ! ! $self->{motion}
1508}
1509
1510sub invoke_visibility_change {
1511 my ($self, $visible) = @_;
1512
1513 delete $self->{motion} unless $visible;
1514
1515 0
1137} 1516}
1138 1517
1139sub _draw { 1518sub _draw {
1140 my ($self) = @_; 1519 my ($self) = @_;
1141 1520
1521 my $child = $self->{children}[0];
1522
1142 my ($w, $h ) = ($self->{w}, $self->{h}); 1523 my ($w, $h ) = ($self->{w}, $self->{h});
1143 my ($cw, $ch) = ($self->child->{w}, $self->child->{h}); 1524 my ($cw, $ch) = ($child->{w}, $child->{h});
1144 1525
1145 glEnable GL_TEXTURE_2D; 1526 glEnable GL_TEXTURE_2D;
1146 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE; 1527 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
1147 1528
1148 my $border = $self->border; 1529 my $border = $self->border;
1163 $bg->draw_quad_alpha ($border, $border, $cw, $ch); 1544 $bg->draw_quad_alpha ($border, $border, $cw, $ch);
1164 } 1545 }
1165 1546
1166 glDisable GL_TEXTURE_2D; 1547 glDisable GL_TEXTURE_2D;
1167 1548
1168 $self->{title}->draw if $self->{title};
1169
1170 $self->child->draw; 1549 $child->draw;
1550
1551 if ($self->{title_widget}) {
1552 glTranslate 0, $border - $self->{h};
1553 $self->{title_widget}->_draw;
1554
1555 glTranslate 0, - ($border - $self->{h});
1556 }
1557
1558 $self->{close_button}->draw
1559 if $self->{close_button};
1171} 1560}
1172 1561
1173############################################################################# 1562#############################################################################
1174 1563
1175package CFClient::UI::Table; 1564package CFPlus::UI::Table;
1176 1565
1177our @ISA = CFClient::UI::Base::; 1566our @ISA = CFPlus::UI::Base::;
1178 1567
1179use List::Util qw(max sum); 1568use List::Util qw(max sum);
1180 1569
1181use CFClient::OpenGL; 1570use CFPlus::OpenGL;
1182 1571
1183sub new { 1572sub new {
1184 my $class = shift; 1573 my $class = shift;
1185 1574
1186 $class->SUPER::new ( 1575 $class->SUPER::new (
1576 children => [],
1187 col_expand => [], 1577 col_expand => [],
1578 row_expand => [],
1188 @_, 1579 @_,
1189 ) 1580 )
1190} 1581}
1191 1582
1192sub children { 1583sub children {
1193 grep $_, map @$_, grep $_, @{ $_[0]{children} } 1584 grep $_, map @$_, grep $_, @{ $_[0]{children} }
1194} 1585}
1195 1586
1587# TODO: store row/col info in child widget and use standard add/del
1196sub add { 1588sub add {
1197 my ($self, $x, $y, $child) = @_; 1589 my ($self) = shift;
1198 1590
1591 while (@_) {
1592 my ($x, $y, $child) = splice @_, 0, 3, ();
1199 $child->set_parent ($self); 1593 $child->set_parent ($self);
1200 $self->{children}[$y][$x] = $child; 1594 $self->{children}[$y][$x] = $child;
1595 }
1201 1596
1597 $self->{force_realloc} = 1;
1598 $self->{force_size_alloc} = 1;
1202 $self->realloc; 1599 $self->realloc;
1600}
1601
1602sub remove {
1603 my ($self, $child) = @_;
1604
1605 for (@{ $self->{children} }) {
1606 for (@{ $_ || [] }) {
1607 $_ = undef if $_ == $child;
1608 }
1609 }
1203} 1610}
1204 1611
1205# TODO: move to container class maybe? send children a signal on removal? 1612# TODO: move to container class maybe? send children a signal on removal?
1206sub clear { 1613sub clear {
1207 my ($self) = @_; 1614 my ($self) = @_;
1248 (sum @$ws), 1655 (sum @$ws),
1249 (sum @$hs), 1656 (sum @$hs),
1250 ) 1657 )
1251} 1658}
1252 1659
1253sub size_allocate { 1660sub invoke_size_allocate {
1254 my ($self, $w, $h) = @_; 1661 my ($self, $w, $h) = @_;
1255 1662
1256 my ($ws, $hs) = $self->get_wh; 1663 my ($ws, $hs) = $self->get_wh;
1257 1664
1258 my $req_w = (sum @$ws) || 1; 1665 my $req_w = (sum @$ws) || 1;
1259 my $req_h = (sum @$hs) || 1; 1666 my $req_h = (sum @$hs) || 1;
1260 1667
1261 # TODO: nicer code && do row_expand 1668 # TODO: nicer code
1262 my @col_expand = @{$self->{col_expand}}; 1669 my @col_expand = @{$self->{col_expand}};
1263 @col_expand = (1) x @$ws unless @col_expand; 1670 @col_expand = (1) x @$ws unless @col_expand;
1264 my $col_expand = (sum @col_expand) || 1; 1671 my $col_expand = (sum @col_expand) || 1;
1265 1672
1266 # linearly scale sizes
1267 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws; 1673 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws;
1268 $hs->[$_] *= 1 * $h / $req_h for 0 .. $#$hs;
1269 1674
1270 CFClient::UI::harmonize $ws; 1675 CFPlus::UI::harmonize $ws;
1676
1677 my @row_expand = @{$self->{row_expand}};
1678 @row_expand = (1) x @$ws unless @row_expand;
1679 my $row_expand = (sum @row_expand) || 1;
1680
1681 $hs->[$_] += $row_expand[$_] / $row_expand * ($h - $req_h) for 0 .. $#$hs;
1682
1271 CFClient::UI::harmonize $hs; 1683 CFPlus::UI::harmonize $hs;
1272 1684
1273 my $y; 1685 my $y;
1274 1686
1275 for my $r (0 .. $#{$self->{children}}) { 1687 for my $r (0 .. $#{$self->{children}}) {
1276 my $row = $self->{children}[$r] 1688 my $row = $self->{children}[$r]
1290 } 1702 }
1291 1703
1292 $y += $row_h; 1704 $y += $row_h;
1293 } 1705 }
1294 1706
1707 1
1295} 1708}
1296 1709
1297sub find_widget { 1710sub find_widget {
1298 my ($self, $x, $y) = @_; 1711 my ($self, $x, $y) = @_;
1299 1712
1318 } 1731 }
1319} 1732}
1320 1733
1321############################################################################# 1734#############################################################################
1322 1735
1323package CFClient::UI::Box; 1736package CFPlus::UI::Fixed;
1324 1737
1738use List::Util qw(min max);
1739
1325our @ISA = CFClient::UI::Container::; 1740our @ISA = CFPlus::UI::Container::;
1741
1742sub add {
1743 my ($self, $child, $posmode, $x, $y, $sizemode, $w, $h) = @_;
1744
1745 $child->{_fixed} = [$posmode, $x, $y, $sizemode, $w, $h];
1746 $self->SUPER::add ($child);
1747}
1748
1749sub _scale($$$) {
1750 my ($mode, $val, $max) = @_;
1751
1752 $mode eq "abs" ? $val
1753 : $mode eq "rel" ? $val * $max
1754 : 0
1755}
1756
1757sub size_request {
1758 my ($self) = @_;
1759
1760 my ($x1, $y1, $x2, $y2) = (0, 0, 0, 0);
1761
1762 # determine overall size by querying abs widgets
1763 for my $child ($self->visible_children) {
1764 my ($pos, $x, $y, $size, $w, $h) = @{ $child->{_fixed} };
1765
1766 if ($pos eq "abs") {
1767 $w = _scale $size, $w, $child->{req_w};
1768 $h = _scale $size, $h, $child->{req_h};
1769
1770 $x1 = min $x1, $x; $x2 = max $x2, $x + $w;
1771 $y1 = min $y1, $y; $y2 = max $y2, $y + $h;
1772 }
1773 }
1774
1775 my $W = $x2 - $x1;
1776 my $H = $y2 - $y1;
1777
1778 # now layout remaining widgets
1779 for my $child ($self->visible_children) {
1780 my ($pos, $x, $y, $size, $w, $h) = @{ $child->{_fixed} };
1781
1782 if ($pos ne "abs") {
1783 $x = _scale $pos, $x, $W;
1784 $y = _scale $pos, $x, $H;
1785 $w = _scale $size, $w, $child->{req_w};
1786 $h = _scale $size, $h, $child->{req_h};
1787
1788 $x1 = min $x1, $x; $x2 = max $x2, $x + $w;
1789 $y1 = min $y1, $y; $y2 = max $y2, $y + $h;
1790 }
1791 }
1792
1793 my $W = $x2 - $x1;
1794 my $H = $y2 - $y1;
1795
1796 ($W, $H)
1797}
1798
1799sub invoke_size_allocate {
1800 my ($self, $W, $H) = @_;
1801
1802 for my $child ($self->visible_children) {
1803 my ($pos, $x, $y, $size, $w, $h) = @{ $child->{_fixed} };
1804
1805 $x = _scale $pos, $x, $W;
1806 $y = _scale $pos, $x, $H;
1807 $w = _scale $size, $w, $child->{req_w};
1808 $h = _scale $size, $h, $child->{req_h};
1809
1810 $child->configure ($x, $y, $w, $h);
1811 }
1812
1813 1
1814}
1815
1816#############################################################################
1817
1818package CFPlus::UI::Box;
1819
1820our @ISA = CFPlus::UI::Container::;
1326 1821
1327sub size_request { 1822sub size_request {
1328 my ($self) = @_; 1823 my ($self) = @_;
1329 1824
1330 $self->{vertical} 1825 $self->{vertical}
1336 (List::Util::sum map $_->{req_w}, @{$self->{children}}), 1831 (List::Util::sum map $_->{req_w}, @{$self->{children}}),
1337 (List::Util::max map $_->{req_h}, @{$self->{children}}), 1832 (List::Util::max map $_->{req_h}, @{$self->{children}}),
1338 ) 1833 )
1339} 1834}
1340 1835
1341sub size_allocate { 1836sub invoke_size_allocate {
1342 my ($self, $w, $h) = @_; 1837 my ($self, $w, $h) = @_;
1343 1838
1344 my $space = $self->{vertical} ? $h : $w; 1839 my $space = $self->{vertical} ? $h : $w;
1345 my $children = $self->{children}; 1840 my @children = $self->visible_children;
1346 1841
1347 my @req; 1842 my @req;
1348 1843
1349 if ($self->{homogeneous}) { 1844 if ($self->{homogeneous}) {
1350 @req = ($space / (@$children || 1)) x @$children; 1845 @req = ($space / (@children || 1)) x @children;
1351 } else { 1846 } else {
1352 @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @$children; 1847 @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @children;
1353 my $req = List::Util::sum @req; 1848 my $req = List::Util::sum @req;
1354 1849
1355 if ($req > $space) { 1850 if ($req > $space) {
1356 # ah well, not enough space 1851 # ah well, not enough space
1357 $_ *= $space / $req for @req; 1852 $_ *= $space / $req for @req;
1358 } else { 1853 } else {
1359 my $expand = (List::Util::sum map $_->{expand}, @$children) || 1; 1854 my $expand = (List::Util::sum map $_->{expand}, @children) || 1;
1360 1855
1361 $space = ($space - $req) / $expand; # remaining space to give away 1856 $space = ($space - $req) / $expand; # remaining space to give away
1362 1857
1363 $req[$_] += $space * $children->[$_]{expand} 1858 $req[$_] += $space * $children[$_]{expand}
1364 for 0 .. $#$children; 1859 for 0 .. $#children;
1365 } 1860 }
1366 } 1861 }
1367 1862
1368 CFClient::UI::harmonize \@req; 1863 CFPlus::UI::harmonize \@req;
1369 1864
1370 my $pos = 0; 1865 my $pos = 0;
1371 for (0 .. $#$children) { 1866 for (0 .. $#children) {
1372 my $alloc = $req[$_]; 1867 my $alloc = $req[$_];
1373 $children->[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h)); 1868 $children[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h));
1374 1869
1375 $pos += $alloc; 1870 $pos += $alloc;
1376 } 1871 }
1377 1872
1378 1 1873 1
1379} 1874}
1380 1875
1381############################################################################# 1876#############################################################################
1382 1877
1383package CFClient::UI::HBox; 1878package CFPlus::UI::HBox;
1384 1879
1385our @ISA = CFClient::UI::Box::; 1880our @ISA = CFPlus::UI::Box::;
1386 1881
1387sub new { 1882sub new {
1388 my $class = shift; 1883 my $class = shift;
1389 1884
1390 $class->SUPER::new ( 1885 $class->SUPER::new (
1393 ) 1888 )
1394} 1889}
1395 1890
1396############################################################################# 1891#############################################################################
1397 1892
1398package CFClient::UI::VBox; 1893package CFPlus::UI::VBox;
1399 1894
1400our @ISA = CFClient::UI::Box::; 1895our @ISA = CFPlus::UI::Box::;
1401 1896
1402sub new { 1897sub new {
1403 my $class = shift; 1898 my $class = shift;
1404 1899
1405 $class->SUPER::new ( 1900 $class->SUPER::new (
1408 ) 1903 )
1409} 1904}
1410 1905
1411############################################################################# 1906#############################################################################
1412 1907
1413package CFClient::UI::Label; 1908package CFPlus::UI::Label;
1414 1909
1415our @ISA = CFClient::UI::DrawBG::; 1910our @ISA = CFPlus::UI::DrawBG::;
1416 1911
1417use CFClient::OpenGL; 1912use CFPlus::OpenGL;
1418 1913
1419sub new { 1914sub new {
1420 my ($class, %arg) = @_; 1915 my ($class, %arg) = @_;
1421 1916
1422 my $self = $class->SUPER::new ( 1917 my $self = $class->SUPER::new (
1425 #active_bg => none 1920 #active_bg => none
1426 #font => default_font 1921 #font => default_font
1427 #text => initial text 1922 #text => initial text
1428 #markup => initial narkup 1923 #markup => initial narkup
1429 #max_w => maximum pixel width 1924 #max_w => maximum pixel width
1925 #style => 0, # render flags
1430 ellipsise => 3, # end 1926 ellipsise => 3, # end
1431 layout => (new CFClient::Layout), 1927 layout => (new CFPlus::Layout),
1432 fontsize => 1, 1928 fontsize => 1,
1433 align => -1, 1929 align => -1,
1434 valign => -1, 1930 valign => -1,
1435 padding_x => 2, 1931 padding_x => 2,
1436 padding_y => 2, 1932 padding_y => 2,
1437 can_events => 0, 1933 can_events => 0,
1438 %arg 1934 %arg
1439 ); 1935 );
1440 1936
1441 if (exists $self->{template}) { 1937 if (exists $self->{template}) {
1442 my $layout = new CFClient::Layout; 1938 my $layout = new CFPlus::Layout;
1443 $layout->set_text (delete $self->{template}); 1939 $layout->set_text (delete $self->{template});
1444 $self->{template} = $layout; 1940 $self->{template} = $layout;
1445 } 1941 }
1446 1942
1447 if (exists $self->{markup}) { 1943 if (exists $self->{markup}) {
1451 } 1947 }
1452 1948
1453 $self 1949 $self
1454} 1950}
1455 1951
1456sub escape($) {
1457 local $_ = $_[0];
1458
1459 s/&/&amp;/g;
1460 s/>/&gt;/g;
1461 s/</&lt;/g;
1462
1463 $_
1464}
1465
1466sub update { 1952sub update {
1467 my ($self) = @_; 1953 my ($self) = @_;
1468 1954
1469 delete $self->{texture}; 1955 delete $self->{texture};
1470 $self->SUPER::update; 1956 $self->SUPER::update;
1471} 1957}
1472 1958
1959sub realloc {
1960 my ($self) = @_;
1961
1962 delete $self->{ox};
1963 $self->SUPER::realloc;
1964}
1965
1473sub set_text { 1966sub set_text {
1474 my ($self, $text) = @_; 1967 my ($self, $text) = @_;
1475 1968
1476 return if $self->{text} eq "T$text"; 1969 return if $self->{text} eq "T$text";
1477 $self->{text} = "T$text"; 1970 $self->{text} = "T$text";
1478 1971
1479 $self->{layout} = new CFClient::Layout if $self->{layout}->is_rgba;
1480 $self->{layout}->set_text ($text); 1972 $self->{layout}->set_text ($text);
1481 1973
1974 delete $self->{size_req};
1482 $self->realloc; 1975 $self->realloc;
1483 $self->update; 1976 $self->update;
1484} 1977}
1485 1978
1486sub set_markup { 1979sub set_markup {
1489 return if $self->{text} eq "M$markup"; 1982 return if $self->{text} eq "M$markup";
1490 $self->{text} = "M$markup"; 1983 $self->{text} = "M$markup";
1491 1984
1492 my $rgba = $markup =~ /span.*(?:foreground|background)/; 1985 my $rgba = $markup =~ /span.*(?:foreground|background)/;
1493 1986
1494 $self->{layout} = new CFClient::Layout $rgba if $self->{layout}->is_rgba != $rgba;
1495 $self->{layout}->set_markup ($markup); 1987 $self->{layout}->set_markup ($markup);
1496 1988
1989 delete $self->{size_req};
1497 $self->realloc; 1990 $self->realloc;
1498 $self->update; 1991 $self->update;
1499} 1992}
1500 1993
1501sub size_request { 1994sub size_request {
1502 my ($self) = @_; 1995 my ($self) = @_;
1503 1996
1997 $self->{size_req} ||= do {
1504 $self->{layout}->set_font ($self->{font}) if $self->{font}; 1998 $self->{layout}->set_font ($self->{font}) if $self->{font};
1505 $self->{layout}->set_width ($self->{max_w} || -1); 1999 $self->{layout}->set_width ($self->{max_w} || -1);
1506 $self->{layout}->set_ellipsise ($self->{ellipsise}); 2000 $self->{layout}->set_ellipsise ($self->{ellipsise});
1507 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise}); 2001 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1508 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 2002 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1509 2003
1510 my ($w, $h) = $self->{layout}->size; 2004 my ($w, $h) = $self->{layout}->size;
1511 2005
1512 if (exists $self->{template}) { 2006 if (exists $self->{template}) {
1513 $self->{template}->set_font ($self->{font}) if $self->{font}; 2007 $self->{template}->set_font ($self->{font}) if $self->{font};
2008 $self->{template}->set_width ($self->{max_w} || -1);
1514 $self->{template}->set_height ($self->{fontsize} * $::FONTSIZE); 2009 $self->{template}->set_height ($self->{fontsize} * $::FONTSIZE);
1515 2010
1516 my ($w2, $h2) = $self->{template}->size; 2011 my ($w2, $h2) = $self->{template}->size;
1517 2012
1518 $w = List::Util::max $w, $w2; 2013 $w = List::Util::max $w, $w2;
1519 $h = List::Util::max $h, $h2; 2014 $h = List::Util::max $h, $h2;
2015 }
2016
2017 [$w, $h]
1520 } 2018 };
1521 2019
1522 ($w, $h) 2020 @{ $self->{size_req} }
1523} 2021}
1524 2022
2023sub baseline_shift {
2024 $_[0]{layout}->descent
2025}
2026
1525sub size_allocate { 2027sub invoke_size_allocate {
1526 my ($self, $w, $h) = @_; 2028 my ($self, $w, $h) = @_;
2029
2030 delete $self->{ox};
1527 2031
1528 delete $self->{texture} 2032 delete $self->{texture}
1529 unless $w >= $self->{req_w} && $self->{old_w} >= $self->{req_w}; 2033 unless $w >= $self->{req_w} && $self->{old_w} >= $self->{req_w};
2034
2035 1
1530} 2036}
1531 2037
1532sub set_fontsize { 2038sub set_fontsize {
1533 my ($self, $fontsize) = @_; 2039 my ($self, $fontsize) = @_;
1534 2040
1535 $self->{fontsize} = $fontsize; 2041 $self->{fontsize} = $fontsize;
2042 delete $self->{size_req};
1536 delete $self->{texture}; 2043 delete $self->{texture};
1537 2044
1538 $self->realloc; 2045 $self->realloc;
1539} 2046}
1540 2047
2048sub reconfigure {
2049 my ($self) = @_;
2050
2051 delete $self->{size_req};
2052 delete $self->{texture};
2053
2054 $self->SUPER::reconfigure;
2055}
2056
1541sub _draw { 2057sub _draw {
1542 my ($self) = @_; 2058 my ($self) = @_;
1543 2059
1544 $self->SUPER::_draw; # draw background, if applicable 2060 $self->SUPER::_draw; # draw background, if applicable
1545 2061
1546 my $tex = $self->{texture} ||= do { 2062 my $size = $self->{texture} ||= do {
1547 $self->{layout}->set_foreground (@{$self->{fg}}); 2063 $self->{layout}->set_foreground (@{$self->{fg}});
1548 $self->{layout}->set_font ($self->{font}) if $self->{font}; 2064 $self->{layout}->set_font ($self->{font}) if $self->{font};
1549 $self->{layout}->set_width ($self->{w}); 2065 $self->{layout}->set_width ($self->{w});
1550 $self->{layout}->set_ellipsise ($self->{ellipsise}); 2066 $self->{layout}->set_ellipsise ($self->{ellipsise});
1551 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise}); 2067 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1552 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 2068 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1553 2069
1554 my $tex = new_from_layout CFClient::Texture $self->{layout}; 2070 [$self->{layout}->size]
2071 };
1555 2072
2073 unless (exists $self->{ox}) {
1556 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x} 2074 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x}
1557 : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding_x} 2075 : $self->{align} > 0 ? $self->{w} - $size->[0] - $self->{padding_x}
1558 : ($self->{w} - $tex->{w}) * 0.5); 2076 : ($self->{w} - $size->[0]) * 0.5);
1559 2077
1560 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y} 2078 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y}
1561 : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding_y} 2079 : $self->{valign} > 0 ? $self->{h} - $size->[1] - $self->{padding_y}
1562 : ($self->{h} - $tex->{h}) * 0.5); 2080 : ($self->{h} - $size->[1]) * 0.5);
1563
1564 $tex
1565 }; 2081 };
1566 2082
1567 glEnable GL_TEXTURE_2D; 2083 my $w = List::Util::min $self->{w} + 4, $size->[0];
1568 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 2084 my $h = List::Util::min $self->{h} + 2, $size->[1];
1569 2085
1570 if ($tex->{format} == GL_ALPHA) { 2086 $self->{layout}->render ($self->{ox}, $self->{oy}, $self->{style});
1571 glColor @{$self->{fg}};
1572 $tex->draw_quad_alpha ($self->{ox}, $self->{oy});
1573 } else {
1574 $tex->draw_quad_alpha_premultiplied ($self->{ox}, $self->{oy});
1575 }
1576
1577 glDisable GL_TEXTURE_2D;
1578} 2087}
1579 2088
1580############################################################################# 2089#############################################################################
1581 2090
1582package CFClient::UI::EntryBase; 2091package CFPlus::UI::EntryBase;
1583 2092
1584our @ISA = CFClient::UI::Label::; 2093our @ISA = CFPlus::UI::Label::;
1585 2094
1586use CFClient::OpenGL; 2095use CFPlus::OpenGL;
1587 2096
1588sub new { 2097sub new {
1589 my $class = shift; 2098 my $class = shift;
1590 2099
1591 $class->SUPER::new ( 2100 $class->SUPER::new (
1595 active_fg => [0, 0, 0], 2104 active_fg => [0, 0, 0],
1596 can_hover => 1, 2105 can_hover => 1,
1597 can_focus => 1, 2106 can_focus => 1,
1598 valign => 0, 2107 valign => 0,
1599 can_events => 1, 2108 can_events => 1,
2109 ellipsise => 0,
1600 #text => ... 2110 #text => ...
2111 #hidden => "*",
1601 @_ 2112 @_
1602 ) 2113 )
1603} 2114}
1604 2115
1605sub _set_text { 2116sub _set_text {
1607 2118
1608 delete $self->{cur_h}; 2119 delete $self->{cur_h};
1609 2120
1610 return if $self->{text} eq $text; 2121 return if $self->{text} eq $text;
1611 2122
1612 delete $self->{texture};
1613
1614 $self->{last_activity} = $::NOW; 2123 $self->{last_activity} = $::NOW;
1615 $self->{text} = $text; 2124 $self->{text} = $text;
1616 2125
1617 $text =~ s/./*/g if $self->{hidden}; 2126 $text =~ s/./*/g if $self->{hidden};
1618 $self->{layout}->set_text ("$text "); 2127 $self->{layout}->set_text ("$text ");
2128 delete $self->{size_req};
1619 2129
1620 $self->_emit (changed => $self->{text}); 2130 $self->emit (changed => $self->{text});
2131
2132 $self->realloc;
2133 $self->update;
1621} 2134}
1622 2135
1623sub set_text { 2136sub set_text {
1624 my ($self, $text) = @_; 2137 my ($self, $text) = @_;
1625 2138
1626 $self->{cursor} = length $text; 2139 $self->{cursor} = length $text;
1627 $self->_set_text ($text); 2140 $self->_set_text ($text);
1628
1629 $self->realloc;
1630} 2141}
1631 2142
1632sub get_text { 2143sub get_text {
1633 $_[0]{text} 2144 $_[0]{text}
1634} 2145}
1639 my ($w, $h) = $self->SUPER::size_request; 2150 my ($w, $h) = $self->SUPER::size_request;
1640 2151
1641 ($w + 1, $h) # add 1 for cursor 2152 ($w + 1, $h) # add 1 for cursor
1642} 2153}
1643 2154
1644sub key_down { 2155sub invoke_key_down {
1645 my ($self, $ev) = @_; 2156 my ($self, $ev) = @_;
1646 2157
1647 my $mod = $ev->{mod}; 2158 my $mod = $ev->{mod};
1648 my $sym = $ev->{sym}; 2159 my $sym = $ev->{sym};
1649 my $uni = $ev->{unicode}; 2160 my $uni = $ev->{unicode};
1650 2161
1651 my $text = $self->get_text; 2162 my $text = $self->get_text;
2163
2164 $self->{cursor} = List::Util::max 0, List::Util::min $self->{cursor}, length $text;
1652 2165
1653 if ($uni == 8) { 2166 if ($uni == 8) {
1654 substr $text, --$self->{cursor}, 1, "" if $self->{cursor}; 2167 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1655 } elsif ($uni == 127) { 2168 } elsif ($uni == 127) {
1656 substr $text, $self->{cursor}, 1, ""; 2169 substr $text, $self->{cursor}, 1, "";
1657 } elsif ($sym == CFClient::SDLK_LEFT) { 2170 } elsif ($sym == CFPlus::SDLK_LEFT) {
1658 --$self->{cursor} if $self->{cursor}; 2171 --$self->{cursor} if $self->{cursor};
1659 } elsif ($sym == CFClient::SDLK_RIGHT) { 2172 } elsif ($sym == CFPlus::SDLK_RIGHT) {
1660 ++$self->{cursor} if $self->{cursor} < length $self->{text}; 2173 ++$self->{cursor} if $self->{cursor} < length $self->{text};
1661 } elsif ($sym == CFClient::SDLK_HOME) { 2174 } elsif ($sym == CFPlus::SDLK_HOME) {
2175 # what a hack
2176 $self->{cursor} =
2177 (substr $self->{text}, 0, $self->{cursor}) =~ /^(.*\012)/
2178 ? length $1
2179 : 0;
2180 } elsif ($sym == CFPlus::SDLK_END) {
2181 # uh, again
2182 $self->{cursor} =
2183 (substr $self->{text}, $self->{cursor}) =~ /^([^\012]*)\012/
2184 ? $self->{cursor} + length $1
2185 : length $self->{text};
2186 } elsif ($uni == 21) { # ctrl-u
2187 $text = "";
1662 $self->{cursor} = 0; 2188 $self->{cursor} = 0;
1663 } elsif ($sym == CFClient::SDLK_END) {
1664 $self->{cursor} = length $text;
1665 } elsif ($uni == 27) { 2189 } elsif ($uni == 27) {
1666 $self->_emit ('escape'); 2190 $self->emit ('escape');
1667 } elsif ($uni) { 2191 } elsif ($uni == 0x0d) {
2192 substr $text, $self->{cursor}++, 0, "\012";
2193 } elsif ($uni >= 0x20) {
1668 substr $text, $self->{cursor}++, 0, chr $uni; 2194 substr $text, $self->{cursor}++, 0, chr $uni;
2195 } else {
2196 return 0;
1669 } 2197 }
1670 2198
1671 $self->_set_text ($text); 2199 $self->_set_text ($text);
1672 2200
1673 $self->realloc; 2201 $self->realloc;
1674} 2202 $self->update;
1675 2203
2204 1
2205}
2206
1676sub focus_in { 2207sub invoke_focus_in {
1677 my ($self) = @_; 2208 my ($self) = @_;
1678 2209
1679 $self->{last_activity} = $::NOW; 2210 $self->{last_activity} = $::NOW;
1680 2211
1681 $self->SUPER::focus_in; 2212 $self->SUPER::invoke_focus_in
1682} 2213}
1683 2214
1684sub button_down { 2215sub invoke_button_down {
1685 my ($self, $ev, $x, $y) = @_; 2216 my ($self, $ev, $x, $y) = @_;
1686 2217
1687 $self->SUPER::button_down ($ev, $x, $y); 2218 $self->SUPER::invoke_button_down ($ev, $x, $y);
1688 2219
1689 my $idx = $self->{layout}->xy_to_index ($x, $y); 2220 my $idx = $self->{layout}->xy_to_index ($x, $y);
1690 2221
1691 # byte-index to char-index 2222 # byte-index to char-index
1692 my $text = $self->{text}; 2223 my $text = $self->{text};
1693 utf8::encode $text; 2224 utf8::encode $text; $text = substr $text, 0, $idx; utf8::decode $text;
1694 $self->{cursor} = length substr $text, 0, $idx; 2225 $self->{cursor} = length $text;
1695 2226
1696 $self->_set_text ($self->{text}); 2227 $self->_set_text ($self->{text});
1697 $self->update; 2228 $self->update;
2229
2230 1
1698} 2231}
1699 2232
1700sub mouse_motion { 2233sub invoke_mouse_motion {
1701 my ($self, $ev, $x, $y) = @_; 2234 my ($self, $ev, $x, $y) = @_;
1702# printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d# 2235# printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d#
2236
2237 1
1703} 2238}
1704 2239
1705sub _draw { 2240sub _draw {
1706 my ($self) = @_; 2241 my ($self) = @_;
1707 2242
1708 local $self->{fg} = $self->{fg}; 2243 local $self->{fg} = $self->{fg};
1709 2244
1710 if ($FOCUS == $self) { 2245 if ($FOCUS == $self) {
1711 glColor @{$self->{active_bg}}; 2246 glColor_premultiply @{$self->{active_bg}};
1712 $self->{fg} = $self->{active_fg}; 2247 $self->{fg} = $self->{active_fg};
1713 } else { 2248 } else {
1714 glColor @{$self->{bg}}; 2249 glColor_premultiply @{$self->{bg}};
1715 } 2250 }
1716 2251
1717 glEnable GL_BLEND; 2252 glEnable GL_BLEND;
1718 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 2253 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
1719 glBegin GL_QUADS; 2254 glBegin GL_QUADS;
1720 glVertex 0 , 0; 2255 glVertex 0 , 0;
1721 glVertex 0 , $self->{h}; 2256 glVertex 0 , $self->{h};
1722 glVertex $self->{w}, $self->{h}; 2257 glVertex $self->{w}, $self->{h};
1723 glVertex $self->{w}, 0; 2258 glVertex $self->{w}, 0;
1734 utf8::encode $text; 2269 utf8::encode $text;
1735 2270
1736 @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text) 2271 @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text)
1737 } 2272 }
1738 2273
1739 glColor @{$self->{fg}};
1740 glBegin GL_LINES; 2274 glBegin GL_LINES;
1741 glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy}; 2275 glVertex 0.5 + $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy};
1742 glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy} + $self->{cur_h}; 2276 glVertex 0.5 + $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy} + $self->{cur_h};
1743 glEnd; 2277 glEnd;
1744 } 2278 }
1745} 2279}
1746 2280
2281#############################################################################
2282
1747package CFClient::UI::Entry; 2283package CFPlus::UI::Entry;
1748 2284
1749our @ISA = CFClient::UI::EntryBase::; 2285our @ISA = CFPlus::UI::EntryBase::;
1750 2286
1751use CFClient::OpenGL; 2287use CFPlus::OpenGL;
1752 2288
1753sub key_down { 2289sub invoke_key_down {
1754 my ($self, $ev) = @_; 2290 my ($self, $ev) = @_;
1755 2291
1756 my $sym = $ev->{sym}; 2292 my $sym = $ev->{sym};
1757 2293
1758 if ($sym == 13) { 2294 if ($ev->{uni} == 0x0d || $sym == 13) {
1759 unshift @{$self->{history}}, 2295 unshift @{$self->{history}},
1760 my $txt = $self->get_text; 2296 my $txt = $self->get_text;
2297
1761 $self->{history_pointer} = -1; 2298 $self->{history_pointer} = -1;
1762 $self->{history_saveback} = ''; 2299 $self->{history_saveback} = '';
1763 $self->_emit (activate => $txt); 2300 $self->emit (activate => $txt);
1764 $self->update; 2301 $self->update;
1765 2302
1766 } elsif ($sym == CFClient::SDLK_UP) { 2303 } elsif ($sym == CFPlus::SDLK_UP) {
1767 if ($self->{history_pointer} < 0) { 2304 if ($self->{history_pointer} < 0) {
1768 $self->{history_saveback} = $self->get_text; 2305 $self->{history_saveback} = $self->get_text;
1769 } 2306 }
1770 if (@{$self->{history} || []} > 0) { 2307 if (@{$self->{history} || []} > 0) {
1771 $self->{history_pointer}++; 2308 $self->{history_pointer}++;
1773 $self->{history_pointer} = @{$self->{history} || []} - 1; 2310 $self->{history_pointer} = @{$self->{history} || []} - 1;
1774 } 2311 }
1775 $self->set_text ($self->{history}->[$self->{history_pointer}]); 2312 $self->set_text ($self->{history}->[$self->{history_pointer}]);
1776 } 2313 }
1777 2314
1778 } elsif ($sym == CFClient::SDLK_DOWN) { 2315 } elsif ($sym == CFPlus::SDLK_DOWN) {
1779 $self->{history_pointer}--; 2316 $self->{history_pointer}--;
1780 $self->{history_pointer} = -1 if $self->{history_pointer} < 0; 2317 $self->{history_pointer} = -1 if $self->{history_pointer} < 0;
1781 2318
1782 if ($self->{history_pointer} >= 0) { 2319 if ($self->{history_pointer} >= 0) {
1783 $self->set_text ($self->{history}->[$self->{history_pointer}]); 2320 $self->set_text ($self->{history}->[$self->{history_pointer}]);
1784 } else { 2321 } else {
1785 $self->set_text ($self->{history_saveback}); 2322 $self->set_text ($self->{history_saveback});
1786 } 2323 }
1787 2324
1788 } else { 2325 } else {
1789 $self->SUPER::key_down ($ev); 2326 return $self->SUPER::invoke_key_down ($ev)
2327 }
2328
1790 } 2329 1
1791
1792} 2330}
1793 2331
1794############################################################################# 2332#############################################################################
1795 2333
2334package CFPlus::UI::TextEdit;
2335
2336our @ISA = CFPlus::UI::EntryBase::;
2337
2338use CFPlus::OpenGL;
2339
2340sub move_cursor_ver {
2341 my ($self, $dy) = @_;
2342
2343 my ($y, $x) = $self->{layout}->index_to_line_x ($self->{cursor});
2344
2345 $y += $dy;
2346
2347 if (defined (my $index = $self->{layout}->line_x_to_index ($y, $x))) {
2348 $self->{cursor} = $index;
2349 delete $self->{cur_h};
2350 $self->update;
2351 return;
2352 }
2353}
2354
2355sub invoke_key_down {
2356 my ($self, $ev) = @_;
2357
2358 my $sym = $ev->{sym};
2359
2360 if ($sym == CFPlus::SDLK_UP) {
2361 $self->move_cursor_ver (-1);
2362 } elsif ($sym == CFPlus::SDLK_DOWN) {
2363 $self->move_cursor_ver (+1);
2364 } else {
2365 return $self->SUPER::invoke_key_down ($ev)
2366 }
2367
2368 1
2369}
2370
2371#############################################################################
2372
1796package CFClient::UI::Button; 2373package CFPlus::UI::Button;
1797 2374
1798our @ISA = CFClient::UI::Label::; 2375our @ISA = CFPlus::UI::Label::;
1799 2376
1800use CFClient::OpenGL; 2377use CFPlus::OpenGL;
1801 2378
1802my @tex = 2379my @tex =
1803 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 2380 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1804 qw(b1_button_active.png); 2381 qw(b1_button_active.png);
1805 2382
1806sub new { 2383sub new {
1807 my $class = shift; 2384 my $class = shift;
1808 2385
1817 can_events => 1, 2394 can_events => 1,
1818 @_ 2395 @_
1819 ) 2396 )
1820} 2397}
1821 2398
1822sub activate { }
1823
1824sub button_up { 2399sub invoke_button_up {
1825 my ($self, $ev, $x, $y) = @_; 2400 my ($self, $ev, $x, $y) = @_;
1826 2401
1827 $self->emit ("activate") 2402 $self->emit ("activate")
1828 if $x >= 0 && $x < $self->{w} 2403 if $x >= 0 && $x < $self->{w}
1829 && $y >= 0 && $y < $self->{h}; 2404 && $y >= 0 && $y < $self->{h};
2405
2406 1
1830} 2407}
1831 2408
1832sub _draw { 2409sub _draw {
1833 my ($self) = @_; 2410 my ($self) = @_;
1834 2411
1835 local $self->{fg} = $self->{fg}; 2412 local $self->{fg} = $GRAB == $self ? $self->{active_fg} : $self->{fg};
1836
1837 if ($GRAB == $self) {
1838 $self->{fg} = $self->{active_fg};
1839 }
1840 2413
1841 glEnable GL_TEXTURE_2D; 2414 glEnable GL_TEXTURE_2D;
1842 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 2415 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1843 glColor 0, 0, 0, 1; 2416 glColor 0, 0, 0, 1;
1844 2417
1849 $self->SUPER::_draw; 2422 $self->SUPER::_draw;
1850} 2423}
1851 2424
1852############################################################################# 2425#############################################################################
1853 2426
1854package CFClient::UI::CheckBox; 2427package CFPlus::UI::CheckBox;
1855 2428
1856our @ISA = CFClient::UI::DrawBG::; 2429our @ISA = CFPlus::UI::DrawBG::;
1857 2430
1858my @tex = 2431my @tex =
1859 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 2432 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1860 qw(c1_checkbox_bg.png c1_checkbox_active.png); 2433 qw(c1_checkbox_bg.png c1_checkbox_active.png);
1861 2434
1862use CFClient::OpenGL; 2435use CFPlus::OpenGL;
1863 2436
1864sub new { 2437sub new {
1865 my $class = shift; 2438 my $class = shift;
1866 2439
1867 $class->SUPER::new ( 2440 $class->SUPER::new (
1881 my ($self) = @_; 2454 my ($self) = @_;
1882 2455
1883 (6) x 2 2456 (6) x 2
1884} 2457}
1885 2458
2459sub toggle {
2460 my ($self) = @_;
2461
2462 $self->{state} = !$self->{state};
2463 $self->emit (changed => $self->{state});
2464 $self->update;
2465}
2466
1886sub button_down { 2467sub invoke_button_down {
1887 my ($self, $ev, $x, $y) = @_; 2468 my ($self, $ev, $x, $y) = @_;
1888 2469
1889 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x} 2470 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
1890 && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) { 2471 && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
1891 $self->{state} = !$self->{state}; 2472 $self->toggle;
1892 $self->_emit (changed => $self->{state}); 2473 } else {
2474 return 0
2475 }
2476
1893 } 2477 1
1894} 2478}
1895 2479
1896sub _draw { 2480sub _draw {
1897 my ($self) = @_; 2481 my ($self) = @_;
1898 2482
1913 glDisable GL_TEXTURE_2D; 2497 glDisable GL_TEXTURE_2D;
1914} 2498}
1915 2499
1916############################################################################# 2500#############################################################################
1917 2501
1918package CFClient::UI::Image; 2502package CFPlus::UI::Image;
1919 2503
1920our @ISA = CFClient::UI::Base::; 2504our @ISA = CFPlus::UI::Base::;
1921 2505
1922use CFClient::OpenGL; 2506use CFPlus::OpenGL;
1923use Carp qw/confess/;
1924 2507
1925our %loaded_images; 2508our %texture_cache;
1926 2509
1927sub new { 2510sub new {
1928 my $class = shift; 2511 my $class = shift;
1929 2512
1930 my $self = $class->SUPER::new (can_events => 0, @_); 2513 my $self = $class->SUPER::new (
2514 can_events => 0,
2515 @_,
2516 );
1931 2517
1932 $self->{image} or confess "Image has 'image' not set. This is a fatal error!"; 2518 $self->{path} || $self->{tex}
2519 or Carp::croak "'path' or 'tex' attributes required";
1933 2520
1934 $loaded_images{$self->{image}} ||= 2521 $self->{tex} ||= $texture_cache{$self->{path}} ||=
1935 new_from_file CFClient::Texture CFClient::find_rcfile $self->{image}, mipmap => 1; 2522 new_from_file CFPlus::Texture CFPlus::find_rcfile $self->{path}, mipmap => 1;
1936 2523
1937 my $tex = $self->{tex} = $loaded_images{$self->{image}}; 2524 CFPlus::weaken $texture_cache{$self->{path}};
1938 2525
1939 Scalar::Util::weaken $loaded_images{$self->{image}}; 2526 $self->{aspect} ||= $self->{tex}{w} / $self->{tex}{h};
1940
1941 $self->{aspect} = $tex->{w} / $tex->{h};
1942 2527
1943 $self 2528 $self
1944} 2529}
1945 2530
2531sub STORABLE_freeze {
2532 my ($self, $cloning) = @_;
2533
2534 $self->{path}
2535 or die "cannot serialise CFPlus::UI::Image on non-loadable images\n";
2536
2537 $self->{path}
2538}
2539
2540sub STORABLE_attach {
2541 my ($self, $cloning, $path) = @_;
2542
2543 $self->new (path => $path)
2544}
2545
1946sub size_request { 2546sub size_request {
1947 my ($self) = @_; 2547 my ($self) = @_;
1948 2548
1949 ($self->{tex}->{w}, $self->{tex}->{h}) 2549 ($self->{tex}{w}, $self->{tex}{h})
1950} 2550}
1951 2551
1952sub _draw { 2552sub _draw {
1953 my ($self) = @_; 2553 my ($self) = @_;
1954 2554
1964 } 2564 }
1965 2565
1966 glEnable GL_TEXTURE_2D; 2566 glEnable GL_TEXTURE_2D;
1967 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 2567 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1968 2568
1969 $tex->draw_quad_alpha (0, 0, $w, $h); 2569 $tex->draw_quad (0, 0, $w, $h);
1970 2570
1971 glDisable GL_TEXTURE_2D; 2571 glDisable GL_TEXTURE_2D;
1972} 2572}
1973 2573
1974############################################################################# 2574#############################################################################
1975 2575
2576package CFPlus::UI::ImageButton;
2577
2578our @ISA = CFPlus::UI::Image::;
2579
2580use CFPlus::OpenGL;
2581
2582my %textures;
2583
2584sub new {
2585 my $class = shift;
2586
2587 my $self = $class->SUPER::new (
2588 padding_x => 4,
2589 padding_y => 4,
2590 fg => [1, 1, 1],
2591 active_fg => [0, 0, 1],
2592 can_hover => 1,
2593 align => 0,
2594 valign => 0,
2595 can_events => 1,
2596 @_
2597 );
2598}
2599
2600sub invoke_button_up {
2601 my ($self, $ev, $x, $y) = @_;
2602
2603 $self->emit ("activate")
2604 if $x >= 0 && $x < $self->{w}
2605 && $y >= 0 && $y < $self->{h};
2606
2607 1
2608}
2609
2610#############################################################################
2611
1976package CFClient::UI::VGauge; 2612package CFPlus::UI::VGauge;
1977 2613
1978our @ISA = CFClient::UI::Base::; 2614our @ISA = CFPlus::UI::Base::;
1979 2615
1980use List::Util qw(min max); 2616use List::Util qw(min max);
1981 2617
1982use CFClient::OpenGL; 2618use CFPlus::OpenGL;
1983 2619
1984my %tex = ( 2620my %tex = (
1985 food => [ 2621 food => [
1986 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 2622 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1987 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/ 2623 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/
1988 ], 2624 ],
1989 grace => [ 2625 grace => [
1990 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 2626 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1991 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/ 2627 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/
1992 ], 2628 ],
1993 hp => [ 2629 hp => [
1994 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 2630 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1995 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/ 2631 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/
1996 ], 2632 ],
1997 mana => [ 2633 mana => [
1998 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 2634 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
1999 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/ 2635 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/
2000 ], 2636 ],
2001); 2637);
2002 2638
2003# eg. VGauge->new (gauge => 'food'), default gauge: food 2639# eg. VGauge->new (gauge => 'food'), default gauge: food
2063 my $ycut1 = max 0, min 1, $ycut; 2699 my $ycut1 = max 0, min 1, $ycut;
2064 my $ycut2 = max 0, min 1, $ycut - 1; 2700 my $ycut2 = max 0, min 1, $ycut - 1;
2065 2701
2066 my $h1 = $self->{h} * (1 - $ycut1); 2702 my $h1 = $self->{h} * (1 - $ycut1);
2067 my $h2 = $self->{h} * (1 - $ycut2); 2703 my $h2 = $self->{h} * (1 - $ycut2);
2704 my $h3 = $self->{h};
2705
2706 $_ = $_ * (284-4)/288 + 4/288 for ($h1, $h2, $h3);
2068 2707
2069 glEnable GL_BLEND; 2708 glEnable GL_BLEND;
2070 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 2709 glBlendFuncSeparate GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
2710 GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2071 glEnable GL_TEXTURE_2D; 2711 glEnable GL_TEXTURE_2D;
2072 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 2712 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2073 2713
2074 glBindTexture GL_TEXTURE_2D, $t1->{name}; 2714 glBindTexture GL_TEXTURE_2D, $t1->{name};
2075 glBegin GL_QUADS; 2715 glBegin GL_QUADS;
2090 2730
2091 if ($t3) { 2731 if ($t3) {
2092 glBindTexture GL_TEXTURE_2D, $t3->{name}; 2732 glBindTexture GL_TEXTURE_2D, $t3->{name};
2093 glBegin GL_QUADS; 2733 glBegin GL_QUADS;
2094 glTexCoord 0 , $t3->{t} * (1 - $ycut2); glVertex 0 , $h2; 2734 glTexCoord 0 , $t3->{t} * (1 - $ycut2); glVertex 0 , $h2;
2095 glTexCoord 0 , $t3->{t}; glVertex 0 , $self->{h}; 2735 glTexCoord 0 , $t3->{t}; glVertex 0 , $h3;
2096 glTexCoord $t3->{s}, $t3->{t}; glVertex $w, $self->{h}; 2736 glTexCoord $t3->{s}, $t3->{t}; glVertex $w, $h3;
2097 glTexCoord $t3->{s}, $t3->{t} * (1 - $ycut2); glVertex $w, $h2; 2737 glTexCoord $t3->{s}, $t3->{t} * (1 - $ycut2); glVertex $w, $h2;
2098 glEnd; 2738 glEnd;
2099 } 2739 }
2100 2740
2101 glDisable GL_BLEND; 2741 glDisable GL_BLEND;
2102 glDisable GL_TEXTURE_2D; 2742 glDisable GL_TEXTURE_2D;
2103} 2743}
2104 2744
2105############################################################################# 2745#############################################################################
2106 2746
2107package CFClient::UI::Gauge; 2747package CFPlus::UI::Gauge;
2108 2748
2109our @ISA = CFClient::UI::VBox::; 2749our @ISA = CFPlus::UI::VBox::;
2110 2750
2111sub new { 2751sub new {
2112 my ($class, %arg) = @_; 2752 my ($class, %arg) = @_;
2113 2753
2114 my $self = $class->SUPER::new ( 2754 my $self = $class->SUPER::new (
2116 can_hover => 1, 2756 can_hover => 1,
2117 can_events => 1, 2757 can_events => 1,
2118 %arg, 2758 %arg,
2119 ); 2759 );
2120 2760
2121 $self->add ($self->{value} = new CFClient::UI::Label valign => +1, align => 0, template => "999"); 2761 $self->add ($self->{value} = new CFPlus::UI::Label valign => +1, align => 0, template => "999");
2122 $self->add ($self->{gauge} = new CFClient::UI::VGauge type => $self->{type}, expand => 1, can_hover => 1); 2762 $self->add ($self->{gauge} = new CFPlus::UI::VGauge type => $self->{type}, expand => 1, can_hover => 1);
2123 $self->add ($self->{max} = new CFClient::UI::Label valign => -1, align => 0, template => "999"); 2763 $self->add ($self->{max} = new CFPlus::UI::Label valign => -1, align => 0, template => "999");
2124 2764
2125 $self 2765 $self
2126} 2766}
2127 2767
2128sub set_fontsize { 2768sub set_fontsize {
2149 $self->{value}->set_text ($val); 2789 $self->{value}->set_text ($val);
2150} 2790}
2151 2791
2152############################################################################# 2792#############################################################################
2153 2793
2154package CFClient::UI::Slider; 2794package CFPlus::UI::Slider;
2155 2795
2156use strict; 2796use strict;
2157 2797
2158use CFClient::OpenGL; 2798use CFPlus::OpenGL;
2159 2799
2160our @ISA = CFClient::UI::DrawBG::; 2800our @ISA = CFPlus::UI::DrawBG::;
2161 2801
2162my @tex = 2802my @tex =
2163 map { new_from_file CFClient::Texture CFClient::find_rcfile $_ } 2803 map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_ }
2164 qw(s1_slider.png s1_slider_bg.png); 2804 qw(s1_slider.png s1_slider_bg.png);
2165 2805
2166sub new { 2806sub new {
2167 my $class = shift; 2807 my $class = shift;
2168 2808
2189 $self->update; 2829 $self->update;
2190 2830
2191 $self 2831 $self
2192} 2832}
2193 2833
2194sub changed { }
2195
2196sub set_range { 2834sub set_range {
2197 my ($self, $range) = @_; 2835 my ($self, $range) = @_;
2198 2836
2199 ($range, $self->{range}) = ($self->{range}, $range); 2837 ($range, $self->{range}) = ($self->{range}, $range);
2200 2838
2201 $self->update
2202 if "@$range" ne "@{$self->{range}}"; 2839 if ("@$range" ne "@{$self->{range}}") {
2840 $self->update;
2841 $self->set_value ($self->{range}[0]);
2842 }
2203} 2843}
2204 2844
2205sub set_value { 2845sub set_value {
2206 my ($self, $value) = @_; 2846 my ($self, $value) = @_;
2207 2847
2218 if $unit; 2858 if $unit;
2219 2859
2220 @{$self->{range}} = ($value, $lo, $hi, $page, $unit); 2860 @{$self->{range}} = ($value, $lo, $hi, $page, $unit);
2221 2861
2222 if ($value != $old_value) { 2862 if ($value != $old_value) {
2223 $self->_emit (changed => $value); 2863 $self->emit (changed => $value);
2224 $self->update; 2864 $self->update;
2225 } 2865 }
2226} 2866}
2227 2867
2228sub size_request { 2868sub size_request {
2229 my ($self) = @_; 2869 my ($self) = @_;
2230 2870
2231 ($self->{req_w}, $self->{req_h}) 2871 ($self->{req_w}, $self->{req_h})
2232} 2872}
2233 2873
2234sub button_down { 2874sub invoke_button_down {
2235 my ($self, $ev, $x, $y) = @_; 2875 my ($self, $ev, $x, $y) = @_;
2236 2876
2237 $self->SUPER::button_down ($ev, $x, $y); 2877 $self->SUPER::invoke_button_down ($ev, $x, $y);
2238 2878
2239 $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x]; 2879 $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x];
2240 2880
2241 $self->mouse_motion ($ev, $x, $y); 2881 $self->invoke_mouse_motion ($ev, $x, $y)
2242} 2882}
2243 2883
2244sub mouse_motion { 2884sub invoke_mouse_motion {
2245 my ($self, $ev, $x, $y) = @_; 2885 my ($self, $ev, $x, $y) = @_;
2246 2886
2247 if ($GRAB == $self) { 2887 if ($GRAB == $self) {
2248 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w}); 2888 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w});
2249 2889
2250 my (undef, $lo, $hi, $page) = @{$self->{range}}; 2890 my (undef, $lo, $hi, $page) = @{$self->{range}};
2251 2891
2252 $x = ($x - $self->{click}[1]) / ($w * $self->{scale}); 2892 $x = ($x - $self->{click}[1]) / ($w * $self->{scale});
2253 2893
2254 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo)); 2894 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo));
2895 } else {
2896 return 0;
2897 }
2898
2255 } 2899 1
2900}
2901
2902sub invoke_mouse_wheel {
2903 my ($self, $ev) = @_;
2904
2905 my $delta = $self->{vertical} ? $ev->{dy} : $ev->{dx};
2906
2907 my $pagepart = $ev->{mod} & CFPlus::KMOD_SHIFT ? 1 : 0.2;
2908
2909 $self->set_value ($self->{range}[0] + $delta * $self->{range}[3] * $pagepart);
2910
2911 ! ! $delta
2256} 2912}
2257 2913
2258sub update { 2914sub update {
2259 my ($self) = @_; 2915 my ($self) = @_;
2260 2916
2261 $CFClient::UI::ROOT->on_post_alloc ($self => sub { 2917 delete $self->{knob_w};
2918 $self->SUPER::update;
2919}
2920
2921sub _draw {
2922 my ($self) = @_;
2923
2924 unless ($self->{knob_w}) {
2262 $self->set_value ($self->{range}[0]); 2925 $self->set_value ($self->{range}[0]);
2263 2926
2264 my ($value, $lo, $hi, $page) = @{$self->{range}}; 2927 my ($value, $lo, $hi, $page) = @{$self->{range}};
2265 my $range = ($hi - $page - $lo) || 1e-100; 2928 my $range = ($hi - $page - $lo) || 1e-100;
2266 2929
2272 $value = ($value - $lo) / $range; 2935 $value = ($value - $lo) / $range;
2273 $value = $value * $self->{scale} + $self->{offset}; 2936 $value = $value * $self->{scale} + $self->{offset};
2274 2937
2275 $self->{knob_x} = $value - $knob_w * 0.5; 2938 $self->{knob_x} = $value - $knob_w * 0.5;
2276 $self->{knob_w} = $knob_w; 2939 $self->{knob_w} = $knob_w;
2277 }); 2940 }
2278
2279 $self->SUPER::update;
2280}
2281
2282sub _draw {
2283 my ($self) = @_;
2284 2941
2285 $self->SUPER::_draw (); 2942 $self->SUPER::_draw ();
2286 2943
2287 glScale $self->{w}, $self->{h}; 2944 glScale $self->{w}, $self->{h};
2288 2945
2308 glDisable GL_TEXTURE_2D; 2965 glDisable GL_TEXTURE_2D;
2309} 2966}
2310 2967
2311############################################################################# 2968#############################################################################
2312 2969
2313package CFClient::UI::ValSlider; 2970package CFPlus::UI::ValSlider;
2314 2971
2315our @ISA = CFClient::UI::HBox::; 2972our @ISA = CFPlus::UI::HBox::;
2316 2973
2317sub new { 2974sub new {
2318 my ($class, %arg) = @_; 2975 my ($class, %arg) = @_;
2319 2976
2320 my $range = delete $arg{range}; 2977 my $range = delete $arg{range};
2321 2978
2322 my $self = $class->SUPER::new ( 2979 my $self = $class->SUPER::new (
2323 slider => (new CFClient::UI::Slider expand => 1, range => $range), 2980 slider => (new CFPlus::UI::Slider expand => 1, range => $range),
2324 entry => (new CFClient::UI::Label text => "", template => delete $arg{template}), 2981 entry => (new CFPlus::UI::Label text => "", template => delete $arg{template}),
2325 to_value => sub { shift }, 2982 to_value => sub { shift },
2326 from_value => sub { shift }, 2983 from_value => sub { shift },
2327 %arg, 2984 %arg,
2328 ); 2985 );
2329 2986
2349sub set_range { shift->{slider}->set_range (@_) } 3006sub set_range { shift->{slider}->set_range (@_) }
2350sub set_value { shift->{slider}->set_value (@_) } 3007sub set_value { shift->{slider}->set_value (@_) }
2351 3008
2352############################################################################# 3009#############################################################################
2353 3010
2354package CFClient::UI::TextView; 3011package CFPlus::UI::TextScroller;
2355 3012
2356our @ISA = CFClient::UI::HBox::; 3013our @ISA = CFPlus::UI::HBox::;
2357 3014
2358use CFClient::OpenGL; 3015use CFPlus::OpenGL;
2359 3016
2360sub new { 3017sub new {
2361 my $class = shift; 3018 my $class = shift;
2362 3019
2363 my $self = $class->SUPER::new ( 3020 my $self = $class->SUPER::new (
2364 fontsize => 1, 3021 fontsize => 1,
2365 can_events => 0, 3022 can_events => 1,
3023 indent => 0,
2366 #font => default_font 3024 #font => default_font
2367 @_, 3025 @_,
2368 3026
2369 layout => (new CFClient::Layout 1), 3027 layout => (new CFPlus::Layout),
2370 par => [], 3028 par => [],
3029 max_par => 0,
2371 height => 0, 3030 height => 0,
2372 children => [ 3031 children => [
2373 (new CFClient::UI::Empty expand => 1), 3032 (new CFPlus::UI::Empty expand => 1),
2374 (new CFClient::UI::Slider vertical => 1), 3033 (new CFPlus::UI::Slider vertical => 1),
2375 ], 3034 ],
2376 ); 3035 );
2377 3036
2378 $self->{children}[1]->connect (changed => sub { $self->update }); 3037 $self->{children}[1]->connect (changed => sub { $self->update });
2379 3038
2385 3044
2386 $self->{fontsize} = $fontsize; 3045 $self->{fontsize} = $fontsize;
2387 $self->reflow; 3046 $self->reflow;
2388} 3047}
2389 3048
3049sub size_request {
3050 my ($self) = @_;
3051
3052 my ($empty, $slider) = @{ $self->{children} };
3053
3054 local $self->{children} = [$empty, $slider];
3055 $self->SUPER::size_request
3056}
3057
2390sub size_allocate { 3058sub invoke_size_allocate {
2391 my ($self, $w, $h) = @_; 3059 my ($self, $w, $h) = @_;
2392 3060
2393 $self->SUPER::size_allocate ($w, $h); 3061 my ($empty, $slider, @other) = @{ $self->{children} };
3062 $_->configure (@$_{qw(x y req_w req_h)}) for @other;
2394 3063
2395 $self->{layout}->set_font ($self->{font}) if $self->{font}; 3064 $self->{layout}->set_font ($self->{font}) if $self->{font};
2396 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 3065 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
2397 $self->{layout}->set_width ($self->{children}[0]{w}); 3066 $self->{layout}->set_width ($empty->{w});
3067 $self->{layout}->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent});
2398 3068
2399 $self->reflow; 3069 $self->reflow;
2400}
2401 3070
2402sub text_size { 3071 local $self->{children} = [$empty, $slider];
3072 $self->SUPER::invoke_size_allocate ($w, $h)
3073}
3074
3075sub invoke_mouse_wheel {
2403 my ($self, $text, $indent) = @_; 3076 my ($self, $ev) = @_;
3077
3078 return 0 unless $ev->{dy}; # only vertical movements
3079
3080 $self->{children}[1]->emit (mouse_wheel => $ev);
3081
3082 1
3083}
3084
3085sub get_layout {
3086 my ($self, $para) = @_;
2404 3087
2405 my $layout = $self->{layout}; 3088 my $layout = $self->{layout};
2406 3089
3090 $layout->set_font ($self->{font}) if $self->{font};
3091 $layout->set_foreground (@{$para->{fg}});
2407 $layout->set_height ($self->{fontsize} * $::FONTSIZE); 3092 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2408 $layout->set_width ($self->{children}[0]{w} - $indent); 3093 $layout->set_width ($self->{children}[0]{w} - $para->{indent});
3094 $layout->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent});
2409 $layout->set_markup ($text); 3095 $layout->set_markup ($para->{markup});
3096
3097 $layout->set_shapes (
3098 map
3099 +(0, $_->baseline_shift +$_->{padding_y} - $_->{h}, $_->{w}, $_->{h}),
3100 @{$para->{widget}}
2410 3101 );
3102
2411 $layout->size 3103 $layout
2412} 3104}
2413 3105
2414sub reflow { 3106sub reflow {
2415 my ($self) = @_; 3107 my ($self) = @_;
2416 3108
2423 3115
2424 # todo: base offset on lines or so, not on pixels 3116 # todo: base offset on lines or so, not on pixels
2425 $self->{children}[1]->set_value ($offset); 3117 $self->{children}[1]->set_value ($offset);
2426} 3118}
2427 3119
3120sub current_paragraph {
3121 my ($self) = @_;
3122
3123 $self->{top_paragraph} - 1
3124}
3125
3126sub scroll_to {
3127 my ($self, $para) = @_;
3128
3129 $para = List::Util::max 0, List::Util::min $#{$self->{par}}, $para;
3130
3131 $self->{scroll_to} = $para;
3132 $self->update;
3133}
3134
2428sub clear { 3135sub clear {
2429 my ($self) = @_; 3136 my ($self) = @_;
3137
3138 my (undef, undef, @other) = @{ $self->{children} };
3139 $self->remove ($_) for @other;
2430 3140
2431 $self->{par} = []; 3141 $self->{par} = [];
2432 $self->{height} = 0; 3142 $self->{height} = 0;
2433 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]); 3143 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]);
2434} 3144}
2435 3145
2436sub add_paragraph { 3146sub add_paragraph {
2437 my ($self, $color, $text, $indent) = @_; 3147 my $self = shift;
2438 3148
2439 for my $line (split /\n/, $text) { 3149 for my $para (@_) {
2440 my ($w, $h) = $self->text_size ($line); 3150 $para = {
3151 fg => [1, 1, 1, 1],
3152 indent => 0,
3153 markup => "",
3154 widget => [],
3155 ref $para ? %$para : (markup => $para),
3156 w => 1e10,
3157 wrapped => 1,
3158 };
3159
3160 $self->add (@{ $para->{widget} }) if @{ $para->{widget} };
3161 push @{$self->{par}}, $para;
3162 }
3163
3164 if (my $max = $self->{max_par}) {
3165 shift @{$self->{par}} while @{$self->{par}} > $max;
3166 }
3167
3168 $self->{need_reflow}++;
3169 $self->update;
3170}
3171
3172sub scroll_to_bottom {
3173 my ($self) = @_;
3174
3175 $self->{scroll_to} = $#{$self->{par}};
3176 $self->update;
3177}
3178
3179sub force_uptodate {
3180 my ($self) = @_;
3181
3182 if (delete $self->{need_reflow}) {
3183 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
3184
3185 my $height = 0;
3186
3187 for my $para (@{$self->{par}}) {
3188 if ($para->{w} != $W && ($para->{wrapped} || $para->{w} > $W)) {
3189 my $layout = $self->get_layout ($para);
3190 my ($w, $h) = $layout->size;
3191
3192 $para->{w} = $w + $para->{indent};
3193 $para->{h} = $h;
3194 $para->{wrapped} = $layout->has_wrapped;
3195 }
3196
3197 $para->{y} = $height;
3198 $height += $para->{h};
3199 }
3200
2441 $self->{height} += $h; 3201 $self->{height} = $height;
2442 push @{$self->{par}}, [$w + $indent, $h, $color, $indent, $line]; 3202 $self->{children}[1]->set_range ([$self->{children}[1]{range}[0], 0, $height, $H, 1]);
2443 }
2444 3203
2445 $self->{children}[1]->set_range ([$self->{height}, 0, $self->{height}, $self->{h}, 1]); 3204 delete $self->{texture};
3205 }
3206
3207 if (my $paridx = delete $self->{scroll_to}) {
3208 $self->{children}[1]->set_value ($self->{par}[$paridx]{y});
3209 }
2446} 3210}
2447 3211
2448sub update { 3212sub update {
2449 my ($self) = @_; 3213 my ($self) = @_;
2450 3214
2452 3216
2453 return unless $self->{h} > 0; 3217 return unless $self->{h} > 0;
2454 3218
2455 delete $self->{texture}; 3219 delete $self->{texture};
2456 3220
2457 $ROOT->on_post_alloc ($self, sub { 3221 $ROOT->on_post_alloc ($self => sub {
3222 $self->force_uptodate;
3223
2458 my ($W, $H) = @{$self->{children}[0]}{qw(w h)}; 3224 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
2459 3225
2460 if (delete $self->{need_reflow}) {
2461 my $height = 0;
2462
2463 my $layout = $self->{layout};
2464
2465 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2466
2467 for (@{$self->{par}}) {
2468 if (1 || $_->[0] >= $W) { # TODO: works,but needs reconfigure etc. support
2469 $layout->set_width ($W - $_->[3]);
2470 $layout->set_markup ($_->[4]);
2471 my ($w, $h) = $layout->size;
2472 $_->[0] = $w + $_->[3];
2473 $_->[1] = $h;
2474 }
2475
2476 $height += $_->[1];
2477 }
2478
2479 $self->{height} = $height;
2480
2481 $self->{children}[1]->set_range ([$height, 0, $height, $H, 1]);
2482
2483 delete $self->{texture};
2484 }
2485
2486 $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub { 3226 $self->{texture} ||= new_from_opengl CFPlus::Texture $W, $H, sub {
2487 glClearColor 0.5, 0.5, 0.5, 0; 3227 glClearColor 0, 0, 0, 0;
2488 glClear GL_COLOR_BUFFER_BIT; 3228 glClear GL_COLOR_BUFFER_BIT;
2489 3229
3230 package CFPlus::UI::Base;
3231 local ($draw_x, $draw_y, $draw_w, $draw_h) =
3232 (0, 0, $self->{w}, $self->{h});
3233
3234 my $top = int $self->{children}[1]{range}[0];
3235
3236 my $paridx = 0;
3237 my $top_paragraph;
2490 my $top = int $self->{children}[1]{range}[0]; 3238 my $top = int $self->{children}[1]{range}[0];
2491 3239
2492 my $y0 = $top; 3240 my $y0 = $top;
2493 my $y1 = $top + $H; 3241 my $y1 = $top + $H;
2494 3242
2495 my $y = 0;
2496
2497 my $layout = $self->{layout};
2498
2499 $layout->set_font ($self->{font}) if $self->{font};
2500
2501 glEnable GL_BLEND;
2502 #TODO# not correct in windows where rgba is forced off
2503 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2504
2505 for my $par (@{$self->{par}}) { 3243 for my $para (@{$self->{par}}) {
2506 my $h = $par->[1]; 3244 my $h = $para->{h};
3245 my $y = $para->{y};
2507 3246
2508 if ($y0 < $y + $h && $y < $y1) { 3247 if ($y0 < $y + $h && $y < $y1) {
2509 $layout->set_foreground (@{ $par->[2] }); 3248 my $layout = $self->get_layout ($para);
2510 $layout->set_width ($W - $par->[3]);
2511 $layout->set_markup ($par->[4]);
2512 3249
2513 my ($w, $h, $data, $format, $internalformat) = $layout->render; 3250 $layout->render ($para->{indent}, $y - $y0);
2514 3251
2515 glRasterPos $par->[3], $y - $y0; 3252 if (my @w = @{ $para->{widget} }) {
2516 glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data; 3253 my @s = $layout->get_shapes;
3254
3255 for (@w) {
3256 my ($dx, $dy) = splice @s, 0, 2, ();
3257
3258 $_->{x} = $dx + $para->{indent};
3259 $_->{y} = $dy + $y - $y0;
3260
3261 $_->draw;
3262 }
3263 }
2517 } 3264 }
2518 3265
2519 $y += $h; 3266 $paridx++;
3267 $top_paragraph ||= $paridx if $y >= $top;
2520 } 3268 }
2521 3269
2522 glDisable GL_BLEND; 3270 $self->{top_paragraph} = $top_paragraph;
2523 }; 3271 };
2524 }); 3272 });
2525} 3273}
2526 3274
3275sub reconfigure {
3276 my ($self) = @_;
3277
3278 $self->SUPER::reconfigure;
3279
3280 $_->{w} = 1e10 for @{ $self->{par} };
3281 $self->reflow;
3282}
3283
2527sub _draw { 3284sub _draw {
2528 my ($self) = @_; 3285 my ($self) = @_;
2529 3286
2530 glEnable GL_TEXTURE_2D; 3287 glEnable GL_TEXTURE_2D;
2531 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 3288 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2532 glColor 1, 1, 1, 1; 3289 glColor 0, 0, 0, 1;
2533 $self->{texture}->draw_quad_alpha (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h}); 3290 $self->{texture}->draw_quad_alpha_premultiplied (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h});
2534 glDisable GL_TEXTURE_2D; 3291 glDisable GL_TEXTURE_2D;
2535 3292
2536 $self->{children}[1]->draw; 3293 $self->{children}[1]->draw;
2537
2538} 3294}
2539 3295
2540############################################################################# 3296#############################################################################
2541 3297
2542package CFClient::UI::Animator; 3298package CFPlus::UI::Animator;
2543 3299
2544use CFClient::OpenGL; 3300use CFPlus::OpenGL;
2545 3301
2546our @ISA = CFClient::UI::Bin::; 3302our @ISA = CFPlus::UI::Bin::;
2547 3303
2548sub moveto { 3304sub moveto {
2549 my ($self, $x, $y) = @_; 3305 my ($self, $x, $y) = @_;
2550 3306
2551 $self->{moveto} = [$self->{x}, $self->{y}, $x, $y]; 3307 $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
2579 glPopMatrix; 3335 glPopMatrix;
2580} 3336}
2581 3337
2582############################################################################# 3338#############################################################################
2583 3339
2584package CFClient::UI::Flopper; 3340package CFPlus::UI::Flopper;
2585 3341
2586our @ISA = CFClient::UI::Button::; 3342our @ISA = CFPlus::UI::Button::;
2587 3343
2588sub new { 3344sub new {
2589 my $class = shift; 3345 my $class = shift;
2590 3346
2591 my $self = $class->SUPER::new ( 3347 my $self = $class->SUPER::new (
2603 $self->{other}->toggle_visibility; 3359 $self->{other}->toggle_visibility;
2604} 3360}
2605 3361
2606############################################################################# 3362#############################################################################
2607 3363
2608package CFClient::UI::Tooltip; 3364package CFPlus::UI::Tooltip;
2609 3365
2610our @ISA = CFClient::UI::Bin::; 3366our @ISA = CFPlus::UI::Bin::;
2611 3367
2612use CFClient::OpenGL; 3368use CFPlus::OpenGL;
2613 3369
2614sub new { 3370sub new {
2615 my $class = shift; 3371 my $class = shift;
2616 3372
2617 $class->SUPER::new ( 3373 $class->SUPER::new (
2620 ) 3376 )
2621} 3377}
2622 3378
2623sub set_tooltip_from { 3379sub set_tooltip_from {
2624 my ($self, $widget) = @_; 3380 my ($self, $widget) = @_;
3381
3382 $widget->{tooltip} = CFPlus::Pod::section_label tooltip => $1
3383 if $widget->{tooltip} =~ /^#(.*)$/;
2625 3384
2626 my $tooltip = $widget->{tooltip}; 3385 my $tooltip = $widget->{tooltip};
2627 3386
2628 if ($ENV{CFPLUS_DEBUG} & 2) { 3387 if ($ENV{CFPLUS_DEBUG} & 2) {
2629 $tooltip .= "\n\n" . (ref $widget) . "\n" 3388 $tooltip .= "\n\n" . (ref $widget) . "\n"
2630 . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n" 3389 . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n"
2631 . "req $widget->{req_w} $widget->{req_h}\n" 3390 . "req $widget->{req_w} $widget->{req_h}\n"
2632 . "visible $widget->{visible}"; 3391 . "visible $widget->{visible}";
2633 } 3392 }
2634 3393
3394 $tooltip =~ s/^\n+//;
3395 $tooltip =~ s/\n+$//;
3396
2635 $self->add (new CFClient::UI::Label 3397 $self->add (new CFPlus::UI::Label
2636 markup => $tooltip, 3398 markup => $tooltip,
2637 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH, 3399 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH,
2638 fontsize => 0.8, 3400 fontsize => 0.8,
2639 fg => [0, 0, 0, 1], 3401 style => 1, # FLAG_INVERSE
2640 ellipsise => 0, 3402 ellipsise => 0,
2641 font => ($widget->{tooltip_font} || $::FONT_PROP), 3403 font => ($widget->{tooltip_font} || $::FONT_PROP),
2642 ); 3404 );
2643} 3405}
2644 3406
2648 my ($w, $h) = @{$self->child}{qw(req_w req_h)}; 3410 my ($w, $h) = @{$self->child}{qw(req_w req_h)};
2649 3411
2650 ($w + 4, $h + 4) 3412 ($w + 4, $h + 4)
2651} 3413}
2652 3414
2653sub size_allocate { 3415sub invoke_size_allocate {
2654 my ($self, $w, $h) = @_; 3416 my ($self, $w, $h) = @_;
2655 3417
2656 $self->SUPER::size_allocate ($w - 4, $h - 4); 3418 $self->SUPER::invoke_size_allocate ($w - 4, $h - 4)
2657} 3419}
2658 3420
2659sub visibility_change { 3421sub invoke_visibility_change {
2660 my ($self, $visible) = @_; 3422 my ($self, $visible) = @_;
2661 3423
2662 return unless $visible; 3424 return unless $visible;
2663 3425
2664 $self->{root}->on_post_alloc ("move_$self" => sub { 3426 $self->{root}->on_post_alloc ("move_$self" => sub {
2665 my $widget = $self->{owner} 3427 my $widget = $self->{owner}
2666 or return; 3428 or return;
2667 3429
3430 if ($widget->{visible}) {
2668 my ($x, $y) = $widget->coord2global ($widget->{w}, 0); 3431 my ($x, $y) = $widget->coord2global ($widget->{w}, 0);
2669 3432
2670 ($x, $y) = $widget->coord2global (-$self->{w}, 0) 3433 ($x, $y) = $widget->coord2global (-$self->{w}, 0)
2671 if $x + $self->{w} > $::WIDTH; 3434 if $x + $self->{w} > $self->{root}{w};
2672 3435
2673 $self->move_abs ($x, $y); 3436 $self->move_abs ($x, $y);
3437 } else {
3438 $self->hide;
3439 }
2674 }); 3440 });
2675} 3441}
2676 3442
2677sub _draw { 3443sub _draw {
2678 my ($self) = @_; 3444 my ($self) = @_;
2702 $self->SUPER::_draw; 3468 $self->SUPER::_draw;
2703} 3469}
2704 3470
2705############################################################################# 3471#############################################################################
2706 3472
2707package CFClient::UI::Face; 3473package CFPlus::UI::Face;
2708 3474
2709our @ISA = CFClient::UI::Base::; 3475our @ISA = CFPlus::UI::DrawBG::;
2710 3476
2711use CFClient::OpenGL; 3477use CFPlus::OpenGL;
2712 3478
2713sub new { 3479sub new {
2714 my $class = shift; 3480 my $class = shift;
2715 3481
2716 my $self = $class->SUPER::new ( 3482 my $self = $class->SUPER::new (
3483 size_w => 32,
3484 size_h => 8,
2717 aspect => 1, 3485 aspect => 1,
2718 can_events => 0, 3486 can_events => 0,
2719 @_, 3487 @_,
2720 ); 3488 );
2721 3489
2722 if ($self->{anim} && $self->{animspeed}) { 3490 if ($self->{anim} && $self->{animspeed}) {
2723 Scalar::Util::weaken (my $widget = $self); 3491 CFPlus::weaken (my $widget = $self);
2724 3492
2725 $self->{timer} = Event->timer ( 3493 $self->{timer} = Event->timer (
2726 at => $self->{animspeed} * int $::NOW / $self->{animspeed}, 3494 at => $self->{animspeed} * int $::NOW / $self->{animspeed},
2727 hard => 1, 3495 hard => 1,
2728 interval => $self->{animspeed}, 3496 interval => $self->{animspeed},
2729 cb => sub { 3497 cb => sub {
3498 return unless $::CONN;
3499
2730 ++$widget->{frame}; 3500 ++$widget->{frame};
3501 $self->update_face;
2731 $widget->update; 3502 $self->update;
2732 }, 3503 },
2733 ); 3504 );
3505
3506 $self->update_face;
2734 } 3507 }
2735 3508
2736 $self 3509 $self
2737} 3510}
2738 3511
3512sub update_face {
3513 my ($self) = @_;
3514
3515 return unless $::CONN;
3516
3517 if (my $anim = $::CONN->{anim}[$self->{anim}]) {
3518 if ($anim && @$anim) {
3519 delete $self->{wait_face};
3520 $self->{face} = $anim->[ $self->{frame} % @$anim ];
3521 }
3522 }
3523}
3524
2739sub size_request { 3525sub size_request {
2740 (32, 8) 3526 my ($self) = @_;
3527
3528 if ($::CONN) {
3529 if (my $faceid = $::CONN->{faceid}[$self->{face}]) {
3530 if (my $tex = $::CONN->{texture}[$faceid]) {
3531 return ($self->{size_w} || $tex->{w}, $self->{size_h} || $tex->{h});
3532 } else {
3533 $self->{wait_face} ||= $::CONN->connect_face_update ($faceid, sub {
3534 $self->realloc;
3535 });
3536 }
3537 }
3538 }
3539
3540 ($self->{size_w} || 8, $self->{size_h} || 8)
2741} 3541}
2742 3542
2743sub update { 3543sub update {
2744 my ($self) = @_; 3544 my ($self) = @_;
2745 3545
2751sub _draw { 3551sub _draw {
2752 my ($self) = @_; 3552 my ($self) = @_;
2753 3553
2754 return unless $::CONN; 3554 return unless $::CONN;
2755 3555
2756 my $face; 3556 $self->SUPER::_draw;
2757 3557
2758 if ($self->{frame}) {
2759 my $anim = $::CONN->{anim}[$self->{anim}]; 3558 my $faceid = $::CONN->{faceid}[$self->{face}]
2760 3559 or return;
2761 $face = $anim->[ $self->{frame} % @$anim ]
2762 if $anim && @$anim;
2763 }
2764 3560
2765 my $tex = $::CONN->{texture}[$::CONN->{faceid}[$face || $self->{face}]]; 3561 my $tex = $::CONN->{texture}[$faceid];
2766 3562
2767 if ($tex) { 3563 if ($tex) {
2768 glEnable GL_TEXTURE_2D; 3564 glEnable GL_TEXTURE_2D;
2769 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 3565 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2770 glColor 1, 1, 1, 1; 3566 glColor 0, 0, 0, 1;
2771 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h}); 3567 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2772 glDisable GL_TEXTURE_2D; 3568 glDisable GL_TEXTURE_2D;
2773 } 3569 }
2774} 3570}
2775 3571
2776sub DESTROY { 3572sub destroy {
2777 my ($self) = @_; 3573 my ($self) = @_;
2778 3574
2779 $self->{timer}->cancel 3575 $self->{timer}->cancel
2780 if $self->{timer}; 3576 if $self->{timer};
2781 3577
2782 $self->SUPER::DESTROY; 3578 $self->SUPER::destroy;
2783} 3579}
2784 3580
2785############################################################################# 3581#############################################################################
2786 3582
3583package CFPlus::UI::Buttonbar;
3584
3585our @ISA = CFPlus::UI::HBox::;
3586
3587# TODO: should actually wrap buttons and other goodies.
3588
3589#############################################################################
3590
2787package CFClient::UI::Menu; 3591package CFPlus::UI::Menu;
2788 3592
2789our @ISA = CFClient::UI::FancyFrame::; 3593our @ISA = CFPlus::UI::Toplevel::;
2790 3594
2791use CFClient::OpenGL; 3595use CFPlus::OpenGL;
2792 3596
2793sub new { 3597sub new {
2794 my $class = shift; 3598 my $class = shift;
2795 3599
2796 my $self = $class->SUPER::new ( 3600 my $self = $class->SUPER::new (
2797 items => [], 3601 items => [],
2798 z => 100, 3602 z => 100,
2799 @_, 3603 @_,
2800 ); 3604 );
2801 3605
2802 $self->add ($self->{vbox} = new CFClient::UI::VBox); 3606 $self->add ($self->{vbox} = new CFPlus::UI::VBox);
2803 3607
2804 for my $item (@{ $self->{items} }) { 3608 for my $item (@{ $self->{items} }) {
2805 my ($widget, $cb) = @$item; 3609 my ($widget, $cb, $tooltip) = @$item;
2806 3610
2807 # handle various types of items, only text for now 3611 # handle various types of items, only text for now
2808 if (!ref $widget) { 3612 if (!ref $widget) {
3613 if ($widget =~ /\t/) {
3614 my ($left, $right) = split /\t/, $widget, 2;
3615
2809 $widget = new CFClient::UI::Label 3616 $widget = new CFPlus::UI::HBox
2810 can_hover => 1, 3617 can_hover => 1,
2811 can_events => 1, 3618 can_events => 1,
3619 tooltip => $tooltip,
3620 children => [
3621 (new CFPlus::UI::Label markup => $left, expand => 1),
3622 (new CFPlus::UI::Label markup => $right, align => +1),
3623 ],
3624 ;
3625
3626 } else {
3627 $widget = new CFPlus::UI::Label
3628 can_hover => 1,
3629 can_events => 1,
2812 text => $widget; 3630 markup => $widget,
3631 tooltip => $tooltip;
3632 }
2813 } 3633 }
2814 3634
2815 $self->{item}{$widget} = $item; 3635 $self->{item}{$widget} = $item;
2816 3636
2817 $self->{vbox}->add ($widget); 3637 $self->{vbox}->add ($widget);
2822 3642
2823# popup given the event (must be a mouse button down event currently) 3643# popup given the event (must be a mouse button down event currently)
2824sub popup { 3644sub popup {
2825 my ($self, $ev) = @_; 3645 my ($self, $ev) = @_;
2826 3646
2827 $self->_emit ("popdown"); 3647 $self->emit ("popdown");
2828 3648
2829 # maybe save $GRAB? must be careful about events... 3649 # maybe save $GRAB? must be careful about events...
2830 $GRAB = $self; 3650 $GRAB = $self;
2831 $self->{button} = $ev->{button}; 3651 $self->{button} = $ev->{button};
2832 3652
2833 $self->show; 3653 $self->show;
2834 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5); 3654 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5);
2835} 3655}
2836 3656
2837sub mouse_motion { 3657sub invoke_mouse_motion {
2838 my ($self, $ev, $x, $y) = @_; 3658 my ($self, $ev, $x, $y) = @_;
2839 3659
2840 # TODO: should use vbox->find_widget or so 3660 # TODO: should use vbox->find_widget or so
2841 $HOVER = $ROOT->find_widget ($ev->{x}, $ev->{y}); 3661 $HOVER = $ROOT->find_widget ($ev->{x}, $ev->{y});
2842 $self->{hover} = $self->{item}{$HOVER}; 3662 $self->{hover} = $self->{item}{$HOVER};
2843}
2844 3663
3664 0
3665}
3666
2845sub button_up { 3667sub invoke_button_up {
2846 my ($self, $ev, $x, $y) = @_; 3668 my ($self, $ev, $x, $y) = @_;
2847 3669
2848 if ($ev->{button} == $self->{button}) { 3670 if ($ev->{button} == $self->{button}) {
2849 undef $GRAB; 3671 undef $GRAB;
2850 $self->hide; 3672 $self->hide;
2851 3673
2852 $self->_emit ("popdown"); 3674 $self->emit ("popdown");
2853 $self->{hover}[1]->() if $self->{hover}; 3675 $self->{hover}[1]->() if $self->{hover};
3676 } else {
3677 return 0
3678 }
3679
2854 } 3680 1
2855} 3681}
2856 3682
2857############################################################################# 3683#############################################################################
2858 3684
2859package CFClient::UI::Statusbox; 3685package CFPlus::UI::Multiplexer;
2860 3686
2861our @ISA = CFClient::UI::VBox::; 3687our @ISA = CFPlus::UI::Container::;
2862 3688
2863sub new { 3689sub new {
2864 my $class = shift; 3690 my $class = shift;
2865 3691
2866 $class->SUPER::new ( 3692 my $self = $class->SUPER::new (
3693 @_,
3694 );
3695
3696 $self->{current} = $self->{children}[0]
3697 if @{ $self->{children} };
3698
3699 $self
3700}
3701
3702sub add {
3703 my ($self, @widgets) = @_;
3704
3705 $self->SUPER::add (@widgets);
3706
3707 $self->{current} = $self->{children}[0]
3708 if @{ $self->{children} };
3709}
3710
3711sub get_current_page {
3712 my ($self) = @_;
3713
3714 $self->{current}
3715}
3716
3717sub set_current_page {
3718 my ($self, $page_or_widget) = @_;
3719
3720 my $widget = ref $page_or_widget
3721 ? $page_or_widget
3722 : $self->{children}[$page_or_widget];
3723
3724 $self->{current} = $widget;
3725 $self->{current}->configure (0, 0, $self->{w}, $self->{h});
3726
3727 $self->emit (page_changed => $self->{current});
3728
3729 $self->realloc;
3730}
3731
3732sub visible_children {
3733 $_[0]{current}
3734}
3735
3736sub size_request {
3737 my ($self) = @_;
3738
3739 $self->{current}->size_request
3740}
3741
3742sub invoke_size_allocate {
3743 my ($self, $w, $h) = @_;
3744
3745 $self->{current}->configure (0, 0, $w, $h);
3746
3747 1
3748}
3749
3750sub _draw {
3751 my ($self) = @_;
3752
3753 $self->{current}->draw;
3754}
3755
3756#############################################################################
3757
3758package CFPlus::UI::Notebook;
3759
3760our @ISA = CFPlus::UI::VBox::;
3761
3762sub new {
3763 my $class = shift;
3764
3765 my $self = $class->SUPER::new (
3766 buttonbar => (new CFPlus::UI::Buttonbar),
3767 multiplexer => (new CFPlus::UI::Multiplexer expand => 1),
3768 # filter => # will be put between multiplexer and $self
3769 @_,
3770 );
3771
3772 $self->{filter}->add ($self->{multiplexer}) if $self->{filter};
3773 $self->SUPER::add ($self->{buttonbar}, $self->{filter} || $self->{multiplexer});
3774
3775 $self
3776}
3777
3778sub add {
3779 my ($self, $title, $widget, $tooltip) = @_;
3780
3781 CFPlus::weaken $self;
3782
3783 $self->{buttonbar}->add (new CFPlus::UI::Button
3784 markup => $title,
3785 tooltip => $tooltip,
3786 on_activate => sub { $self->set_current_page ($widget) },
3787 );
3788
3789 $self->{multiplexer}->add ($widget);
3790}
3791
3792sub get_current_page {
3793 my ($self) = @_;
3794
3795 $self->{multiplexer}->get_current_page
3796}
3797
3798sub set_current_page {
3799 my ($self, $page) = @_;
3800
3801 $self->{multiplexer}->set_current_page ($page);
3802 $self->emit (page_changed => $self->{multiplexer}{current});
3803}
3804
3805#############################################################################
3806
3807package CFPlus::UI::Selector;
3808
3809use utf8;
3810
3811our @ISA = CFPlus::UI::Button::;
3812
3813sub new {
3814 my $class = shift;
3815
3816 my $self = $class->SUPER::new (
3817 options => [], # [value, title, longdesc], ...
3818 value => undef,
3819 @_,
3820 );
3821
3822 $self->_set_value ($self->{value});
3823
3824 $self
3825}
3826
3827sub invoke_button_down {
3828 my ($self, $ev) = @_;
3829
3830 my @menu_items;
3831
3832 for (@{ $self->{options} }) {
3833 my ($value, $title, $tooltip) = @$_;
3834
3835 push @menu_items, [$tooltip || $title, sub { $self->set_value ($value) }];
3836 }
3837
3838 CFPlus::UI::Menu->new (items => \@menu_items)->popup ($ev);
3839}
3840
3841sub _set_value {
3842 my ($self, $value) = @_;
3843
3844 my ($item) = grep $_->[0] eq $value, @{ $self->{options} }
3845 or return;
3846
3847 $self->{value} = $item->[0];
3848 $self->set_markup ("$item->[1] ⇓");
3849 $self->set_tooltip ($item->[2]);
3850}
3851
3852sub set_value {
3853 my ($self, $value) = @_;
3854
3855 return unless $self->{value} ne $value;
3856
3857 $self->_set_value ($value);
3858 $self->emit (changed => $value);
3859}
3860
3861#############################################################################
3862
3863package CFPlus::UI::Statusbox;
3864
3865our @ISA = CFPlus::UI::VBox::;
3866
3867sub new {
3868 my $class = shift;
3869
3870 my $self = $class->SUPER::new (
2867 fontsize => 0.8, 3871 fontsize => 0.8,
2868 @_, 3872 @_,
2869 ) 3873 );
3874
3875 CFPlus::weaken (my $this = $self);
3876
3877 $self->{timer} = Event->timer (after => 1, interval => 1, cb => sub { $this->reorder });
3878
3879 $self
2870} 3880}
2871 3881
2872sub reorder { 3882sub reorder {
2873 my ($self) = @_; 3883 my ($self) = @_;
2874 my $NOW = time; 3884 my $NOW = Time::HiRes::time;
3885
3886 # freeze display when hovering over any label
3887 return if $CFPlus::UI::TOOLTIP->{owner}
3888 && grep $CFPlus::UI::TOOLTIP->{owner} == $_->{label},
3889 values %{ $self->{item} };
2875 3890
2876 while (my ($k, $v) = each %{ $self->{item} }) { 3891 while (my ($k, $v) = each %{ $self->{item} }) {
2877 delete $self->{item}{$k} if $v->{timeout} < $NOW; 3892 delete $self->{item}{$k} if $v->{timeout} < $NOW;
2878 } 3893 }
2879 3894
2882 my @items = sort { 3897 my @items = sort {
2883 $a->{pri} <=> $b->{pri} 3898 $a->{pri} <=> $b->{pri}
2884 or $b->{id} <=> $a->{id} 3899 or $b->{id} <=> $a->{id}
2885 } values %{ $self->{item} }; 3900 } values %{ $self->{item} };
2886 3901
3902 $self->{timer}->interval (1);
3903
2887 my $count = 10 + 1; 3904 my $count = 10 + 1;
2888 for my $item (@items) { 3905 for my $item (@items) {
2889 last unless --$count; 3906 last unless --$count;
2890 3907
2891 push @widgets, $item->{label} ||= do { 3908 my $label = $item->{label} ||= do {
2892 # TODO: doesn't handle markup well (read as: at all) 3909 # TODO: doesn't handle markup well (read as: at all)
2893 my $short = $item->{count} > 1 3910 my $short = $item->{count} > 1
2894 ? "<b>$item->{count} ×</b> $item->{text}" 3911 ? "<b>$item->{count} ×</b> $item->{text}"
2895 : $item->{text}; 3912 : $item->{text};
2896 3913
2897 for ($short) { 3914 for ($short) {
2898 s/^\s+//; 3915 s/^\s+//;
2899 s/\s+/ /g; 3916 s/\s+/ /g;
2900 } 3917 }
2901 3918
2902 new CFClient::UI::Label 3919 new CFPlus::UI::Label
2903 markup => $short, 3920 markup => $short,
2904 tooltip => $item->{tooltip}, 3921 tooltip => $item->{tooltip},
2905 tooltip_font => $::FONT_PROP, 3922 tooltip_font => $::FONT_PROP,
2906 tooltip_width => 0.67, 3923 tooltip_width => 0.67,
2907 fontsize => $item->{fontsize} || $self->{fontsize}, 3924 fontsize => $item->{fontsize} || $self->{fontsize},
2908 max_w => $::WIDTH * 0.44, 3925 max_w => $::WIDTH * 0.44,
2909 fg => $item->{fg}, 3926 fg => [@{ $item->{fg} }],
2910 can_events => 1, 3927 can_events => 1,
2911 can_hover => 1 3928 can_hover => 1
2912 }; 3929 };
3930
3931 if ((my $diff = $item->{timeout} - $NOW) < 2) {
3932 $label->{fg}[3] = ($item->{fg}[3] || 1) * $diff / 2;
3933 $label->update;
3934 $label->set_max_size (undef, $label->{req_h} * $diff)
3935 if $diff < 1;
3936 $self->{timer}->interval (1/30);
3937 } else {
3938 $label->{fg}[3] = $item->{fg}[3] || 1;
3939 }
3940
3941 push @widgets, $label;
2913 } 3942 }
2914 3943
2915 $self->clear; 3944 $self->clear;
2916 $self->SUPER::add (reverse @widgets); 3945 $self->SUPER::add (reverse @widgets);
2917} 3946}
2922 $text =~ s/^\s+//; 3951 $text =~ s/^\s+//;
2923 $text =~ s/\s+$//; 3952 $text =~ s/\s+$//;
2924 3953
2925 return unless $text; 3954 return unless $text;
2926 3955
2927 my $timeout = time + ((delete $arg{timeout}) || 60); 3956 my $timeout = (int time) + ((delete $arg{timeout}) || 60);
2928 3957
2929 my $group = exists $arg{group} ? $arg{group} : ++$self->{id}; 3958 my $group = exists $arg{group} ? $arg{group} : ++$self->{id};
2930 3959
2931 if (my $item = $self->{item}{$group}) { 3960 if (my $item = $self->{item}{$group}) {
2932 if ($item->{text} eq $text) { 3961 if ($item->{text} eq $text) {
2933 $item->{count}++; 3962 $item->{count}++;
2934 } else { 3963 } else {
2935 $item->{count} = 1; 3964 $item->{count} = 1;
2936 $item->{text} = $item->{tooltip} = $text; 3965 $item->{text} = $item->{tooltip} = $text;
2937 } 3966 }
2938 $item->{id} = ++$self->{id}; 3967 $item->{id} += 0.2;#d#
2939 $item->{timeout} = $timeout; 3968 $item->{timeout} = $timeout;
2940 delete $item->{label}; 3969 delete $item->{label};
2941 } else { 3970 } else {
2942 $self->{item}{$group} = { 3971 $self->{item}{$group} = {
2943 id => ++$self->{id}, 3972 id => ++$self->{id},
2949 count => 1, 3978 count => 1,
2950 %arg, 3979 %arg,
2951 }; 3980 };
2952 } 3981 }
2953 3982
3983 $ROOT->on_refresh (reorder => sub {
2954 $self->reorder; 3984 $self->reorder;
3985 });
2955} 3986}
2956 3987
2957sub reconfigure { 3988sub reconfigure {
2958 my ($self) = @_; 3989 my ($self) = @_;
2959 3990
2962 3993
2963 $self->reorder; 3994 $self->reorder;
2964 $self->SUPER::reconfigure; 3995 $self->SUPER::reconfigure;
2965} 3996}
2966 3997
3998sub destroy {
3999 my ($self) = @_;
4000
4001 $self->{timer}->cancel;
4002
4003 $self->SUPER::destroy;
4004}
4005
2967############################################################################# 4006#############################################################################
2968 4007
2969package CFClient::UI::Inventory;
2970
2971our @ISA = CFClient::UI::ScrolledWindow::;
2972
2973sub new {
2974 my $class = shift;
2975
2976 my $self = $class->SUPER::new (
2977 scrolled => (new CFClient::UI::Table col_expand => [0, 1, 0]),
2978 @_,
2979 );
2980
2981 $self
2982}
2983
2984sub set_items {
2985 my ($self, $items) = @_;
2986
2987 $self->{scrolled}->clear;
2988 return unless $items;
2989
2990 my @items = sort {
2991 ($a->{type} <=> $b->{type})
2992 or ($a->{name} cmp $b->{name})
2993 } @$items;
2994
2995 $self->{real_items} = \@items;
2996
2997 my $row = 0;
2998 for my $item (@items) {
2999 CFClient::Item::update_widgets $item;
3000
3001 $self->{scrolled}->add (0, $row, $item->{face_widget});
3002 $self->{scrolled}->add (1, $row, $item->{desc_widget});
3003 $self->{scrolled}->add (2, $row, $item->{weight_widget});
3004
3005 $row++;
3006 }
3007}
3008
3009#############################################################################
3010
3011package CFClient::UI::BindEditor;
3012
3013our @ISA = CFClient::UI::FancyFrame::;
3014
3015sub new {
3016 my $class = shift;
3017
3018 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3019
3020 $self->add (my $vb = new CFClient::UI::VBox);
3021
3022
3023 $vb->add ($self->{rec_btn} = new CFClient::UI::Button
3024 text => "start recording",
3025 tooltip => "Start/Stops recording of actions."
3026 ."All subsequent actions after the recording started will be captured."
3027 ."The actions are displayed after the record was stopped."
3028 ."To bind the action you have to click on the 'Bind' button",
3029 on_activate => sub {
3030 unless ($self->{recording}) {
3031 $self->start;
3032 } else {
3033 $self->stop;
3034 }
3035 });
3036
3037 $vb->add (new CFClient::UI::Label text => "Actions:");
3038 $vb->add ($self->{cmdbox} = new CFClient::UI::VBox);
3039
3040 $vb->add (new CFClient::UI::Label text => "Bound to: ");
3041 $vb->add (my $hb = new CFClient::UI::HBox);
3042 $hb->add ($self->{keylbl} = new CFClient::UI::Label expand => 1);
3043 $hb->add (new CFClient::UI::Button
3044 text => "bind",
3045 tooltip => "This opens a query where you have to press the key combination to bind the recorded actions",
3046 on_activate => sub {
3047 $self->ask_for_bind;
3048 });
3049
3050 $vb->add (my $hb = new CFClient::UI::HBox);
3051 $hb->add (new CFClient::UI::Button
3052 text => "ok",
3053 expand => 1,
3054 tooltip => "This closes the binding editor and saves the binding",
3055 on_activate => sub {
3056 $self->hide;
3057 $self->commit;
3058 });
3059
3060 $hb->add (new CFClient::UI::Button
3061 text => "cancel",
3062 expand => 1,
3063 tooltip => "This closes the binding editor without saving",
3064 on_activate => sub {
3065 $self->hide;
3066 $self->{binding_cancel}->()
3067 if $self->{binding_cancel};
3068 });
3069
3070 $self->update_binding_widgets;
3071
3072 $self
3073}
3074
3075sub commit {
3076 my ($self) = @_;
3077 my ($mod, $sym, $cmds) = $self->get_binding;
3078 if ($sym != 0 && @$cmds > 0) {
3079 $::STATUSBOX->add ("Bound actions to '".CFClient::Binder::keycombo_to_name ($mod, $sym)
3080 ."'. Don't forget 'Save Config'!");
3081 $self->{binding_change}->($mod, $sym, $cmds)
3082 if $self->{binding_change};
3083 } else {
3084 $::STATUSBOX->add ("No action bound, no key or action specified!");
3085 $self->{binding_cancel}->()
3086 if $self->{binding_cancel};
3087 }
3088}
3089
3090sub start {
3091 my ($self) = @_;
3092
3093 $self->{rec_btn}->set_text ("stop recording");
3094 $self->{recording} = 1;
3095 $self->clear_command_list;
3096 $::CONN->start_record if $::CONN;
3097}
3098
3099sub stop {
3100 my ($self) = @_;
3101
3102 $self->{rec_btn}->set_text ("start recording");
3103 $self->{recording} = 0;
3104
3105 my $rec;
3106 $rec = $::CONN->stop_record if $::CONN;
3107 return unless ref $rec eq 'ARRAY';
3108 $self->set_command_list ($rec);
3109}
3110
3111# if $commit is true, the binding will be set after the user entered a key combo
3112sub ask_for_bind {
3113 my ($self, $commit) = @_;
3114
3115 CFClient::Binder::open_binding_dialog (sub {
3116 my ($mod, $sym) = @_;
3117 $self->{binding} = [$mod, $sym]; # XXX: how to stop that memleak?
3118 $self->update_binding_widgets;
3119 $self->commit if $commit;
3120 });
3121}
3122
3123# $mod and $sym are the modifiers and key symbol
3124# $cmds is a array ref of strings (the commands)
3125# $cb is the callback that is executed on OK
3126# $ccb is the callback that is executed on CANCEL and
3127# when the binding was unsuccessful on OK
3128sub set_binding {
3129 my ($self, $mod, $sym, $cmds, $cb, $ccb) = @_;
3130
3131 $self->clear_command_list;
3132 $self->{recording} = 0;
3133 $self->{rec_btn}->set_text ("start recording");
3134
3135 $self->{binding} = [$mod, $sym];
3136 $self->{commands} = $cmds;
3137
3138 $self->{binding_change} = $cb;
3139 $self->{binding_cancel} = $ccb;
3140
3141 $self->update_binding_widgets;
3142}
3143
3144# this is a shortcut method that asks for a binding
3145# and then just binds it.
3146sub do_quick_binding {
3147 my ($self, $cmds) = @_;
3148 $self->set_binding (undef, undef, $cmds, sub {
3149 $::CFG->{bindings}->{$_[0]}->{$_[1]} = $_[2];
3150 });
3151 $self->ask_for_bind (1);
3152}
3153
3154sub update_binding_widgets {
3155 my ($self) = @_;
3156 my ($mod, $sym, $cmds) = $self->get_binding;
3157 $self->{keylbl}->set_text (CFClient::Binder::keycombo_to_name ($mod, $sym));
3158 $self->set_command_list ($cmds);
3159}
3160
3161sub get_binding {
3162 my ($self) = @_;
3163 return (
3164 $self->{binding}->[0],
3165 $self->{binding}->[1],
3166 [ grep { defined $_ } @{$self->{commands}} ]
3167 );
3168}
3169
3170sub clear_command_list {
3171 my ($self) = @_;
3172 $self->{cmdbox}->clear ();
3173}
3174
3175sub set_command_list {
3176 my ($self, $cmds) = @_;
3177
3178 $self->{cmdbox}->clear ();
3179 $self->{commands} = $cmds;
3180
3181 my $idx = 0;
3182
3183 for (@$cmds) {
3184 $self->{cmdbox}->add (my $hb = new CFClient::UI::HBox);
3185
3186 my $i = $idx;
3187 $hb->add (new CFClient::UI::Label text => $_);
3188 $hb->add (new CFClient::UI::Button
3189 text => "delete",
3190 tooltip => "Deletes the action from the record",
3191 on_activate => sub {
3192 $self->{cmdbox}->remove ($hb);
3193 $cmds->[$i] = undef;
3194 });
3195
3196
3197 $idx++
3198 }
3199}
3200
3201#############################################################################
3202
3203package CFClient::UI::SpellList;
3204
3205our @ISA = CFClient::UI::FancyFrame::;
3206
3207sub new {
3208 my $class = shift;
3209
3210 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3211
3212 $self->add (new CFClient::UI::ScrolledWindow
3213 scrolled => $self->{spellbox} = new CFClient::UI::Table);
3214
3215 $self;
3216}
3217
3218# XXX: Do sorting? Argl...
3219sub add_spell {
3220 my ($self, $spell) = @_;
3221 $self->{spells}->{$spell->{name}} = $spell;
3222
3223 $self->{spellbox}->add (0, $self->{tbl_idx}, new CFClient::UI::Face
3224 face => $spell->{face},
3225 can_hover => 1,
3226 can_events => 1,
3227 tooltip => $spell->{message});
3228
3229 $self->{spellbox}->add (1, $self->{tbl_idx}, new CFClient::UI::Label
3230 text => $spell->{name},
3231 can_hover => 1,
3232 can_events => 1,
3233 tooltip => $spell->{message},
3234 expand => 1);
3235
3236 $self->{spellbox}->add (2, $self->{tbl_idx}, new CFClient::UI::Label
3237 text => (sprintf "lvl: %2d sp: %2d dmg: %2d",
3238 $spell->{level}, ($spell->{mana} || $spell->{grace}), $spell->{damage}),
3239 expand => 1);
3240
3241 $self->{spellbox}->add (3, $self->{tbl_idx}++, new CFClient::UI::Button
3242 text => "bind to key",
3243 on_activate => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) });
3244}
3245
3246sub rebuild_spell_list {
3247 my ($self) = @_;
3248 $self->{tbl_idx} = 0;
3249 $self->add_spell ($_) for values %{$self->{spells}};
3250}
3251
3252sub remove_spell {
3253 my ($self, $spell) = @_;
3254 delete $self->{spells}->{$spell->{name}};
3255 $self->rebuild_spell_list;
3256}
3257
3258#############################################################################
3259
3260package CFClient::UI::Root; 4008package CFPlus::UI::Root;
3261 4009
3262our @ISA = CFClient::UI::Container::; 4010our @ISA = CFPlus::UI::Container::;
3263 4011
4012use List::Util qw(min max);
4013
3264use CFClient::OpenGL; 4014use CFPlus::OpenGL;
3265 4015
3266sub new { 4016sub new {
3267 my $class = shift; 4017 my $class = shift;
3268 4018
3269 my $self = $class->SUPER::new ( 4019 my $self = $class->SUPER::new (
3270 visible => 1, 4020 visible => 1,
3271 @_, 4021 @_,
3272 ); 4022 );
3273 4023
3274 Scalar::Util::weaken ($self->{root} = $self); 4024 CFPlus::weaken ($self->{root} = $self);
3275 4025
3276 $self 4026 $self
3277} 4027}
3278 4028
3279sub size_request { 4029sub size_request {
3294 $coord = $max - $size if $coord > $max - $size; 4044 $coord = $max - $size if $coord > $max - $size;
3295 4045
3296 int $coord + 0.5 4046 int $coord + 0.5
3297} 4047}
3298 4048
3299sub size_allocate { 4049sub invoke_size_allocate {
3300 my ($self, $w, $h) = @_; 4050 my ($self, $w, $h) = @_;
3301 4051
3302 for my $child ($self->children) { 4052 for my $child ($self->children) {
3303 my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)}; 4053 my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)};
3304 4054
3308 $X = _to_pixel $X, $W, $self->{w}; 4058 $X = _to_pixel $X, $W, $self->{w};
3309 $Y = _to_pixel $Y, $H, $self->{h}; 4059 $Y = _to_pixel $Y, $H, $self->{h};
3310 4060
3311 $child->configure ($X, $Y, $W, $H); 4061 $child->configure ($X, $Y, $W, $H);
3312 } 4062 }
4063
4064 1
3313} 4065}
3314 4066
3315sub coord2local { 4067sub coord2local {
3316 my ($self, $x, $y) = @_; 4068 my ($self, $x, $y) = @_;
3317 4069
3402 4154
3403 delete $queue{$widget+0}; 4155 delete $queue{$widget+0};
3404 4156
3405 my ($w, $h) = $widget->size_request; 4157 my ($w, $h) = $widget->size_request;
3406 4158
3407 $w = List::Util::max $widget->{min_w}, $w + $widget->{padding_x} * 2; 4159 $w = max $widget->{min_w}, $w + $widget->{padding_x} * 2;
3408 $h = List::Util::max $widget->{min_h}, $h + $widget->{padding_y} * 2; 4160 $h = max $widget->{min_h}, $h + $widget->{padding_y} * 2;
4161
4162 $w = min $widget->{max_w}, $w if exists $widget->{max_w};
4163 $h = min $widget->{max_h}, $h if exists $widget->{max_h};
3409 4164
3410 $w = $widget->{force_w} if exists $widget->{force_w}; 4165 $w = $widget->{force_w} if exists $widget->{force_w};
3411 $h = $widget->{force_h} if exists $widget->{force_h}; 4166 $h = $widget->{force_h} if exists $widget->{force_h};
3412 4167
3413 if ($widget->{req_w} != $w || $widget->{req_h} != $h 4168 if ($widget->{req_w} != $w || $widget->{req_h} != $h
3439 4194
3440 my ($w, $h) = @$widget{qw(alloc_w alloc_h)}; 4195 my ($w, $h) = @$widget{qw(alloc_w alloc_h)};
3441 4196
3442 $w = 0 if $w < 0; 4197 $w = 0 if $w < 0;
3443 $h = 0 if $h < 0; 4198 $h = 0 if $h < 0;
4199
4200 $w = max $widget->{min_w}, $w;
4201 $h = max $widget->{min_h}, $h;
4202
4203# $w = min $self->{w} - $widget->{x}, $w if $self->{w};
4204# $h = min $self->{h} - $widget->{y}, $h if $self->{h};
4205
4206 $w = min $widget->{max_w}, $w if exists $widget->{max_w};
4207 $h = min $widget->{max_h}, $h if exists $widget->{max_h};
3444 4208
3445 $w = int $w + 0.5; 4209 $w = int $w + 0.5;
3446 $h = int $h + 0.5; 4210 $h = int $h + 0.5;
3447 4211
3448 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) { 4212 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) {
3471 glLoadIdentity; 4235 glLoadIdentity;
3472 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000; 4236 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000;
3473 glMatrixMode GL_MODELVIEW; 4237 glMatrixMode GL_MODELVIEW;
3474 glLoadIdentity; 4238 glLoadIdentity;
3475 4239
4240 {
4241 package CFPlus::UI::Base;
4242
4243 local ($draw_x, $draw_y, $draw_w, $draw_h) =
4244 (0, 0, $self->{w}, $self->{h});
4245
3476 $self->_draw; 4246 $self->_draw;
4247 }
3477} 4248}
3478 4249
3479############################################################################# 4250#############################################################################
3480 4251
3481package CFClient::UI; 4252package CFPlus::UI;
3482 4253
3483$ROOT = new CFClient::UI::Root; 4254$ROOT = new CFPlus::UI::Root;
3484$TOOLTIP = new CFClient::UI::Tooltip z => 900; 4255$TOOLTIP = new CFPlus::UI::Tooltip z => 900;
3485 4256
34861 42571
3487 4258

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines