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.307 by root, Sun Jun 18 19:13:20 2006 UTC vs.
Revision 1.457 by root, Fri Dec 28 15:05:20 2007 UTC

1package CFClient::UI; 1package DC::UI;
2 2
3use utf8; 3use utf8;
4use strict; 4use strict;
5 5
6use Scalar::Util ();
7use List::Util (); 6use List::Util ();
8use Event;
9 7
10use CFClient; 8use DC;
9use DC::Pod;
11use CFClient::Texture; 10use DC::Texture;
12 11
13our ($FOCUS, $HOVER, $GRAB); # various widgets 12our ($FOCUS, $HOVER, $GRAB); # various widgets
14 13
15our $LAYOUT; 14our $LAYOUT;
16our $ROOT; 15our $ROOT;
17our $TOOLTIP; 16our $TOOLTIP;
18our $BUTTON_STATE; 17our $BUTTON_STATE;
19 18
20our %WIDGET; # all widgets, weak-referenced 19our %WIDGET; # all widgets, weak-referenced
21 20
22our $TOOLTIP_WATCHER = Event->idle (min => 1/60, cb => sub { 21our $TOOLTIP_WATCHER = EV::timer_ns 0, 0.03, sub {
22 $_[0]->stop;
23
23 if (!$GRAB) { 24 if (!$GRAB) {
24 for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) { 25 for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) {
25 if (length $widget->{tooltip}) { 26 if (length $widget->{tooltip}) {
26 if ($TOOLTIP->{owner} != $widget) { 27 if ($TOOLTIP->{owner} != $widget) {
28 $TOOLTIP->{owner}->emit ("tooltip_hide") if $TOOLTIP->{owner};
27 $TOOLTIP->hide; 29 $TOOLTIP->hide;
28 30
29 $TOOLTIP->{owner} = $widget; 31 $TOOLTIP->{owner} = $widget;
32 $TOOLTIP->{owner}->emit ("tooltip_show") if $TOOLTIP->{owner};
30 33
31 return if $ENV{CFPLUS_DEBUG} & 8; 34 return if $ENV{CFPLUS_DEBUG} & 8;
32 35
33 my $tip = $widget->{tooltip};
34
35 $tip = $tip->($widget) if CODE:: eq ref $tip;
36
37 $TOOLTIP->set_tooltip_from ($widget); 36 $TOOLTIP->set_tooltip_from ($widget);
38 $TOOLTIP->show; 37 $TOOLTIP->show;
39 } 38 }
40 39
41 return; 40 return;
42 } 41 }
43 } 42 }
44 } 43 }
45 44
46 $TOOLTIP->hide; 45 $TOOLTIP->hide;
46 $TOOLTIP->{owner}->emit ("tooltip_hide") if $TOOLTIP->{owner};
47 delete $TOOLTIP->{owner}; 47 delete $TOOLTIP->{owner};
48}); 48};
49 49
50sub get_layout { 50sub get_layout {
51 my $layout; 51 my $layout;
52 52
53 for (grep { $_->{name} } values %WIDGET) { 53 for (grep { $_->{name} } values %WIDGET) {
79sub feed_sdl_key_up_event { 79sub feed_sdl_key_up_event {
80 $FOCUS->emit (key_up => $_[0]) 80 $FOCUS->emit (key_up => $_[0])
81 if $FOCUS; 81 if $FOCUS;
82} 82}
83 83
84sub check_hover {
85 my ($widget) = @_;
86
87 if ($widget != $HOVER) {
88 my $hover = $HOVER; $HOVER = $widget;
89
90 $hover->update if $hover && $hover->{can_hover};
91 $HOVER->update if $HOVER && $HOVER->{can_hover};
92
93 $TOOLTIP_WATCHER->again;
94 }
95}
96
84sub feed_sdl_button_down_event { 97sub feed_sdl_button_down_event {
85 my ($ev) = @_; 98 my ($ev) = @_;
86 my ($x, $y) = ($ev->{x}, $ev->{y}); 99 my ($x, $y) = ($ev->{x}, $ev->{y});
87 100
88 if (!$BUTTON_STATE) { 101 $BUTTON_STATE |= 1 << ($ev->{button} - 1);
102
103 unless ($GRAB) {
89 my $widget = $ROOT->find_widget ($x, $y); 104 my $widget = $ROOT->find_widget ($x, $y);
90 105
91 $GRAB = $widget; 106 $GRAB = $widget;
92 $GRAB->update if $GRAB; 107 $GRAB->update if $GRAB;
93 108
94 $TOOLTIP_WATCHER->cb->(); 109 $TOOLTIP_WATCHER->invoke;
95 } 110 }
96 111
97 $BUTTON_STATE |= 1 << ($ev->{button} - 1); 112 if ($GRAB) {
113 if ($ev->{button} == 4 || $ev->{button} == 5) {
114 # mousewheel
115 my $delta = $ev->{button} * 2 - 9;
116 my $shift = $ev->{mod} & DC::KMOD_SHIFT;
98 117
99 $GRAB->emit (button_down => $ev, $GRAB->coord2local ($x, $y)) 118 $ev->{dx} = $shift ? $delta : 0;
100 if $GRAB; 119 $ev->{dy} = $shift ? 0 : $delta;
120
121 $GRAB->emit (mouse_wheel => $ev);
122 } else {
123 $GRAB->emit (button_down => $ev)
124 }
125 }
101} 126}
102 127
103sub feed_sdl_button_up_event { 128sub feed_sdl_button_up_event {
104 my ($ev) = @_; 129 my ($ev) = @_;
105 my ($x, $y) = ($ev->{x}, $ev->{y});
106 130
107 my $widget = $GRAB || $ROOT->find_widget ($x, $y); 131 my $widget = $GRAB || $ROOT->find_widget ($ev->{x}, $ev->{y});
108 132
109 $BUTTON_STATE &= ~(1 << ($ev->{button} - 1)); 133 $BUTTON_STATE &= ~(1 << ($ev->{button} - 1));
110 134
111 $GRAB->emit (button_up => $ev, $GRAB->coord2local ($x, $y)) 135 $GRAB->emit (button_up => $ev)
112 if $GRAB; 136 if $GRAB && $ev->{button} != 4 && $ev->{button} != 5;
113 137
114 if (!$BUTTON_STATE) { 138 unless ($BUTTON_STATE) {
115 my $grab = $GRAB; undef $GRAB; 139 my $grab = $GRAB; undef $GRAB;
116 $grab->update if $grab; 140 $grab->update if $grab;
117 $GRAB->update if $GRAB; 141 $GRAB->update if $GRAB;
118 142
143 check_hover $widget;
119 $TOOLTIP_WATCHER->cb->(); 144 $TOOLTIP_WATCHER->invoke;
120 } 145 }
121} 146}
122 147
123sub feed_sdl_motion_event { 148sub feed_sdl_motion_event {
124 my ($ev) = @_; 149 my ($ev) = @_;
125 my ($x, $y) = ($ev->{x}, $ev->{y}); 150 my ($x, $y) = ($ev->{x}, $ev->{y});
126 151
127 my $widget = $GRAB || $ROOT->find_widget ($x, $y); 152 my $widget = $GRAB || $ROOT->find_widget ($x, $y);
128 153
129 if ($widget != $HOVER) { 154 check_hover $widget;
130 my $hover = $HOVER; $HOVER = $widget;
131 155
132 $hover->update if $hover && $hover->{can_hover}; 156 $HOVER->emit (mouse_motion => $ev)
133 $HOVER->update if $HOVER && $HOVER->{can_hover};
134
135 $TOOLTIP_WATCHER->start;
136 }
137
138 $HOVER->emit (mouse_motion => $ev, $HOVER->coord2local ($x, $y))
139 if $HOVER; 157 if $HOVER;
140} 158}
141 159
142# convert position array to integers 160# convert position array to integers
143sub harmonize { 161sub harmonize {
193 reconfigure_widgets; 211 reconfigure_widgets;
194} 212}
195 213
196############################################################################# 214#############################################################################
197 215
216package DC::UI::Event;
217
218sub xy {
219 $_[1]->coord2local ($_[0]{x}, $_[0]{y})
220}
221
222#############################################################################
223
198package CFClient::UI::Base; 224package DC::UI::Base;
199 225
200use strict; 226use strict;
201 227
202use CFClient::OpenGL; 228use DC::OpenGL;
203 229
204sub new { 230sub new {
205 my $class = shift; 231 my $class = shift;
206 232
207 my $self = bless { 233 my $self = bless {
212 h => undef, 238 h => undef,
213 can_events => 1, 239 can_events => 1,
214 @_ 240 @_
215 }, $class; 241 }, $class;
216 242
217 Scalar::Util::weaken ($CFClient::UI::WIDGET{$self+0} = $self); 243 DC::weaken ($DC::UI::WIDGET{$self+0} = $self);
218 244
219 for (keys %$self) { 245 for (keys %$self) {
220 if (/^on_(.*)$/) { 246 if (/^on_(.*)$/) {
221 $self->connect ($1 => delete $self->{$_}); 247 $self->connect ($1 => delete $self->{$_});
222 } 248 }
223 } 249 }
224 250
225 if (my $layout = $CFClient::UI::LAYOUT->{$self->{name}}) { 251 if (my $layout = $DC::UI::LAYOUT->{$self->{name}}) {
226 $self->{x} = $layout->{x} * $CFClient::UI::ROOT->{alloc_w} if exists $layout->{x}; 252 $self->{x} = $layout->{x} * $DC::UI::ROOT->{alloc_w} if exists $layout->{x};
227 $self->{y} = $layout->{y} * $CFClient::UI::ROOT->{alloc_h} if exists $layout->{y}; 253 $self->{y} = $layout->{y} * $DC::UI::ROOT->{alloc_h} if exists $layout->{y};
228 $self->{force_w} = $layout->{w} * $CFClient::UI::ROOT->{alloc_w} if exists $layout->{w}; 254 $self->{force_w} = $layout->{w} * $DC::UI::ROOT->{alloc_w} if exists $layout->{w};
229 $self->{force_h} = $layout->{h} * $CFClient::UI::ROOT->{alloc_h} if exists $layout->{h}; 255 $self->{force_h} = $layout->{h} * $DC::UI::ROOT->{alloc_h} if exists $layout->{h};
230 256
231 $self->{x} -= $self->{force_w} * 0.5 if exists $layout->{x}; 257 $self->{x} -= $self->{force_w} * 0.5 if exists $layout->{x};
232 $self->{y} -= $self->{force_h} * 0.5 if exists $layout->{y}; 258 $self->{y} -= $self->{force_h} * 0.5 if exists $layout->{y};
233 259
234 $self->show if $layout->{show}; 260 $self->show if $layout->{show};
239 265
240sub destroy { 266sub destroy {
241 my ($self) = @_; 267 my ($self) = @_;
242 268
243 $self->hide; 269 $self->hide;
270 $self->emit ("destroy");
244 %$self = (); 271 %$self = ();
245} 272}
246 273
274sub TO_JSON {
275 { "\fw" => $_[0]{s_id} }
276}
277
247sub show { 278sub show {
248 my ($self) = @_; 279 my ($self) = @_;
249 280
250 return if $self->{parent}; 281 return if $self->{parent};
251 282
252 $CFClient::UI::ROOT->add ($self); 283 $DC::UI::ROOT->add ($self);
253} 284}
254 285
255sub set_visible { 286sub set_visible {
256 my ($self) = @_; 287 my ($self) = @_;
257 288
278 delete $self->{root}; 309 delete $self->{root};
279 310
280 undef $GRAB if $GRAB == $self; 311 undef $GRAB if $GRAB == $self;
281 undef $HOVER if $HOVER == $self; 312 undef $HOVER if $HOVER == $self;
282 313
283 $CFClient::UI::TOOLTIP_WATCHER->cb->() 314 $DC::UI::TOOLTIP_WATCHER->invoke
284 if $TOOLTIP->{owner} == $self; 315 if $TOOLTIP->{owner} == $self;
285 316
286 $self->emit ("focus_out"); 317 $self->emit ("focus_out");
287 $self->emit (visibility_change => 0); 318 $self->emit (visibility_change => 0);
288} 319}
290sub set_visibility { 321sub set_visibility {
291 my ($self, $visible) = @_; 322 my ($self, $visible) = @_;
292 323
293 return if $self->{visible} == $visible; 324 return if $self->{visible} == $visible;
294 325
295 $visible ? $self->hide 326 $visible ? $self->show
296 : $self->show; 327 : $self->hide;
297} 328}
298 329
299sub toggle_visibility { 330sub toggle_visibility {
300 my ($self) = @_; 331 my ($self) = @_;
301 332
307sub hide { 338sub hide {
308 my ($self) = @_; 339 my ($self) = @_;
309 340
310 $self->set_invisible; 341 $self->set_invisible;
311 342
343 # extra $parent copy for 5.8.8+ bug workaround
344 # (otherwise $_[0] in remove gets freed
345 if (my $parent = $self->{parent}) {
312 $self->{parent}->remove ($self) 346 $parent->remove ($self);
313 if $self->{parent}; 347 }
314} 348}
315 349
316sub move_abs { 350sub move_abs {
317 my ($self, $x, $y, $z) = @_; 351 my ($self, $x, $y, $z) = @_;
318 352
330 $self->{force_h} = $h; 364 $self->{force_h} = $h;
331 365
332 $self->realloc; 366 $self->realloc;
333} 367}
334 368
369# traverse the widget chain up to find the maximum "physical" size constraints
370sub get_max_wh {
371 my ($self) = @_;
372
373 return $self->{parent}->get_max_wh
374 if $self->{parent};
375
376 ($::WIDTH, $::HEIGHT)
377}
378
335sub size_request { 379sub size_request {
336 require Carp; 380 require Carp;
337 Carp::confess "size_request is abstract"; 381 Carp::confess "size_request is abstract";
338} 382}
339 383
384sub baseline_shift {
385 0
386}
387
340sub configure { 388sub configure {
341 my ($self, $x, $y, $w, $h) = @_; 389 my ($self, $x, $y, $w, $h) = @_;
342 390
343 if ($self->{aspect}) { 391 if ($self->{aspect}) {
344 my ($ow, $oh) = ($w, $h); 392 my ($ow, $oh) = ($w, $h);
345 393
346 $w = List::Util::min $w, int $h * $self->{aspect}; 394 $w = List::Util::min $w, DC::ceil $h * $self->{aspect};
347 $h = List::Util::min $h, int $w / $self->{aspect}; 395 $h = List::Util::min $h, DC::ceil $w / $self->{aspect};
348 396
349 # use alignment to adjust x, y 397 # use alignment to adjust x, y
350 398
351 $x += int 0.5 * ($ow - $w); 399 $x += int 0.5 * ($ow - $w);
352 $y += int 0.5 * ($oh - $h); 400 $y += int 0.5 * ($oh - $h);
393 441
394 return if $self->{tooltip} eq $tooltip; 442 return if $self->{tooltip} eq $tooltip;
395 443
396 $self->{tooltip} = $tooltip; 444 $self->{tooltip} = $tooltip;
397 445
398 if ($CFClient::UI::TOOLTIP->{owner} == $self) { 446 if ($DC::UI::TOOLTIP->{owner} == $self) {
399 delete $CFClient::UI::TOOLTIP->{owner}; 447 delete $DC::UI::TOOLTIP->{owner};
400 $CFClient::UI::TOOLTIP_WATCHER->cb->(); 448 $DC::UI::TOOLTIP_WATCHER->invoke;
401 } 449 }
402} 450}
403 451
404# translate global coordinates to local coordinate system 452# translate global coordinates to local coordinate system
405sub coord2local { 453sub coord2local {
406 my ($self, $x, $y) = @_; 454 my ($self, $x, $y) = @_;
407 455
456 return (undef, undef) unless $self->{parent};
457
408 $self->{parent}->coord2local ($x - $self->{x}, $y - $self->{y}) 458 $self->{parent}->coord2local ($x - $self->{x}, $y - $self->{y})
409} 459}
410 460
411# translate local coordinates to global coordinate system 461# translate local coordinates to global coordinate system
412sub coord2global { 462sub coord2global {
413 my ($self, $x, $y) = @_; 463 my ($self, $x, $y) = @_;
414 464
465 return (undef, undef) unless $self->{parent};
466
415 $self->{parent}->coord2global ($x + $self->{x}, $y + $self->{y}) 467 $self->{parent}->coord2global ($x + $self->{x}, $y + $self->{y})
416} 468}
417 469
418sub invoke_focus_in { 470sub invoke_focus_in {
419 my ($self) = @_; 471 my ($self) = @_;
420 472
421 return if $FOCUS == $self; 473 return if $FOCUS == $self;
422 return unless $self->{can_focus}; 474 return unless $self->{can_focus};
423 475
424 my $focus = $FOCUS; $FOCUS = $self; 476 $FOCUS = $self;
425 477
426 $focus->update if $focus; 478 $self->update;
427 $FOCUS->update;
428 479
429 0 480 0
430} 481}
431 482
432sub invoke_focus_out { 483sub invoke_focus_out {
433 my ($self) = @_; 484 my ($self) = @_;
434 485
435 return unless $FOCUS == $self; 486 return unless $FOCUS == $self;
436 487
437 my $focus = $FOCUS; undef $FOCUS; 488 undef $FOCUS;
438 489
439 $focus->update if $focus; #? 490 $self->update;
440 491
441 $::MAPWIDGET->grab_focus #d# focus mapwidget if no other widget has focus 492 $::MAPWIDGET->grab_focus #d# focus mapwidget if no other widget has focus
442 unless $FOCUS; 493 unless $FOCUS;
443 494
444 0 495 0
445} 496}
446 497
447sub grab_focus { 498sub grab_focus {
448 my ($self) = @_; 499 my ($self) = @_;
449 500
501 $FOCUS->emit ("focus_out") if $FOCUS;
450 $self->emit ("focus_in"); 502 $self->emit ("focus_in");
451} 503}
452 504
453sub invoke_mouse_motion { 1 } 505sub invoke_mouse_motion { 0 }
454sub invoke_button_up { 1 } 506sub invoke_button_up { 0 }
455sub invoke_key_down { 1 } 507sub invoke_key_down { 0 }
456sub invoke_key_up { 1 } 508sub invoke_key_up { 0 }
509sub invoke_mouse_wheel { 0 }
457 510
458sub invoke_button_down { 511sub invoke_button_down {
459 my ($self, $ev, $x, $y) = @_; 512 my ($self, $ev, $x, $y) = @_;
460 513
461 $self->grab_focus; 514 $self->grab_focus;
462 515
463 1 516 0
464} 517}
465 518
466sub connect { 519sub connect {
467 my ($self, $signal, $cb) = @_; 520 my ($self, $signal, $cb) = @_;
468 521
469 push @{ $self->{signal_cb}{$signal} }, $cb; 522 push @{ $self->{signal_cb}{$signal} }, $cb;
523
524 defined wantarray and DC::guard {
525 @{ $self->{signal_cb}{$signal} } = grep $_ != $cb,
526 @{ $self->{signal_cb}{$signal} };
527 }
470} 528}
529
530sub disconnect_all {
531 my ($self, $signal) = @_;
532
533 delete $self->{signal_cb}{$signal};
534}
535
536my %has_coords = (
537 button_down => 1,
538 button_up => 1,
539 mouse_motion => 1,
540 mouse_wheel => 1,
541);
471 542
472sub emit { 543sub emit {
473 my ($self, $signal, @args) = @_; 544 my ($self, $signal, @args) = @_;
474 545
475 (List::Util::sum +(map $_->($self, @args), @{$self->{signal_cb}{$signal} || []}), # before 546 # I do not really like this solution, but I do not like duplication
547 # and needlessly verbose code, either.
548 my @append
549 = $has_coords{$signal}
550 ? $args[0]->xy ($self)
551 : ();
552
553 #warn +(caller(1))[3] . "emit $signal on $self (parent $self->{parent})\n";#d#
554
555 for my $cb (
556 @{$self->{signal_cb}{$signal} || []}, # before
476 ($self->can ("invoke_$signal") || sub { 1 })->($self, @args)) # closure 557 ($self->can ("invoke_$signal") || sub { 1 }), # closure
558 ) {
559 return $cb->($self, @args, @append) || next;
560 }
561
562 # parent
477 || ($self->{parent} && $self->{parent}->emit ($signal, @args)) # parent 563 $self->{parent} && $self->{parent}->emit ($signal, @args)
478} 564}
479 565
480sub find_widget { 566#sub find_widget {
481 my ($self, $x, $y) = @_; 567# in .xs
482
483 return () unless $self->{can_events};
484
485 return $self
486 if $x >= $self->{x} && $x < $self->{x} + $self->{w}
487 && $y >= $self->{y} && $y < $self->{y} + $self->{h};
488
489 ()
490}
491 568
492sub set_parent { 569sub set_parent {
493 my ($self, $parent) = @_; 570 my ($self, $parent) = @_;
494 571
495 Scalar::Util::weaken ($self->{parent} = $parent); 572 DC::weaken ($self->{parent} = $parent);
496 $self->set_visible if $parent->{visible}; 573 $self->set_visible if $parent->{visible};
497} 574}
498 575
499sub realloc { 576sub realloc {
500 my ($self) = @_; 577 my ($self) = @_;
526 603
527# using global variables seems a bit hacky, but passing through all drawing 604# using global variables seems a bit hacky, but passing through all drawing
528# functions seems pointless. 605# functions seems pointless.
529our ($draw_x, $draw_y, $draw_w, $draw_h); # screen rectangle being drawn 606our ($draw_x, $draw_y, $draw_w, $draw_h); # screen rectangle being drawn
530 607
531sub draw { 608#sub draw {
532 my ($self) = @_; 609#CFPlus.xs
533
534 return unless $self->{h} && $self->{w};
535
536 # update screen rectangle
537 local $draw_x = $draw_x + $self->{x};
538 local $draw_y = $draw_y + $self->{y};
539 local $draw_w = $draw_x + $self->{w};
540 local $draw_h = $draw_y + $self->{h};
541
542 # skip widgets that are entirely outside the drawing area
543 return if ($draw_x + $self->{w} < 0) || ($draw_x >= $draw_w)
544 || ($draw_y + $self->{h} < 0) || ($draw_y >= $draw_h);
545
546 glPushMatrix;
547 glTranslate $self->{x}, $self->{y}, 0;
548
549 if ($self == $HOVER && $self->{can_hover}) {
550 glColor 1*0.2, 0.8*0.2, 0.5*0.2, 0.2;
551 glEnable GL_BLEND;
552 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
553 glBegin GL_QUADS;
554 glVertex 0 , 0;
555 glVertex $self->{w}, 0;
556 glVertex $self->{w}, $self->{h};
557 glVertex 0 , $self->{h};
558 glEnd;
559 glDisable GL_BLEND;
560 }
561
562 if ($ENV{CFPLUS_DEBUG} & 1) {
563 glPushMatrix;
564 glColor 1, 1, 0, 1;
565 glTranslate 0.375, 0.375;
566 glBegin GL_LINE_LOOP;
567 glVertex 0 , 0;
568 glVertex $self->{w} - 1, 0;
569 glVertex $self->{w} - 1, $self->{h} - 1;
570 glVertex 0 , $self->{h} - 1;
571 glEnd;
572 glPopMatrix;
573 #CFClient::UI::Label->new (w => $self->{w}, h => $self->{h}, text => $self, fontsize => 0)->_draw;
574 }
575
576 $self->_draw;
577 glPopMatrix;
578}
579 610
580sub _draw { 611sub _draw {
581 my ($self) = @_; 612 my ($self) = @_;
582 613
583 warn "no draw defined for $self\n"; 614 warn "no draw defined for $self\n";
584} 615}
585 616
586sub DESTROY { 617sub DESTROY {
587 my ($self) = @_; 618 my ($self) = @_;
588 619
589 delete $WIDGET{$self+0}; 620 return if DC::in_destruct;
590 621
622 local $@;
591 eval { $self->destroy }; 623 eval { $self->destroy };
592 warn "exception during widget destruction: $@" if $@ & $@ != /during global destruction/; 624 warn "exception during widget destruction: $@" if $@ & $@ != /during global destruction/;
625
626 delete $WIDGET{$self+0};
593} 627}
594 628
595############################################################################# 629#############################################################################
596 630
597package CFClient::UI::DrawBG; 631package DC::UI::DrawBG;
598 632
599our @ISA = CFClient::UI::Base::; 633our @ISA = DC::UI::Base::;
600 634
601use strict; 635use strict;
602use CFClient::OpenGL; 636use DC::OpenGL;
603 637
604sub new { 638sub new {
605 my $class = shift; 639 my $class = shift;
606
607 # range [value, low, high, page]
608 640
609 $class->SUPER::new ( 641 $class->SUPER::new (
610 #bg => [0, 0, 0, 0.2], 642 #bg => [0, 0, 0, 0.2],
611 #active_bg => [1, 1, 1, 0.5], 643 #active_bg => [1, 1, 1, 0.5],
612 @_ 644 @_
624 my ($w, $h) = @$self{qw(w h)}; 656 my ($w, $h) = @$self{qw(w h)};
625 657
626 glEnable GL_BLEND; 658 glEnable GL_BLEND;
627 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA; 659 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
628 glColor_premultiply @$color; 660 glColor_premultiply @$color;
629
630 glBegin GL_QUADS;
631 glVertex 0 , 0;
632 glVertex 0 , $h;
633 glVertex $w, $h; 661 glRect 0, 0, $w, $h;
634 glVertex $w, 0;
635 glEnd;
636
637 glDisable GL_BLEND; 662 glDisable GL_BLEND;
638 } 663 }
639} 664}
640 665
641############################################################################# 666#############################################################################
642 667
643package CFClient::UI::Empty; 668package DC::UI::Empty;
644 669
645our @ISA = CFClient::UI::Base::; 670our @ISA = DC::UI::Base::;
646 671
647sub new { 672sub new {
648 my ($class, %arg) = @_; 673 my ($class, %arg) = @_;
649 $class->SUPER::new (can_events => 0, %arg); 674 $class->SUPER::new (can_events => 0, %arg);
650} 675}
657 682
658sub draw { } 683sub draw { }
659 684
660############################################################################# 685#############################################################################
661 686
662package CFClient::UI::Container; 687package DC::UI::Container;
663 688
664our @ISA = CFClient::UI::Base::; 689our @ISA = DC::UI::Base::;
665 690
666sub new { 691sub new {
667 my ($class, %arg) = @_; 692 my ($class, %arg) = @_;
668 693
669 my $children = delete $arg{children}; 694 my $children = delete $arg{children};
673 can_events => 0, 698 can_events => 0,
674 %arg, 699 %arg,
675 ); 700 );
676 701
677 $self->add (@$children) 702 $self->add (@$children)
678 if $children; 703 if $children && @$children;
679 704
680 $self 705 $self
706}
707
708sub realloc {
709 my ($self) = @_;
710
711 $self->{force_realloc} = 1;
712 $self->{force_size_alloc} = 1;
713 $self->SUPER::realloc;
681} 714}
682 715
683sub add { 716sub add {
684 my ($self, @widgets) = @_; 717 my ($self, @widgets) = @_;
685 718
686 $_->set_parent ($self) 719 $_->set_parent ($self)
687 for @widgets; 720 for @widgets;
688 721
722 # TODO: only do this in widgets that need it, e.g. root, fixed
689 use sort 'stable'; 723 use sort 'stable';
690 724
691 $self->{children} = [ 725 $self->{children} = [
692 sort { $a->{z} <=> $b->{z} } 726 sort { $a->{z} <=> $b->{z} }
693 @{$self->{children}}, @widgets 727 @{$self->{children}}, @widgets
694 ]; 728 ];
695 729
696 $self->realloc; 730 $self->realloc;
731
732 $self->emit (c_add => \@widgets);
733
734 map $_+0, @widgets
697} 735}
698 736
699sub children { 737sub children {
700 @{ $_[0]{children} } 738 @{ $_[0]{children} }
701} 739}
702 740
703sub remove { 741sub remove {
704 my ($self, $child) = @_; 742 my ($self, @widgets) = @_;
705 743
744 $self->emit (c_remove => \@widgets);
745
746 for my $child (@widgets) {
706 delete $child->{parent}; 747 delete $child->{parent};
707 $child->hide; 748 $child->hide;
708
709 $self->{children} = [ grep $_ != $child, @{ $self->{children} } ]; 749 $self->{children} = [ grep $_ != $child, @{ $self->{children} } ];
750 }
710 751
711 $self->realloc; 752 $self->realloc;
712} 753}
713 754
714sub clear { 755sub clear {
715 my ($self) = @_; 756 my ($self) = @_;
716 757
717 my $children = delete $self->{children}; 758 my $children = $self->{children};
718 $self->{children} = []; 759 $self->{children} = [];
719 760
720 for (@$children) { 761 for (@$children) {
721 delete $_->{parent}; 762 delete $_->{parent};
722 $_->hide; 763 $_->hide;
742} 783}
743 784
744sub _draw { 785sub _draw {
745 my ($self) = @_; 786 my ($self) = @_;
746 787
747 $_->draw for @{$self->{children}}; 788 $_->draw for $self->visible_children;
748} 789}
749 790
750############################################################################# 791#############################################################################
751 792
752package CFClient::UI::Bin; 793package DC::UI::Bin;
753 794
754our @ISA = CFClient::UI::Container::; 795our @ISA = DC::UI::Container::;
755 796
756sub new { 797sub new {
757 my ($class, %arg) = @_; 798 my ($class, %arg) = @_;
758 799
759 my $child = (delete $arg{child}) || new CFClient::UI::Empty::; 800 my $child = (delete $arg{child}) || new DC::UI::Empty::;
760 801
761 $class->SUPER::new (children => [$child], %arg) 802 $class->SUPER::new (children => [$child], %arg)
762} 803}
763 804
764sub add { 805sub add {
765 my ($self, $child) = @_; 806 my ($self, $child) = @_;
766 807
767 $self->SUPER::remove ($_) for @{ $self->{children} }; 808 $self->clear;
768 $self->SUPER::add ($child); 809 $self->SUPER::add ($child);
769} 810}
770 811
771sub remove { 812sub remove {
772 my ($self, $widget) = @_; 813 my ($self, $widget) = @_;
773 814
774 $self->SUPER::remove ($widget); 815 $self->SUPER::remove ($widget);
775 816
776 $self->{children} = [new CFClient::UI::Empty] 817 $self->{children} = [new DC::UI::Empty]
777 unless @{$self->{children}}; 818 unless @{$self->{children}};
778} 819}
779 820
780sub child { $_[0]->{children}[0] } 821sub child { $_[0]->{children}[0] }
781 822
790 831
791 1 832 1
792} 833}
793 834
794############################################################################# 835#############################################################################
795
796# back-buffered drawing area 836# back-buffered drawing area
797 837
798package CFClient::UI::Window; 838package DC::UI::Window;
799 839
800our @ISA = CFClient::UI::Bin::; 840our @ISA = DC::UI::Bin::;
801 841
802use CFClient::OpenGL; 842use DC::OpenGL;
803 843
804sub new { 844sub new {
805 my ($class, %arg) = @_; 845 my ($class, %arg) = @_;
806 846
807 my $self = $class->SUPER::new (%arg); 847 my $self = $class->SUPER::new (%arg);
829} 869}
830 870
831sub render_child { 871sub render_child {
832 my ($self) = @_; 872 my ($self) = @_;
833 873
834 $self->{texture} = new_from_opengl CFClient::Texture $self->{w}, $self->{h}, sub { 874 $self->{texture} = new_from_opengl DC::Texture $self->{w}, $self->{h}, sub {
835 glClearColor 0, 0, 0, 0; 875 glClearColor 0, 0, 0, 0;
836 glClear GL_COLOR_BUFFER_BIT; 876 glClear GL_COLOR_BUFFER_BIT;
837 877
838 { 878 {
839 package CFClient::UI::Base; 879 package DC::UI::Base;
840 880
841 ($draw_x, $draw_y, $draw_w, $draw_h) = 881 local ($draw_x, $draw_y, $draw_w, $draw_h) =
842 (0, 0, $self->{w}, $self->{h}); 882 (0, 0, $self->{w}, $self->{h});
883
884 $self->_render;
843 } 885 }
844
845 $self->_render;
846 }; 886 };
847} 887}
848 888
849sub _draw { 889sub _draw {
850 my ($self) = @_; 890 my ($self) = @_;
851
852 my ($w, $h) = @$self{qw(w h)};
853 891
854 my $tex = $self->{texture} 892 my $tex = $self->{texture}
855 or return; 893 or return;
856 894
857 glEnable GL_TEXTURE_2D; 895 glEnable GL_TEXTURE_2D;
858 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 896 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
859 glColor 0, 0, 0, 1; 897 glColor 0, 0, 0, 1;
860 898
861 $tex->draw_quad_alpha_premultiplied (0, 0, $w, $h); 899 $tex->draw_quad_alpha_premultiplied (0, 0);
862 900
863 glDisable GL_TEXTURE_2D; 901 glDisable GL_TEXTURE_2D;
864} 902}
865 903
866############################################################################# 904#############################################################################
867 905
868package CFClient::UI::ViewPort; 906package DC::UI::ViewPort;
869 907
908use List::Util qw(min max);
909
870our @ISA = CFClient::UI::Window::; 910our @ISA = DC::UI::Window::;
871 911
872sub new { 912sub new {
873 my $class = shift; 913 my $class = shift;
874 914
875 $class->SUPER::new ( 915 $class->SUPER::new (
882sub size_request { 922sub size_request {
883 my ($self) = @_; 923 my ($self) = @_;
884 924
885 my ($w, $h) = @{$self->child}{qw(req_w req_h)}; 925 my ($w, $h) = @{$self->child}{qw(req_w req_h)};
886 926
887 $w = 10 if $self->{scroll_x}; 927 $w = 1 if $self->{scroll_x};
888 $h = 10 if $self->{scroll_y}; 928 $h = 1 if $self->{scroll_y};
889 929
890 ($w, $h) 930 ($w, $h)
891} 931}
892 932
893sub invoke_size_allocate { 933sub invoke_size_allocate {
905} 945}
906 946
907sub set_offset { 947sub set_offset {
908 my ($self, $x, $y) = @_; 948 my ($self, $x, $y) = @_;
909 949
950 my $x = max 0, min $self->child->{w} - $self->{w}, int $x;
951 my $y = max 0, min $self->child->{h} - $self->{h}, int $y;
952
953 if ($x != $self->{view_x} or $y != $self->{view_y}) {
910 $self->{view_x} = int $x; 954 $self->{view_x} = $x;
911 $self->{view_y} = int $y; 955 $self->{view_y} = $y;
912 956
957 $self->emit (changed => $x, $y);
913 $self->update; 958 $self->update;
959 }
960}
961
962sub set_center {
963 my ($self, $x, $y) = @_;
964
965 $self->set_offset ($x - $self->{w} * .5, $y - $self->{h} * .5);
966}
967
968sub make_visible {
969 my ($self, $x, $y, $border) = @_;
970
971 if ( $x < $self->{view_x} + $self->{w} * $border
972 || $x > $self->{view_x} + $self->{w} * (1 - $border)
973 || $y < $self->{view_y} + $self->{h} * $border
974 || $y > $self->{view_y} + $self->{h} * (1 - $border)
975 ) {
976 $self->set_center ($x, $y);
977 }
914} 978}
915 979
916# hmm, this does not work for topleft of $self... but we should not ask for that 980# hmm, this does not work for topleft of $self... but we should not ask for that
917sub coord2local { 981sub coord2local {
918 my ($self, $x, $y) = @_; 982 my ($self, $x, $y) = @_;
933 my ($self, $x, $y) = @_; 997 my ($self, $x, $y) = @_;
934 998
935 if ( $x >= $self->{x} && $x < $self->{x} + $self->{w} 999 if ( $x >= $self->{x} && $x < $self->{x} + $self->{w}
936 && $y >= $self->{y} && $y < $self->{y} + $self->{h} 1000 && $y >= $self->{y} && $y < $self->{y} + $self->{h}
937 ) { 1001 ) {
938 $self->child->find_widget ($x + $self->{view_x}, $y + $self->{view_y}) 1002 $self->child->find_widget ($x + $self->{view_x}, $y + $self->{view_y})
939 } else { 1003 } else {
940 $self->CFClient::UI::Base::find_widget ($x, $y) 1004 $self->DC::UI::Base::find_widget ($x, $y)
941 } 1005 }
942} 1006}
943 1007
944sub _render { 1008sub _render {
945 my ($self) = @_; 1009 my ($self) = @_;
946 1010
947 local $CFClient::UI::Base::draw_x = $CFClient::UI::Base::draw_x - $self->{view_x}; 1011 local $DC::UI::Base::draw_x = $DC::UI::Base::draw_x - $self->{view_x};
948 local $CFClient::UI::Base::draw_y = $CFClient::UI::Base::draw_y - $self->{view_y}; 1012 local $DC::UI::Base::draw_y = $DC::UI::Base::draw_y - $self->{view_y};
949 1013
950 CFClient::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y}; 1014 DC::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y};
951 1015
952 $self->SUPER::_render; 1016 $self->SUPER::_render;
953} 1017}
954 1018
955############################################################################# 1019#############################################################################
956 1020
957package CFClient::UI::ScrolledWindow; 1021package DC::UI::ScrolledWindow;
958 1022
959our @ISA = CFClient::UI::HBox::; 1023our @ISA = DC::UI::Table::;
960 1024
961sub new { 1025sub new {
962 my ($class, %arg) = @_; 1026 my ($class, %arg) = @_;
963 1027
964 my $child = delete $arg{child}; 1028 my $child = delete $arg{child};
965 1029
966 my $self; 1030 my $self;
967 1031
968 my $slider = new CFClient::UI::Slider 1032 my $hslider = new DC::UI::Slider
1033 c_col => 0,
1034 c_row => 1,
1035 vertical => 0,
1036 range => [0, 0, 1, 0.01], # HACK fix
1037 on_changed => sub {
1038 $self->{hpos} = $_[1];
1039 $self->{vp}->set_offset ($self->{hpos}, $self->{vpos});
1040 },
1041 ;
1042
1043 my $vslider = new DC::UI::Slider
1044 c_col => 1,
1045 c_row => 0,
969 vertical => 1, 1046 vertical => 1,
970 range => [0, 0, 1, 0.01], # HACK fix 1047 range => [0, 0, 1, 0.01], # HACK fix
971 on_changed => sub { 1048 on_changed => sub {
972 $self->{vp}->set_offset (0, $_[1]); 1049 $self->{vpos} = $_[1];
1050 $self->{vp}->set_offset ($self->{hpos}, $self->{vpos});
973 }, 1051 },
974 ; 1052 ;
975 1053
976 $self = $class->SUPER::new ( 1054 $self = $class->SUPER::new (
977 vp => (new CFClient::UI::ViewPort expand => 1), 1055 scroll_x => 0,
1056 scroll_y => 1,
1057 can_events => 1,
978 slider => $slider, 1058 hslider => $hslider,
1059 vslider => $vslider,
1060 col_expand => [1, 0],
1061 row_expand => [1, 0],
979 %arg, 1062 %arg,
980 ); 1063 );
981 1064
1065 $self->{vp} = new DC::UI::ViewPort
1066 c_col => 0,
1067 c_row => 0,
1068 expand => 1,
1069 scroll_x => $self->{scroll_x},
1070 scroll_y => $self->{scroll_y},
1071 on_changed => sub {
1072 my ($vp, $x, $y) = @_;
1073
1074 $vp->{parent}{hslider}->set_value ($x);
1075 $vp->{parent}{vslider}->set_value ($y);
1076
1077 0
1078 },
1079 on_size_allocate => sub {
1080 my ($vp, $w, $h) = @_;
1081 $vp->{parent}->update_slider;
1082 0
1083 },
1084 ;
1085
982 $self->SUPER::add ($self->{vp}, $self->{slider}); 1086 $self->SUPER::add ($self->{vp});
1087
983 $self->add ($child) if $child; 1088 $self->add ($child) if $child;
984 1089
985 $self 1090 $self
986} 1091}
987 1092
989 my ($self, $widget) = @_; 1094 my ($self, $widget) = @_;
990 1095
991 $self->{vp}->add ($self->{child} = $widget); 1096 $self->{vp}->add ($self->{child} = $widget);
992} 1097}
993 1098
1099sub set_offset { shift->{vp}->set_offset (@_) }
1100sub set_center { shift->{vp}->set_center (@_) }
1101sub make_visible { shift->{vp}->make_visible (@_) }
1102
994sub update { 1103sub update_slider {
1104 my ($self) = @_;
1105
1106 my $child = ($self->{vp} or return)->child;
1107
1108 if ($self->{scroll_x}) {
1109 my ($w1, $w2) = ($child->{req_w}, $self->{vp}{w});
1110 $self->{hslider}->set_range ([$self->{hslider}{range}[0], 0, $w1, $w2, 1]);
1111
1112 my $visible = $w1 > $w2;
1113 if ($visible != $self->{hslider_visible}) {
1114 $self->{hslider_visible} = $visible;
1115 $visible ? $self->SUPER::add ($self->{hslider})
1116 : $self->SUPER::remove ($self->{hslider});
1117 }
1118 }
1119
1120 if ($self->{scroll_y}) {
1121 my ($h1, $h2) = ($child->{req_h}, $self->{vp}{h});
1122 $self->{vslider}->set_range ([$self->{vslider}{range}[0], 0, $h1, $h2, 1]);
1123
1124 my $visible = $h1 > $h2;
1125 if ($visible != $self->{vslider_visible}) {
1126 $self->{vslider_visible} = $visible;
1127 $visible ? $self->SUPER::add ($self->{vslider})
1128 : $self->SUPER::remove ($self->{vslider});
1129 }
1130 }
1131}
1132
1133sub start_dragging {
995 my ($self) = @_; 1134 my ($self, $ev) = @_;
996 1135
997 $self->SUPER::update; 1136 $self->grab_focus;
998 1137
999 # todo: overwrite size_allocate of child 1138 my $ox = $self->{vp}{view_x};
1000 my $child = $self->{vp}->child; 1139 my $oy = $self->{vp}{view_y};
1001 $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $child->{h}, $self->{vp}{h}, 1]); 1140
1141 $self->{motion} = sub {
1142 my ($ev, $x, $y) = @_;
1143
1144 $ox -= $ev->{xrel};
1145 $oy -= $ev->{yrel};
1146
1147 $self->{vp}->set_offset ($ox, $oy);
1148 };
1149}
1150
1151sub invoke_mouse_wheel {
1152 my ($self, $ev) = @_;
1153
1154 $self->{vslider}->emit (mouse_wheel => $ev) if $self->{vslider_visible};
1155 $self->{hslider}->emit (mouse_wheel => $ev) if $self->{hslider_visible};
1156
1157 1
1158}
1159
1160sub invoke_button_down {
1161 my ($self, $ev, $x, $y) = @_;
1162
1163 if ($ev->{button} == 2) {
1164 $self->start_dragging ($ev);
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
1002} 1190}
1003 1191
1004sub invoke_size_allocate { 1192sub invoke_size_allocate {
1005 my ($self, $w, $h) = @_; 1193 my ($self, $w, $h) = @_;
1006 1194
1007 my $child = $self->{vp}->child; 1195 $self->update_slider;
1008 $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $child->{h}, $self->{vp}{h}, 1]);
1009
1010 $self->SUPER::invoke_size_allocate ($w, $h) 1196 $self->SUPER::invoke_size_allocate ($w, $h)
1011} 1197}
1012 1198
1013#TODO# update range on size_allocate depending on child
1014# update viewport offset on scroll
1015
1016############################################################################# 1199#############################################################################
1017 1200
1018package CFClient::UI::Frame; 1201package DC::UI::Frame;
1019 1202
1020our @ISA = CFClient::UI::Bin::; 1203our @ISA = DC::UI::Bin::;
1021 1204
1022use CFClient::OpenGL; 1205use DC::OpenGL;
1023 1206
1024sub new { 1207sub new {
1025 my $class = shift; 1208 my $class = shift;
1026 1209
1027 $class->SUPER::new ( 1210 $class->SUPER::new (
1037 my ($w, $h) = @$self{qw(w h)}; 1220 my ($w, $h) = @$self{qw(w h)};
1038 1221
1039 glEnable GL_BLEND; 1222 glEnable GL_BLEND;
1040 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA; 1223 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
1041 glColor_premultiply @{ $self->{bg} }; 1224 glColor_premultiply @{ $self->{bg} };
1042
1043 glBegin GL_QUADS;
1044 glVertex 0 , 0;
1045 glVertex 0 , $h;
1046 glVertex $w, $h; 1225 glRect 0, 0, $w, $h;
1047 glVertex $w, 0;
1048 glEnd;
1049
1050 glDisable GL_BLEND; 1226 glDisable GL_BLEND;
1051 } 1227 }
1052 1228
1053 $self->SUPER::_draw; 1229 $self->SUPER::_draw;
1054} 1230}
1055 1231
1056############################################################################# 1232#############################################################################
1057 1233
1058package CFClient::UI::FancyFrame; 1234package DC::UI::FancyFrame;
1059 1235
1060our @ISA = CFClient::UI::Bin::; 1236our @ISA = DC::UI::Bin::;
1061 1237
1062use CFClient::OpenGL; 1238use DC::OpenGL;
1239
1240sub new {
1241 my ($class, %arg) = @_;
1242
1243 if ((exists $arg{label}) && !ref $arg{label}) {
1244 $arg{label} = new DC::UI::Label
1245 align => 1,
1246 valign => 0,
1247 text => $arg{label},
1248 fontsize => ($arg{border} || 0.8) * 0.75;
1249 }
1250
1251 my $self = $class->SUPER::new (
1252 # label => "",
1253 fg => [0.6, 0.3, 0.1],
1254 border => 0.8,
1255 style => 'single',
1256 %arg,
1257 );
1258
1259 $self
1260}
1261
1262sub add {
1263 my ($self, @widgets) = @_;
1264
1265 $self->SUPER::add (@widgets);
1266 $self->DC::UI::Container::add ($self->{label}) if $self->{label};
1267}
1268
1269sub border {
1270 int $_[0]{border} * $::FONTSIZE
1271}
1272
1273sub size_request {
1274 my ($self) = @_;
1275
1276 ($self->{label_w}, undef) = $self->{label}->size_request
1277 if $self->{label};
1278
1279 my ($w, $h) = $self->SUPER::size_request;
1280
1281 (
1282 $w + $self->border * 2,
1283 $h + $self->border * 2,
1284 )
1285}
1286
1287sub invoke_size_allocate {
1288 my ($self, $w, $h) = @_;
1289
1290 my $border = $self->border;
1291
1292 $w -= List::Util::max 0, $border * 2;
1293 $h -= List::Util::max 0, $border * 2;
1294
1295 if (my $label = $self->{label}) {
1296 $label->{w} = List::Util::max 0, List::Util::min $self->{label_w}, $w - $border * 2;
1297 $label->{h} = List::Util::min $h, $border;
1298 $label->invoke_size_allocate ($label->{w}, $label->{h});
1299 }
1300
1301 $self->child->configure ($border, $border, $w, $h);
1302
1303 1
1304}
1305
1306sub _draw {
1307 my ($self) = @_;
1308
1309 my $child = $self->{children}[0];
1310
1311 my $border = $self->border;
1312 my ($w, $h) = ($self->{w}, $self->{h});
1313
1314 $child->draw;
1315
1316 glColor @{$self->{fg}};
1317 glBegin GL_LINE_STRIP;
1318 glVertex $border * 1.5 , $border * 0.5 + 0.5;
1319 glVertex $border * 0.5 + 0.5, $border * 0.5 + 0.5;
1320 glVertex $border * 0.5 + 0.5, $h - $border * 0.5 + 0.5;
1321 glVertex $w - $border * 0.5 + 0.5, $h - $border * 0.5 + 0.5;
1322 glVertex $w - $border * 0.5 + 0.5, $border * 0.5 + 0.5;
1323 glVertex $self->{label} ? $border * 2 + $self->{label}{w} : $border * 1.5, $border * 0.5 + 0.5;
1324 glEnd;
1325
1326 if ($self->{label}) {
1327 glTranslate $border * 2, 0;
1328 $self->{label}->_draw;
1329 }
1330}
1331
1332#############################################################################
1333
1334package DC::UI::Toplevel;
1335
1336our @ISA = DC::UI::Bin::;
1337
1338use DC::OpenGL;
1063 1339
1064my $bg = 1340my $bg =
1065 new_from_file CFClient::Texture CFClient::find_rcfile "d1_bg.png", 1341 new_from_file DC::Texture DC::find_rcfile "d1_bg.png",
1066 mipmap => 1, wrap => 1; 1342 mipmap => 1, wrap => 1;
1067 1343
1068my @border = 1344my @border =
1069 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 1345 map { new_from_file DC::Texture DC::find_rcfile $_, mipmap => 1 }
1070 qw(d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png); 1346 qw(d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png);
1347
1348my @icon =
1349 map { new_from_file DC::Texture DC::find_rcfile $_, mipmap => 1 }
1350 qw(x1_move.png x1_resize.png);
1071 1351
1072sub new { 1352sub new {
1073 my ($class, %arg) = @_; 1353 my ($class, %arg) = @_;
1074 1354
1075 my $self = $class->SUPER::new ( 1355 my $self = $class->SUPER::new (
1076 bg => [1, 1, 1, 1], 1356 bg => [1, 1, 1, 1],
1077 border_bg => [1, 1, 1, 1], 1357 border_bg => [1, 1, 1, 1],
1078 border => 0.6, 1358 border => 0.6,
1079 can_events => 1, 1359 can_events => 1,
1080 min_w => 16, 1360 min_w => 64,
1081 min_h => 16, 1361 min_h => 32,
1082 %arg, 1362 %arg,
1083 ); 1363 );
1084 1364
1085 $self->{title_widget} = new CFClient::UI::Label 1365 $self->{title_widget} = new DC::UI::Label
1086 align => 0, 1366 align => 0,
1087 valign => 1, 1367 valign => 1,
1088 text => $self->{title}, 1368 text => $self->{title},
1089 fontsize => $self->{border}, 1369 fontsize => $self->{border},
1090 if exists $self->{title}; 1370 if exists $self->{title};
1091 1371
1092 if ($self->{has_close_button}) { 1372 if ($self->{has_close_button}) {
1093 $self->{close_button} = 1373 $self->{close_button} =
1094 new CFClient::UI::ImageButton 1374 new DC::UI::ImageButton
1095 image => 'x1_close.png', 1375 path => 'x1_close.png',
1096 on_activate => sub { $self->hide }; 1376 on_activate => sub { $self->emit ("delete") };
1097 1377
1098 $self->CFClient::UI::Container::add ($self->{close_button}); 1378 $self->DC::UI::Container::add ($self->{close_button});
1099 } 1379 }
1100 1380
1101 $self 1381 $self
1102} 1382}
1103 1383
1104sub add { 1384sub add {
1105 my ($self, @widgets) = @_; 1385 my ($self, @widgets) = @_;
1106 1386
1107 $self->SUPER::add (@widgets); 1387 $self->SUPER::add (@widgets);
1108 $self->CFClient::UI::Container::add ($self->{close_button}) if $self->{close_button}; 1388 $self->DC::UI::Container::add ($self->{close_button}) if $self->{close_button};
1109 $self->CFClient::UI::Container::add ($self->{title_widget}) if $self->{title_widget}; 1389 $self->DC::UI::Container::add ($self->{title_widget}) if $self->{title_widget};
1110} 1390}
1111 1391
1112sub border { 1392sub border {
1113 int $_[0]{border} * $::FONTSIZE 1393 int $_[0]{border} * $::FONTSIZE
1394}
1395
1396sub get_max_wh {
1397 my ($self) = @_;
1398
1399 return ($self->{w}, $self->{h})
1400 if $self->{visible} && $self->{w};
1401
1402 $self->SUPER::get_max_wh
1114} 1403}
1115 1404
1116sub size_request { 1405sub size_request {
1117 my ($self) = @_; 1406 my ($self) = @_;
1118 1407
1147 $self->child->configure ($border, $border, $w, $h); 1436 $self->child->configure ($border, $border, $w, $h);
1148 1437
1149 $self->{close_button}->configure ($self->{w} - $border, 0, $border, $border) 1438 $self->{close_button}->configure ($self->{w} - $border, 0, $border, $border)
1150 if $self->{close_button}; 1439 if $self->{close_button};
1151 1440
1441 1
1442}
1443
1444sub invoke_delete {
1445 my ($self) = @_;
1446
1447 $self->hide;
1448
1152 1 1449 1
1153} 1450}
1154 1451
1155sub invoke_button_down { 1452sub invoke_button_down {
1156 my ($self, $ev, $x, $y) = @_; 1453 my ($self, $ev, $x, $y) = @_;
1214 $self->{motion}->($ev, $x, $y) if $self->{motion}; 1511 $self->{motion}->($ev, $x, $y) if $self->{motion};
1215 1512
1216 ! ! $self->{motion} 1513 ! ! $self->{motion}
1217} 1514}
1218 1515
1516sub invoke_visibility_change {
1517 my ($self, $visible) = @_;
1518
1519 delete $self->{motion} unless $visible;
1520
1521 0
1522}
1523
1219sub _draw { 1524sub _draw {
1220 my ($self) = @_; 1525 my ($self) = @_;
1221 1526
1222 my $child = $self->{children}[0]; 1527 my $child = $self->{children}[0];
1223 1528
1228 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE; 1533 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
1229 1534
1230 my $border = $self->border; 1535 my $border = $self->border;
1231 1536
1232 glColor @{ $self->{border_bg} }; 1537 glColor @{ $self->{border_bg} };
1233 $border[0]->draw_quad_alpha (0, 0, $w, $border); 1538 $border[0]->draw_quad_alpha ( 0, 0, $w, $border);
1234 $border[1]->draw_quad_alpha (0, $border, $border, $ch); 1539 $border[1]->draw_quad_alpha ( 0, $border, $border, $ch);
1235 $border[2]->draw_quad_alpha ($w - $border, $border, $border, $ch); 1540 $border[2]->draw_quad_alpha ($w - $border, $border, $border, $ch);
1236 $border[3]->draw_quad_alpha (0, $h - $border, $w, $border); 1541 $border[3]->draw_quad_alpha ( 0, $h - $border, $w, $border);
1542
1543 # move
1544 my $w2 = ($w - $border) * .5;
1545 my $h2 = ($h - $border) * .5;
1546 $icon[0]->draw_quad_alpha ( 0, $h2, $border, $border);
1547 $icon[0]->draw_quad_alpha ($w - $border, $h2, $border, $border);
1548 $icon[0]->draw_quad_alpha ($w2 , $h - $border, $border, $border);
1549
1550 # resize
1551 $icon[1]->draw_quad_alpha ( 0, 0, $border, $border);
1552 $icon[1]->draw_quad_alpha ($w - $border, 0, $border, $border)
1553 unless $self->{has_close_button};
1554 $icon[1]->draw_quad_alpha ( 0, $h - $border, $border, $border);
1555 $icon[1]->draw_quad_alpha ($w - $border, $h - $border, $border, $border);
1237 1556
1238 if (@{$self->{bg}} < 4 || $self->{bg}[3]) { 1557 if (@{$self->{bg}} < 4 || $self->{bg}[3]) {
1239 glColor @{ $self->{bg} }; 1558 glColor @{ $self->{bg} };
1240 1559
1241 # TODO: repeat texture not scale 1560 # TODO: repeat texture not scale
1260 if $self->{close_button}; 1579 if $self->{close_button};
1261} 1580}
1262 1581
1263############################################################################# 1582#############################################################################
1264 1583
1265package CFClient::UI::Table; 1584package DC::UI::Table;
1266 1585
1267our @ISA = CFClient::UI::Base::; 1586our @ISA = DC::UI::Container::;
1268 1587
1269use List::Util qw(max sum); 1588use List::Util qw(max sum);
1270 1589
1271use CFClient::OpenGL; 1590use DC::OpenGL;
1272 1591
1273sub new { 1592sub new {
1274 my $class = shift; 1593 my $class = shift;
1275 1594
1276 $class->SUPER::new ( 1595 $class->SUPER::new (
1277 col_expand => [], 1596 col_expand => [],
1597 row_expand => [],
1278 @_, 1598 @_,
1279 ) 1599 )
1280} 1600}
1281 1601
1282sub children {
1283 grep $_, map @$_, grep $_, @{ $_[0]{children} }
1284}
1285
1286sub add { 1602sub add {
1287 my ($self, $x, $y, $child) = @_; 1603 my ($self, @widgets) = @_;
1288 1604
1289 $child->set_parent ($self); 1605 for my $child (@widgets) {
1290 $self->{children}[$y][$x] = $child; 1606 $child->{c_rowspan} ||= 1;
1607 $child->{c_colspan} ||= 1;
1608 }
1291 1609
1292 $self->realloc; 1610 $self->SUPER::add (@widgets);
1293} 1611}
1294 1612
1295sub remove { 1613sub add_at {
1614 my $self = shift;
1615
1616 my @widgets;
1617
1618 while (@_) {
1619 my ($col, $row, $child) = splice @_, 0, 3, ();
1620
1621 $child->{c_row} = $row;
1622 $child->{c_col} = $col;
1623
1624 push @widgets, $child;
1625 }
1626
1627 $self->add (@widgets);
1628}
1629
1630sub get_wh {
1296 my ($self, $child) = @_; 1631 my ($self) = @_;
1297 1632
1298 # TODO: not yet implemented 1633 my (@w, @h);
1299}
1300
1301# TODO: move to container class maybe? send children a signal on removal?
1302sub clear {
1303 my ($self) = @_;
1304 1634
1305 my @children = $self->children; 1635 my @children = $self->children;
1306 delete $self->{children}; 1636
1637 # first pass, columns
1638 for my $widget (sort { $a->{c_colspan} <=> $b->{c_colspan} } @children) {
1639 my ($c, $w, $cs) = @$widget{qw(c_col req_w c_colspan)};
1640
1641 my $sw = sum @w[$c .. $c + $cs - 1];
1642
1643 if ($w > $sw) {
1644 $_ += ($w - $sw) / ($_ ? $sw / $_ : $cs) for @w[$c .. $c + $cs - 1];
1645 }
1307 1646 }
1308 for (@children) {
1309 delete $_->{parent};
1310 $_->hide;
1311 }
1312 1647
1313 $self->realloc; 1648 # second pass, rows
1314} 1649 for my $widget (sort { $a->{c_rowspan} <=> $b->{c_rowspan} } @children) {
1315
1316sub get_wh {
1317 my ($self) = @_;
1318
1319 my (@w, @h);
1320
1321 for my $y (0 .. $#{$self->{children}}) {
1322 my $row = $self->{children}[$y]
1323 or next;
1324
1325 for my $x (0 .. $#$row) {
1326 my $widget = $row->[$x]
1327 or next;
1328 my ($w, $h) = @$widget{qw(req_w req_h)}; 1650 my ($r, $h, $rs) = @$widget{qw(c_row req_h c_rowspan)};
1329 1651
1330 $w[$x] = max $w[$x], $w; 1652 my $sh = sum @h[$r .. $r + $rs - 1];
1331 $h[$y] = max $h[$y], $h; 1653
1654 if ($h > $sh) {
1655 $_ += ($h - $sh) / ($_ ? $sh / $_ : $rs) for @h[$r .. $r + $rs - 1];
1332 } 1656 }
1333 } 1657 }
1334 1658
1335 (\@w, \@h) 1659 (\@w, \@h)
1336} 1660}
1352 my ($ws, $hs) = $self->get_wh; 1676 my ($ws, $hs) = $self->get_wh;
1353 1677
1354 my $req_w = (sum @$ws) || 1; 1678 my $req_w = (sum @$ws) || 1;
1355 my $req_h = (sum @$hs) || 1; 1679 my $req_h = (sum @$hs) || 1;
1356 1680
1357 # TODO: nicer code && do row_expand 1681 # now linearly scale the rows/columns to the allocated size
1358 my @col_expand = @{$self->{col_expand}}; 1682 my @col_expand = @{$self->{col_expand}};
1359 @col_expand = (1) x @$ws unless @col_expand; 1683 @col_expand = (1) x @$ws unless @col_expand;
1360 my $col_expand = (sum @col_expand) || 1; 1684 my $col_expand = (sum @col_expand) || 1;
1361 1685
1362 # linearly scale sizes
1363 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws; 1686 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws;
1364 $hs->[$_] *= 1 * $h / $req_h for 0 .. $#$hs;
1365 1687
1366 CFClient::UI::harmonize $ws; 1688 DC::UI::harmonize $ws;
1689
1690 my @row_expand = @{$self->{row_expand}};
1691 @row_expand = (1) x @$ws unless @row_expand;
1692 my $row_expand = (sum @row_expand) || 1;
1693
1694 $hs->[$_] += $row_expand[$_] / $row_expand * ($h - $req_h) for 0 .. $#$hs;
1695
1367 CFClient::UI::harmonize $hs; 1696 DC::UI::harmonize $hs;
1368 1697
1369 my $y; 1698 my @x; for (0 .. $#$ws) { $x[$_ + 1] = $x[$_] + $ws->[$_] }
1699 my @y; for (0 .. $#$hs) { $y[$_ + 1] = $y[$_] + $hs->[$_] }
1370 1700
1371 for my $r (0 .. $#{$self->{children}}) { 1701 for my $widget ($self->children) {
1372 my $row = $self->{children}[$r] 1702 my ($r, $c, $w, $h, $rs, $cs) = @$widget{qw(c_row c_col req_w req_h c_rowspan c_colspan)};
1373 or next;
1374 1703
1375 my $x = 0; 1704 $widget->configure (
1376 my $row_h = $hs->[$r]; 1705 $x[$c], $y[$r],
1706 $x[$c + $cs] - $x[$c], $y[$r + $rs] - $y[$r],
1377 1707 );
1378 for my $c (0 .. $#$row) { 1708 }
1379 my $col_w = $ws->[$c];
1380 1709
1381 if (my $widget = $row->[$c]) { 1710 1
1382 $widget->configure ($x, $y, $col_w, $row_h); 1711}
1383 }
1384 1712
1385 $x += $col_w; 1713#############################################################################
1714
1715package DC::UI::Fixed;
1716
1717use List::Util qw(min max);
1718
1719our @ISA = DC::UI::Container::;
1720
1721sub _scale($$$) {
1722 my ($rel, $val, $max) = @_;
1723
1724 $rel ? $val * $max : $val
1725}
1726
1727sub size_request {
1728 my ($self) = @_;
1729
1730 my ($x1, $y1, $x2, $y2) = (0, 0, 0, 0);
1731
1732 # determine overall size by querying abs widgets
1733 for my $child ($self->visible_children) {
1734 unless ($child->{c_rel}) {
1735 my $x = $child->{c_x};
1736 my $y = $child->{c_y};
1737
1738 $x1 = min $x1, $x; $x2 = max $x2, $x + $child->{req_w};
1739 $y1 = min $y1, $y; $y2 = max $y2, $y + $child->{req_h};
1386 } 1740 }
1741 }
1387 1742
1388 $y += $row_h; 1743 my $W = $x2 - $x1;
1744 my $H = $y2 - $y1;
1745
1746 # now layout remaining widgets
1747 for my $child ($self->visible_children) {
1748 if ($child->{c_rel}) {
1749 my $x = _scale $child->{c_rel}, $child->{c_x}, $W;
1750 my $y = _scale $child->{c_rel}, $child->{c_y}, $H;
1751
1752 $x1 = min $x1, $x; $x2 = max $x2, $x + $child->{req_w};
1753 $y1 = min $y1, $y; $y2 = max $y2, $y + $child->{req_h};
1754 }
1755 }
1756
1757 my $W = $x2 - $x1;
1758 my $H = $y2 - $y1;
1759
1760 ($W, $H)
1761}
1762
1763sub invoke_size_allocate {
1764 my ($self, $W, $H) = @_;
1765
1766 for my $child ($self->visible_children) {
1767 my $x = _scale $child->{c_rel}, $child->{c_x}, $W;
1768 my $y = _scale $child->{c_rel}, $child->{c_y}, $H;
1769
1770 $x += $child->{c_halign} * $child->{req_w};
1771 $y += $child->{c_valign} * $child->{req_h};
1772
1773 $child->configure (int $x, int $y, $child->{req_w}, $child->{req_h});
1389 } 1774 }
1390 1775
1391 1 1776 1
1392} 1777}
1393 1778
1394sub find_widget {
1395 my ($self, $x, $y) = @_;
1396
1397 $x -= $self->{x};
1398 $y -= $self->{y};
1399
1400 my $res;
1401
1402 for (grep $_, map @$_, grep $_, @{ $self->{children} }) {
1403 $res = $_->find_widget ($x, $y)
1404 and return $res;
1405 }
1406
1407 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
1408}
1409
1410sub _draw {
1411 my ($self) = @_;
1412
1413 for (grep $_, @{$self->{children}}) {
1414 $_->draw for grep $_, @$_;
1415 }
1416}
1417
1418############################################################################# 1779#############################################################################
1419 1780
1420package CFClient::UI::Box; 1781package DC::UI::Box;
1421 1782
1422our @ISA = CFClient::UI::Container::; 1783our @ISA = DC::UI::Container::;
1423 1784
1424sub size_request { 1785sub size_request {
1425 my ($self) = @_; 1786 my ($self) = @_;
1787
1788 my @children = $self->visible_children;
1426 1789
1427 $self->{vertical} 1790 $self->{vertical}
1428 ? ( 1791 ? (
1429 (List::Util::max map $_->{req_w}, @{$self->{children}}), 1792 (List::Util::max map $_->{req_w}, @children),
1430 (List::Util::sum map $_->{req_h}, @{$self->{children}}), 1793 (List::Util::sum map $_->{req_h}, @children),
1431 ) 1794 )
1432 : ( 1795 : (
1433 (List::Util::sum map $_->{req_w}, @{$self->{children}}), 1796 (List::Util::sum map $_->{req_w}, @children),
1434 (List::Util::max map $_->{req_h}, @{$self->{children}}), 1797 (List::Util::max map $_->{req_h}, @children),
1435 ) 1798 )
1436} 1799}
1437 1800
1438sub invoke_size_allocate { 1801sub invoke_size_allocate {
1439 my ($self, $w, $h) = @_; 1802 my ($self, $w, $h) = @_;
1440 1803
1441 my $space = $self->{vertical} ? $h : $w; 1804 my $space = $self->{vertical} ? $h : $w;
1442 my $children = $self->{children}; 1805 my @children = $self->visible_children;
1443 1806
1444 my @req; 1807 my @req;
1445 1808
1446 if ($self->{homogeneous}) { 1809 if ($self->{homogeneous}) {
1447 @req = ($space / (@$children || 1)) x @$children; 1810 @req = ($space / (@children || 1)) x @children;
1448 } else { 1811 } else {
1449 @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @$children; 1812 @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @children;
1450 my $req = List::Util::sum @req; 1813 my $req = List::Util::sum @req;
1451 1814
1452 if ($req > $space) { 1815 if ($req > $space) {
1453 # ah well, not enough space 1816 # ah well, not enough space
1454 $_ *= $space / $req for @req; 1817 $_ *= $space / $req for @req;
1455 } else { 1818 } else {
1456 my $expand = (List::Util::sum map $_->{expand}, @$children) || 1; 1819 my $expand = (List::Util::sum map $_->{expand}, @children) || 1;
1457 1820
1458 $space = ($space - $req) / $expand; # remaining space to give away 1821 $space = ($space - $req) / $expand; # remaining space to give away
1459 1822
1460 $req[$_] += $space * $children->[$_]{expand} 1823 $req[$_] += $space * $children[$_]{expand}
1461 for 0 .. $#$children; 1824 for 0 .. $#children;
1462 } 1825 }
1463 } 1826 }
1464 1827
1465 CFClient::UI::harmonize \@req; 1828 DC::UI::harmonize \@req;
1466 1829
1467 my $pos = 0; 1830 my $pos = 0;
1468 for (0 .. $#$children) { 1831 for (0 .. $#children) {
1469 my $alloc = $req[$_]; 1832 my $alloc = $req[$_];
1470 $children->[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h)); 1833 $children[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h));
1471 1834
1472 $pos += $alloc; 1835 $pos += $alloc;
1473 } 1836 }
1474 1837
1475 1 1838 1
1476} 1839}
1477 1840
1478############################################################################# 1841#############################################################################
1479 1842
1480package CFClient::UI::HBox; 1843package DC::UI::HBox;
1481 1844
1482our @ISA = CFClient::UI::Box::; 1845our @ISA = DC::UI::Box::;
1483 1846
1484sub new { 1847sub new {
1485 my $class = shift; 1848 my $class = shift;
1486 1849
1487 $class->SUPER::new ( 1850 $class->SUPER::new (
1490 ) 1853 )
1491} 1854}
1492 1855
1493############################################################################# 1856#############################################################################
1494 1857
1495package CFClient::UI::VBox; 1858package DC::UI::VBox;
1496 1859
1497our @ISA = CFClient::UI::Box::; 1860our @ISA = DC::UI::Box::;
1498 1861
1499sub new { 1862sub new {
1500 my $class = shift; 1863 my $class = shift;
1501 1864
1502 $class->SUPER::new ( 1865 $class->SUPER::new (
1505 ) 1868 )
1506} 1869}
1507 1870
1508############################################################################# 1871#############################################################################
1509 1872
1510package CFClient::UI::Label; 1873package DC::UI::Label;
1511 1874
1512our @ISA = CFClient::UI::DrawBG::; 1875our @ISA = DC::UI::DrawBG::;
1513 1876
1514use CFClient::OpenGL; 1877use DC::OpenGL;
1515 1878
1516sub new { 1879sub new {
1517 my ($class, %arg) = @_; 1880 my ($class, %arg) = @_;
1518 1881
1519 my $self = $class->SUPER::new ( 1882 my $self = $class->SUPER::new (
1522 #active_bg => none 1885 #active_bg => none
1523 #font => default_font 1886 #font => default_font
1524 #text => initial text 1887 #text => initial text
1525 #markup => initial narkup 1888 #markup => initial narkup
1526 #max_w => maximum pixel width 1889 #max_w => maximum pixel width
1890 #style => 0, # render flags
1527 ellipsise => 3, # end 1891 ellipsise => 3, # end
1528 layout => (new CFClient::Layout), 1892 layout => (new DC::Layout),
1529 fontsize => 1, 1893 fontsize => 1,
1530 align => -1, 1894 align => -1,
1531 valign => -1, 1895 valign => -1,
1532 padding_x => 2, 1896 padding_x => 2,
1533 padding_y => 2, 1897 padding_y => 2,
1534 can_events => 0, 1898 can_events => 0,
1535 %arg 1899 %arg
1536 ); 1900 );
1537 1901
1538 if (exists $self->{template}) { 1902 if (exists $self->{template}) {
1539 my $layout = new CFClient::Layout; 1903 my $layout = new DC::Layout;
1540 $layout->set_text (delete $self->{template}); 1904 $layout->set_text (delete $self->{template});
1541 $self->{template} = $layout; 1905 $self->{template} = $layout;
1542 } 1906 }
1543 1907
1544 if (exists $self->{markup}) { 1908 if (exists $self->{markup}) {
1548 } 1912 }
1549 1913
1550 $self 1914 $self
1551} 1915}
1552 1916
1553sub escape($) {
1554 local $_ = $_[0];
1555
1556 s/&/&amp;/g;
1557 s/>/&gt;/g;
1558 s/</&lt;/g;
1559
1560 $_
1561}
1562
1563sub update { 1917sub update {
1564 my ($self) = @_; 1918 my ($self) = @_;
1565 1919
1566 delete $self->{texture}; 1920 delete $self->{texture};
1567 $self->SUPER::update; 1921 $self->SUPER::update;
1572 1926
1573 delete $self->{ox}; 1927 delete $self->{ox};
1574 $self->SUPER::realloc; 1928 $self->SUPER::realloc;
1575} 1929}
1576 1930
1931sub clear {
1932 my ($self) = @_;
1933
1934 $self->set_text ("");
1935}
1936
1577sub set_text { 1937sub set_text {
1578 my ($self, $text) = @_; 1938 my ($self, $text) = @_;
1579 1939
1580 return if $self->{text} eq "T$text"; 1940 return if $self->{text} eq "T$text";
1581 $self->{text} = "T$text"; 1941 $self->{text} = "T$text";
1582 1942
1583 $self->{layout} = new CFClient::Layout if $self->{layout}->is_rgba;
1584 $self->{layout}->set_text ($text); 1943 $self->{layout}->set_text ($text);
1585 1944
1586 delete $self->{size_req}; 1945 delete $self->{size_req};
1587 $self->realloc; 1946 $self->realloc;
1588 $self->update; 1947 $self->update;
1594 return if $self->{text} eq "M$markup"; 1953 return if $self->{text} eq "M$markup";
1595 $self->{text} = "M$markup"; 1954 $self->{text} = "M$markup";
1596 1955
1597 my $rgba = $markup =~ /span.*(?:foreground|background)/; 1956 my $rgba = $markup =~ /span.*(?:foreground|background)/;
1598 1957
1599 $self->{layout} = new CFClient::Layout $rgba if $self->{layout}->is_rgba != $rgba;
1600 $self->{layout}->set_markup ($markup); 1958 $self->{layout}->set_markup ($markup);
1601 1959
1602 delete $self->{size_req}; 1960 delete $self->{size_req};
1603 $self->realloc; 1961 $self->realloc;
1604 $self->update; 1962 $self->update;
1606 1964
1607sub size_request { 1965sub size_request {
1608 my ($self) = @_; 1966 my ($self) = @_;
1609 1967
1610 $self->{size_req} ||= do { 1968 $self->{size_req} ||= do {
1969 my ($max_w, $max_h) = $self->get_max_wh;
1970
1611 $self->{layout}->set_font ($self->{font}) if $self->{font}; 1971 $self->{layout}->set_font ($self->{font}) if $self->{font};
1612 $self->{layout}->set_width ($self->{max_w} || -1); 1972 $self->{layout}->set_width ($self->{max_w} || $max_w || -1);
1613 $self->{layout}->set_ellipsise ($self->{ellipsise}); 1973 $self->{layout}->set_ellipsise ($self->{ellipsise});
1614 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise}); 1974 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1615 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 1975 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1616 1976
1617 my ($w, $h) = $self->{layout}->size; 1977 my ($w, $h) = $self->{layout}->size;
1618 1978
1619 if (exists $self->{template}) { 1979 if (exists $self->{template}) {
1620 $self->{template}->set_font ($self->{font}) if $self->{font}; 1980 $self->{template}->set_font ($self->{font}) if $self->{font};
1981 $self->{template}->set_width ($self->{max_w} || -1);
1621 $self->{template}->set_height ($self->{fontsize} * $::FONTSIZE); 1982 $self->{template}->set_height ($self->{fontsize} * $::FONTSIZE);
1622 1983
1623 my ($w2, $h2) = $self->{template}->size; 1984 my ($w2, $h2) = $self->{template}->size;
1624 1985
1625 $w = List::Util::max $w, $w2; 1986 $w = List::Util::max $w, $w2;
1630 }; 1991 };
1631 1992
1632 @{ $self->{size_req} } 1993 @{ $self->{size_req} }
1633} 1994}
1634 1995
1996sub baseline_shift {
1997 $_[0]{layout}->descent
1998}
1999
1635sub invoke_size_allocate { 2000sub invoke_size_allocate {
1636 my ($self, $w, $h) = @_; 2001 my ($self, $w, $h) = @_;
1637 2002
1638 delete $self->{ox}; 2003 delete $self->{ox};
1639 2004
1645 2010
1646sub set_fontsize { 2011sub set_fontsize {
1647 my ($self, $fontsize) = @_; 2012 my ($self, $fontsize) = @_;
1648 2013
1649 $self->{fontsize} = $fontsize; 2014 $self->{fontsize} = $fontsize;
2015 delete $self->{size_req};
1650 delete $self->{texture}; 2016 delete $self->{texture};
1651 2017
1652 $self->realloc; 2018 $self->realloc;
1653} 2019}
1654 2020
1655sub reconfigure { 2021sub reconfigure {
1656 my ($self) = @_; 2022 my ($self) = @_;
1657 2023
1658 delete $self->{size_req}; 2024 delete $self->{size_req};
2025 delete $self->{texture};
1659 2026
1660 $self->SUPER::reconfigure; 2027 $self->SUPER::reconfigure;
1661} 2028}
1662 2029
1663sub _draw { 2030sub _draw {
1664 my ($self) = @_; 2031 my ($self) = @_;
1665 2032
1666 $self->SUPER::_draw; # draw background, if applicable 2033 $self->SUPER::_draw; # draw background, if applicable
1667 2034
1668 my $tex = $self->{texture} ||= do { 2035 my $size = $self->{texture} ||= do {
1669 $self->{layout}->set_foreground (@{$self->{fg}}); 2036 $self->{layout}->set_foreground (@{$self->{fg}});
1670 $self->{layout}->set_font ($self->{font}) if $self->{font}; 2037 $self->{layout}->set_font ($self->{font}) if $self->{font};
1671 $self->{layout}->set_width ($self->{w}); 2038 $self->{layout}->set_width ($self->{w});
1672 $self->{layout}->set_ellipsise ($self->{ellipsise}); 2039 $self->{layout}->set_ellipsise ($self->{ellipsise});
1673 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise}); 2040 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1674 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 2041 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1675 2042
1676 new_from_layout CFClient::Texture $self->{layout} 2043 [$self->{layout}->size]
1677 }; 2044 };
1678 2045
1679 unless (exists $self->{ox}) { 2046 unless (exists $self->{ox}) {
1680 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x} 2047 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x}
1681 : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding_x} 2048 : $self->{align} > 0 ? $self->{w} - $size->[0] - $self->{padding_x}
1682 : ($self->{w} - $tex->{w}) * 0.5); 2049 : ($self->{w} - $size->[0]) * 0.5);
1683 2050
1684 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y} 2051 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y}
1685 : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding_y} 2052 : $self->{valign} > 0 ? $self->{h} - $size->[1] - $self->{padding_y}
1686 : ($self->{h} - $tex->{h}) * 0.5); 2053 : ($self->{h} - $size->[1]) * 0.5);
2054
2055 $self->{layout}->render ($self->{ox}, $self->{oy}, $self->{style});
1687 }; 2056 };
1688 2057
1689 glEnable GL_TEXTURE_2D; 2058# unless ($self->{list}) {
1690 2059# $self->{list} = DC::OpenGL::glGenList;
1691 my $w = List::Util::min $self->{w} + 4, $tex->{w}; 2060# DC::OpenGL::glNewList $self->{list};
1692 my $h = List::Util::min $self->{h} + 2, $tex->{h}; 2061# $self->{layout}->render ($self->{ox}, $self->{oy}, $self->{style});
1693 2062# DC::OpenGL::glEndList;
1694 if ($tex->{format} == GL_ALPHA) {
1695 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
1696 glColor @{$self->{fg}};
1697 $tex->draw_quad_alpha ($self->{ox}, $self->{oy}, $w, $h);
1698 } else {
1699 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1700 $tex->draw_quad_alpha_premultiplied ($self->{ox}, $self->{oy}, $w, $h);
1701 } 2063# }
2064#
2065# DC::OpenGL::glCallList $self->{list};
1702 2066
1703 glDisable GL_TEXTURE_2D; 2067 $self->{layout}->draw;
1704} 2068}
2069
2070#sub destroy {
2071# my ($self) = @_;
2072#
2073# DC::OpenGL::glDeleteList delete $self->{list} if $self->{list};
2074#
2075# $self->SUPER::destroy;
2076#}
1705 2077
1706############################################################################# 2078#############################################################################
1707 2079
1708package CFClient::UI::EntryBase; 2080package DC::UI::EntryBase;
1709 2081
1710our @ISA = CFClient::UI::Label::; 2082our @ISA = DC::UI::Label::;
1711 2083
1712use CFClient::OpenGL; 2084use DC::OpenGL;
1713 2085
1714sub new { 2086sub new {
1715 my $class = shift; 2087 my $class = shift;
1716 2088
1717 $class->SUPER::new ( 2089 $class->SUPER::new (
1718 fg => [1, 1, 1], 2090 fg => [1, 1, 1],
1719 bg => [0, 0, 0, 0.2], 2091 bg => [0, 0, 0, 0.2],
2092 outline => [0.6, 0.3, 0.1],
1720 active_bg => [1, 1, 1, 0.5], 2093 active_bg => [0, 0, 1, .2],
1721 active_fg => [0, 0, 0], 2094 active_fg => [1, 1, 1],
2095 active_outline => [1, 1, 0],
1722 can_hover => 1, 2096 can_hover => 1,
1723 can_focus => 1, 2097 can_focus => 1,
1724 valign => 0, 2098 valign => 0,
1725 can_events => 1, 2099 can_events => 1,
2100 ellipsise => 0,
2101 padding_x => 4,
2102 padding_y => 2,
1726 #text => ... 2103 #text => ...
1727 #hidden => "*", 2104 #hidden => "*",
1728 @_ 2105 @_
1729 ) 2106 )
1730} 2107}
1775 my $sym = $ev->{sym}; 2152 my $sym = $ev->{sym};
1776 my $uni = $ev->{unicode}; 2153 my $uni = $ev->{unicode};
1777 2154
1778 my $text = $self->get_text; 2155 my $text = $self->get_text;
1779 2156
2157 $self->{cursor} = List::Util::max 0, List::Util::min $self->{cursor}, length $text;
2158
1780 if ($uni == 8) { 2159 if ($uni == 8) {
1781 substr $text, --$self->{cursor}, 1, "" if $self->{cursor}; 2160 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1782 } elsif ($uni == 127) { 2161 } elsif ($uni == 127) {
1783 substr $text, $self->{cursor}, 1, ""; 2162 substr $text, $self->{cursor}, 1, "";
1784 } elsif ($sym == CFClient::SDLK_LEFT) { 2163 } elsif ($sym == DC::SDLK_LEFT) {
1785 --$self->{cursor} if $self->{cursor}; 2164 --$self->{cursor} if $self->{cursor};
1786 } elsif ($sym == CFClient::SDLK_RIGHT) { 2165 } elsif ($sym == DC::SDLK_RIGHT) {
1787 ++$self->{cursor} if $self->{cursor} < length $self->{text}; 2166 ++$self->{cursor} if $self->{cursor} < length $self->{text};
1788 } elsif ($sym == CFClient::SDLK_HOME) { 2167 } elsif ($sym == DC::SDLK_HOME) {
2168 # what a hack
2169 $self->{cursor} =
2170 (substr $self->{text}, 0, $self->{cursor}) =~ /^(.*\012)/
2171 ? length $1
2172 : 0;
2173 } elsif ($sym == DC::SDLK_END) {
2174 # uh, again
2175 $self->{cursor} =
2176 (substr $self->{text}, $self->{cursor}) =~ /^([^\012]*)\012/
2177 ? $self->{cursor} + length $1
2178 : length $self->{text};
2179 } elsif ($uni == 21) { # ctrl-u
2180 $text = "";
1789 $self->{cursor} = 0; 2181 $self->{cursor} = 0;
1790 } elsif ($sym == CFClient::SDLK_END) {
1791 $self->{cursor} = length $text;
1792 } elsif ($uni == 27) { 2182 } elsif ($uni == 27) {
1793 $self->emit ('escape'); 2183 $self->emit ('escape');
1794 } elsif ($uni) { 2184 } elsif ($uni == 0x0d) {
2185 substr $text, $self->{cursor}++, 0, "\012";
2186 } elsif ($uni >= 0x20) {
1795 substr $text, $self->{cursor}++, 0, chr $uni; 2187 substr $text, $self->{cursor}++, 0, chr $uni;
1796 } else { 2188 } else {
1797 return 0; 2189 return 0;
1798 } 2190 }
1799 2191
1800 $self->_set_text ($text); 2192 $self->_set_text ($text);
1801 2193
1802 $self->realloc; 2194 $self->realloc;
2195 $self->update;
1803 2196
1804 1 2197 1
1805} 2198}
1806 2199
1807sub invoke_focus_in { 2200sub invoke_focus_in {
1819 2212
1820 my $idx = $self->{layout}->xy_to_index ($x, $y); 2213 my $idx = $self->{layout}->xy_to_index ($x, $y);
1821 2214
1822 # byte-index to char-index 2215 # byte-index to char-index
1823 my $text = $self->{text}; 2216 my $text = $self->{text};
1824 utf8::encode $text; 2217 utf8::encode $text; $text = substr $text, 0, $idx; utf8::decode $text;
1825 $self->{cursor} = length substr $text, 0, $idx; 2218 $self->{cursor} = length $text;
1826 2219
1827 $self->_set_text ($self->{text}); 2220 $self->_set_text ($self->{text});
1828 $self->update; 2221 $self->update;
1829 2222
1830 1 2223 1
1849 glColor_premultiply @{$self->{bg}}; 2242 glColor_premultiply @{$self->{bg}};
1850 } 2243 }
1851 2244
1852 glEnable GL_BLEND; 2245 glEnable GL_BLEND;
1853 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA; 2246 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
1854 glBegin GL_QUADS;
1855 glVertex 0 , 0;
1856 glVertex 0 , $self->{h};
1857 glVertex $self->{w}, $self->{h}; 2247 glRect 0, 0, $self->{w}, $self->{h};
1858 glVertex $self->{w}, 0;
1859 glEnd;
1860 glDisable GL_BLEND; 2248 glDisable GL_BLEND;
1861 2249
1862 $self->SUPER::_draw; 2250 $self->SUPER::_draw;
1863 2251
1864 #TODO: force update every cursor change :( 2252 #TODO: force update every cursor change :(
1866 2254
1867 unless (exists $self->{cur_h}) { 2255 unless (exists $self->{cur_h}) {
1868 my $text = substr $self->{text}, 0, $self->{cursor}; 2256 my $text = substr $self->{text}, 0, $self->{cursor};
1869 utf8::encode $text; 2257 utf8::encode $text;
1870 2258
1871 @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text) 2259 @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text);
1872 } 2260 }
1873 2261
1874 glColor @{$self->{fg}}; 2262 glColor_premultiply @{$self->{active_fg}};
1875 glBegin GL_LINES; 2263 glBegin GL_LINES;
1876 glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy}; 2264 glVertex $self->{cur_x} + $self->{ox} + .5, $self->{cur_y} + $self->{oy};
1877 glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy} + $self->{cur_h}; 2265 glVertex $self->{cur_x} + $self->{ox} + .5, $self->{cur_y} + $self->{oy} + $self->{cur_h};
1878 glEnd; 2266 glEnd;
1879 }
1880}
1881 2267
2268 glLineWidth 3;
2269 glColor @{$self->{active_outline}};
2270 glRect_lineloop 1.5, 1.5, $self->{w} - 1.5, $self->{h} - 1.5;
2271 glLineWidth 1;
2272
2273 } else {
2274 glColor @{$self->{outline}};
2275 glBegin GL_LINE_STRIP;
2276 glVertex .5, $self->{h} * .5;
2277 glVertex .5, $self->{h} - 2.5;
2278 glVertex $self->{w} - .5, $self->{h} - 2.5;
2279 glVertex $self->{w} - .5, $self->{h} * .5;
2280 glEnd;
2281 }
2282}
2283
2284#############################################################################
2285
1882package CFClient::UI::Entry; 2286package DC::UI::Entry;
1883 2287
1884our @ISA = CFClient::UI::EntryBase::; 2288our @ISA = DC::UI::EntryBase::;
1885 2289
1886use CFClient::OpenGL; 2290use DC::OpenGL;
1887 2291
1888sub invoke_key_down { 2292sub invoke_key_down {
1889 my ($self, $ev) = @_; 2293 my ($self, $ev) = @_;
1890 2294
1891 my $sym = $ev->{sym}; 2295 my $sym = $ev->{sym};
1892 2296
1893 if ($sym == 13) { 2297 if ($ev->{uni} == 0x0d || $sym == 13) {
1894 unshift @{$self->{history}}, 2298 unshift @{$self->{history}},
1895 my $txt = $self->get_text; 2299 my $txt = $self->get_text;
1896 2300
1897 $self->{history_pointer} = -1; 2301 $self->{history_pointer} = -1;
1898 $self->{history_saveback} = ''; 2302 $self->{history_saveback} = '';
1899 $self->emit (activate => $txt); 2303 $self->emit (activate => $txt);
1900 $self->update; 2304 $self->update;
1901 2305
1902 } elsif ($sym == CFClient::SDLK_UP) { 2306 } elsif ($sym == DC::SDLK_UP) {
1903 if ($self->{history_pointer} < 0) { 2307 if ($self->{history_pointer} < 0) {
1904 $self->{history_saveback} = $self->get_text; 2308 $self->{history_saveback} = $self->get_text;
1905 } 2309 }
1906 if (@{$self->{history} || []} > 0) { 2310 if (@{$self->{history} || []} > 0) {
1907 $self->{history_pointer}++; 2311 $self->{history_pointer}++;
1909 $self->{history_pointer} = @{$self->{history} || []} - 1; 2313 $self->{history_pointer} = @{$self->{history} || []} - 1;
1910 } 2314 }
1911 $self->set_text ($self->{history}->[$self->{history_pointer}]); 2315 $self->set_text ($self->{history}->[$self->{history_pointer}]);
1912 } 2316 }
1913 2317
1914 } elsif ($sym == CFClient::SDLK_DOWN) { 2318 } elsif ($sym == DC::SDLK_DOWN) {
1915 $self->{history_pointer}--; 2319 $self->{history_pointer}--;
1916 $self->{history_pointer} = -1 if $self->{history_pointer} < 0; 2320 $self->{history_pointer} = -1 if $self->{history_pointer} < 0;
1917 2321
1918 if ($self->{history_pointer} >= 0) { 2322 if ($self->{history_pointer} >= 0) {
1919 $self->set_text ($self->{history}->[$self->{history_pointer}]); 2323 $self->set_text ($self->{history}->[$self->{history_pointer}]);
1928 1 2332 1
1929} 2333}
1930 2334
1931############################################################################# 2335#############################################################################
1932 2336
1933package CFClient::UI::Button; 2337package DC::UI::TextEdit;
1934 2338
1935our @ISA = CFClient::UI::Label::; 2339our @ISA = DC::UI::EntryBase::;
1936 2340
1937use CFClient::OpenGL; 2341use DC::OpenGL;
1938
1939my @tex =
1940 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1941 qw(b1_button_active.png);
1942 2342
1943sub new { 2343sub new {
1944 my $class = shift; 2344 my $class = shift;
1945 2345
1946 $class->SUPER::new ( 2346 $class->SUPER::new (
1947 padding_x => 4,
1948 padding_y => 4, 2347 padding_y => 4,
1949 fg => [1, 1, 1], 2348
1950 active_fg => [0, 0, 1], 2349 @_
2350 )
2351}
2352
2353sub move_cursor_ver {
2354 my ($self, $dy) = @_;
2355
2356 my ($line, $x) = $self->{layout}->index_to_line_x ($self->{cursor});
2357 warn "cursor $self->{cursor} => $x $line\n";#d#
2358
2359 $line += $dy;
2360
2361 if (defined (my $index = $self->{layout}->line_x_to_index ($line, $x))) {
2362 warn "index $x $line => $index\n";#d#
2363 $self->{cursor} = $index;
2364 delete $self->{cur_h};
2365 $self->update;
2366 return;
2367 }
2368}
2369
2370sub invoke_key_down {
2371 my ($self, $ev) = @_;
2372
2373 my $sym = $ev->{sym};
2374
2375 if ($sym == DC::SDLK_UP) {
2376 $self->move_cursor_ver (-1);
2377 } elsif ($sym == DC::SDLK_DOWN) {
2378 $self->move_cursor_ver (+1);
2379 } else {
2380 return $self->SUPER::invoke_key_down ($ev)
2381 }
2382
2383 1
2384}
2385
2386#############################################################################
2387
2388package DC::UI::ButtonBin;
2389
2390our @ISA = DC::UI::Bin::;
2391
2392use DC::OpenGL;
2393
2394my @tex =
2395 map { new_from_file DC::Texture DC::find_rcfile $_, mipmap => 1 }
2396 qw(b1_button_inactive.png b1_button_active.png);
2397
2398sub new {
2399 my $class = shift;
2400
2401 $class->SUPER::new (
1951 can_hover => 1, 2402 can_hover => 1,
1952 align => 0, 2403 align => 0,
1953 valign => 0, 2404 valign => 0,
1954 can_events => 1, 2405 can_events => 1,
1955 @_ 2406 @_
1967} 2418}
1968 2419
1969sub _draw { 2420sub _draw {
1970 my ($self) = @_; 2421 my ($self) = @_;
1971 2422
1972 local $self->{fg} = $GRAB == $self ? $self->{active_fg} : $self->{fg};
1973
1974 glEnable GL_TEXTURE_2D; 2423 glEnable GL_TEXTURE_2D;
1975 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 2424 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1976 glColor 0, 0, 0, 1; 2425 glColor 0, 0, 0, 1;
1977 2426
2427 my $tex = $tex[$GRAB == $self];
1978 $tex[0]->draw_quad_alpha (0, 0, $self->{w}, $self->{h}); 2428 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
1979 2429
1980 glDisable GL_TEXTURE_2D; 2430 glDisable GL_TEXTURE_2D;
1981 2431
1982 $self->SUPER::_draw; 2432 $self->SUPER::_draw;
1983} 2433}
1984 2434
1985############################################################################# 2435#############################################################################
1986 2436
2437package DC::UI::Button;
2438
2439our @ISA = DC::UI::Label::;
2440
2441use DC::OpenGL;
2442
2443my @tex =
2444 map { new_from_file DC::Texture DC::find_rcfile $_, mipmap => 1 }
2445 qw(b1_button_inactive.png b1_button_active.png);
2446
2447sub new {
2448 my $class = shift;
2449
2450 $class->SUPER::new (
2451 padding_x => 8,
2452 padding_y => 4,
2453 fg => [1.0, 1.0, 1.0],
2454 active_fg => [0.8, 0.8, 0.8],
2455 can_hover => 1,
2456 align => 0,
2457 valign => 0,
2458 can_events => 1,
2459 @_
2460 )
2461}
2462
2463sub invoke_button_up {
2464 my ($self, $ev, $x, $y) = @_;
2465
2466 $self->emit ("activate")
2467 if $x >= 0 && $x < $self->{w}
2468 && $y >= 0 && $y < $self->{h};
2469
2470 1
2471}
2472
2473sub _draw {
2474 my ($self) = @_;
2475
2476 local $self->{fg} = $GRAB == $self ? $self->{active_fg} : $self->{fg};
2477
2478 glEnable GL_TEXTURE_2D;
2479 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2480 glColor 0, 0, 0, 1;
2481
2482 my $tex = $tex[$GRAB == $self];
2483 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2484
2485 glDisable GL_TEXTURE_2D;
2486
2487 $self->SUPER::_draw;
2488}
2489
2490#############################################################################
2491
2492package DC::UI::CheckBox;
2493
2494our @ISA = DC::UI::DrawBG::;
2495
2496my @tex =
2497 map { new_from_file DC::Texture DC::find_rcfile $_, mipmap => 1 }
2498 qw(c1_checkbox_bg.png c1_checkbox_active.png);
2499
2500use DC::OpenGL;
2501
2502sub new {
2503 my $class = shift;
2504
2505 $class->SUPER::new (
2506 padding_x => 2,
2507 padding_y => 2,
2508 fg => [1, 1, 1],
2509 active_fg => [1, 1, 0],
2510 bg => [0, 0, 0, 0.2],
2511 active_bg => [1, 1, 1, 0.5],
2512 state => 0,
2513 can_hover => 1,
2514 @_
2515 )
2516}
2517
2518sub size_request {
2519 my ($self) = @_;
2520
2521 (6) x 2
2522}
2523
2524sub toggle {
2525 my ($self) = @_;
2526
2527 $self->{state} = !$self->{state};
2528 $self->emit (changed => $self->{state});
2529 $self->update;
2530}
2531
2532sub invoke_button_down {
2533 my ($self, $ev, $x, $y) = @_;
2534
2535 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
2536 && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
2537 $self->toggle;
2538 } else {
2539 return 0
2540 }
2541
2542 1
2543}
2544
2545sub _draw {
2546 my ($self) = @_;
2547
2548 $self->SUPER::_draw;
2549
2550 glTranslate $self->{padding_x}, $self->{padding_y}, 0;
2551
2552 my ($w, $h) = @$self{qw(w h)};
2553
2554 my $s = List::Util::min $w - $self->{padding_x} * 2, $h - $self->{padding_y} * 2;
2555
2556 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };
2557
2558 my $tex = $self->{state} ? $tex[1] : $tex[0];
2559
2560 glEnable GL_TEXTURE_2D;
2561 $tex->draw_quad_alpha (0, 0, $s, $s);
2562 glDisable GL_TEXTURE_2D;
2563}
2564
2565#############################################################################
2566
2567package DC::UI::Image;
2568
2569our @ISA = DC::UI::Base::;
2570
2571use DC::OpenGL;
2572
2573our %texture_cache;
2574
2575sub new {
2576 my $class = shift;
2577
2578 my $self = $class->SUPER::new (
2579 can_events => 0,
2580 scale => 1,
2581 @_,
2582 );
2583
2584 $self->{path} || $self->{tex}
2585 or Carp::croak "'path' or 'tex' attributes required";
2586
2587 $self->{tex} ||= $texture_cache{$self->{path}} ||=
2588 new_from_file DC::Texture DC::find_rcfile $self->{path}, mipmap => 1;
2589
2590 DC::weaken $texture_cache{$self->{path}};
2591
2592 $self->{aspect} ||= $self->{tex}{w} / $self->{tex}{h};
2593
2594 $self
2595}
2596
2597sub STORABLE_freeze {
2598 my ($self, $cloning) = @_;
2599
2600 $self->{path}
2601 or die "cannot serialise DC::UI::Image on non-loadable images\n";
2602
2603 $self->{path}
2604}
2605
2606sub STORABLE_attach {
2607 my ($self, $cloning, $path) = @_;
2608
2609 $self->new (path => $path)
2610}
2611
2612sub size_request {
2613 my ($self) = @_;
2614
2615 (int $self->{tex}{w} * $self->{scale}, int $self->{tex}{h} * $self->{scale})
2616}
2617
2618sub _draw {
2619 my ($self) = @_;
2620
2621 my $tex = $self->{tex};
2622
2623 my ($w, $h) = ($self->{w}, $self->{h});
2624
2625 if ($self->{rot90}) {
2626 glRotate 90, 0, 0, 1;
2627 glTranslate 0, -$self->{w}, 0;
2628
2629 ($w, $h) = ($h, $w);
2630 }
2631
2632 glEnable GL_TEXTURE_2D;
2633 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2634
2635 $tex->draw_quad_alpha (0, 0, $w, $h);
2636
2637 glDisable GL_TEXTURE_2D;
2638}
2639
2640#############################################################################
2641
1987package CFClient::UI::ImageButton; 2642package DC::UI::ImageButton;
1988 2643
1989our @ISA = CFClient::UI::Image::; 2644our @ISA = DC::UI::Image::;
1990 2645
1991use CFClient::OpenGL; 2646use DC::OpenGL;
1992 2647
1993my %textures; 2648my %textures;
1994 2649
1995sub new { 2650sub new {
1996 my $class = shift; 2651 my $class = shift;
2006 can_events => 1, 2661 can_events => 1,
2007 @_ 2662 @_
2008 ); 2663 );
2009} 2664}
2010 2665
2666sub invoke_button_down {
2667 my ($self, $ev, $x, $y) = @_;
2668
2669 1
2670}
2671
2011sub invoke_button_up { 2672sub invoke_button_up {
2012 my ($self, $ev, $x, $y) = @_; 2673 my ($self, $ev, $x, $y) = @_;
2013 2674
2014 $self->emit ("activate") 2675 $self->emit ("activate")
2015 if $x >= 0 && $x < $self->{w} 2676 if $x >= 0 && $x < $self->{w}
2018 1 2679 1
2019} 2680}
2020 2681
2021############################################################################# 2682#############################################################################
2022 2683
2023package CFClient::UI::CheckBox; 2684package DC::UI::VGauge;
2024 2685
2025our @ISA = CFClient::UI::DrawBG::;
2026
2027my @tex =
2028 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
2029 qw(c1_checkbox_bg.png c1_checkbox_active.png);
2030
2031use CFClient::OpenGL;
2032
2033sub new {
2034 my $class = shift;
2035
2036 $class->SUPER::new (
2037 padding_x => 2,
2038 padding_y => 2,
2039 fg => [1, 1, 1],
2040 active_fg => [1, 1, 0],
2041 bg => [0, 0, 0, 0.2],
2042 active_bg => [1, 1, 1, 0.5],
2043 state => 0,
2044 can_hover => 1,
2045 @_
2046 )
2047}
2048
2049sub size_request {
2050 my ($self) = @_;
2051
2052 (6) x 2
2053}
2054
2055sub invoke_button_down {
2056 my ($self, $ev, $x, $y) = @_;
2057
2058 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
2059 && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
2060 $self->{state} = !$self->{state};
2061 $self->emit (changed => $self->{state});
2062 } else {
2063 return 0
2064 }
2065
2066 1
2067}
2068
2069sub _draw {
2070 my ($self) = @_;
2071
2072 $self->SUPER::_draw;
2073
2074 glTranslate $self->{padding_x} + 0.375, $self->{padding_y} + 0.375, 0;
2075
2076 my ($w, $h) = @$self{qw(w h)};
2077
2078 my $s = List::Util::min $w - $self->{padding_x} * 2, $h - $self->{padding_y} * 2;
2079
2080 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };
2081
2082 my $tex = $self->{state} ? $tex[1] : $tex[0];
2083
2084 glEnable GL_TEXTURE_2D;
2085 $tex->draw_quad_alpha (0, 0, $s, $s);
2086 glDisable GL_TEXTURE_2D;
2087}
2088
2089#############################################################################
2090
2091package CFClient::UI::Image;
2092
2093our @ISA = CFClient::UI::Base::; 2686our @ISA = DC::UI::Base::;
2094
2095use CFClient::OpenGL;
2096use Carp qw/confess/;
2097
2098our %loaded_images;
2099
2100sub new {
2101 my $class = shift;
2102
2103 my $self = $class->SUPER::new (can_events => 0, @_);
2104
2105 $self->{image} or confess "Image has 'image' not set. This is a fatal error!";
2106
2107 $loaded_images{$self->{image}} ||=
2108 new_from_file CFClient::Texture CFClient::find_rcfile $self->{image}, mipmap => 1;
2109
2110 my $tex = $self->{tex} = $loaded_images{$self->{image}};
2111
2112 Scalar::Util::weaken $loaded_images{$self->{image}};
2113
2114 $self->{aspect} = $tex->{w} / $tex->{h};
2115
2116 $self
2117}
2118
2119sub size_request {
2120 my ($self) = @_;
2121
2122 ($self->{tex}->{w}, $self->{tex}->{h})
2123}
2124
2125sub _draw {
2126 my ($self) = @_;
2127
2128 my $tex = $self->{tex};
2129
2130 my ($w, $h) = ($self->{w}, $self->{h});
2131
2132 if ($self->{rot90}) {
2133 glRotate 90, 0, 0, 1;
2134 glTranslate 0, -$self->{w}, 0;
2135
2136 ($w, $h) = ($h, $w);
2137 }
2138
2139 glEnable GL_TEXTURE_2D;
2140 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2141
2142 $tex->draw_quad_alpha (0, 0, $w, $h);
2143
2144 glDisable GL_TEXTURE_2D;
2145}
2146
2147#############################################################################
2148
2149package CFClient::UI::VGauge;
2150
2151our @ISA = CFClient::UI::Base::;
2152 2687
2153use List::Util qw(min max); 2688use List::Util qw(min max);
2154 2689
2155use CFClient::OpenGL; 2690use DC::OpenGL;
2156 2691
2157my %tex = ( 2692my %tex = (
2158 food => [ 2693 food => [
2159 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 2694 map { new_from_file DC::Texture DC::find_rcfile $_, mipmap => 1 }
2160 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/ 2695 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/
2161 ], 2696 ],
2162 grace => [ 2697 grace => [
2163 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 2698 map { new_from_file DC::Texture DC::find_rcfile $_, mipmap => 1 }
2164 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/ 2699 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/
2165 ], 2700 ],
2166 hp => [ 2701 hp => [
2167 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 2702 map { new_from_file DC::Texture DC::find_rcfile $_, mipmap => 1 }
2168 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/ 2703 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/
2169 ], 2704 ],
2170 mana => [ 2705 mana => [
2171 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 } 2706 map { new_from_file DC::Texture DC::find_rcfile $_, mipmap => 1 }
2172 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/ 2707 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/
2173 ], 2708 ],
2174); 2709);
2175 2710
2176# eg. VGauge->new (gauge => 'food'), default gauge: food 2711# eg. VGauge->new (gauge => 'food'), default gauge: food
2236 my $ycut1 = max 0, min 1, $ycut; 2771 my $ycut1 = max 0, min 1, $ycut;
2237 my $ycut2 = max 0, min 1, $ycut - 1; 2772 my $ycut2 = max 0, min 1, $ycut - 1;
2238 2773
2239 my $h1 = $self->{h} * (1 - $ycut1); 2774 my $h1 = $self->{h} * (1 - $ycut1);
2240 my $h2 = $self->{h} * (1 - $ycut2); 2775 my $h2 = $self->{h} * (1 - $ycut2);
2776 my $h3 = $self->{h};
2777
2778 $_ = $_ * (284-4)/288 + 4/288 for ($h1, $h2, $h3);
2241 2779
2242 glEnable GL_BLEND; 2780 glEnable GL_BLEND;
2243 glBlendFuncSeparate GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 2781 glBlendFuncSeparate GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
2244 GL_ONE, GL_ONE_MINUS_SRC_ALPHA; 2782 GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2245 glEnable GL_TEXTURE_2D; 2783 glEnable GL_TEXTURE_2D;
2264 2802
2265 if ($t3) { 2803 if ($t3) {
2266 glBindTexture GL_TEXTURE_2D, $t3->{name}; 2804 glBindTexture GL_TEXTURE_2D, $t3->{name};
2267 glBegin GL_QUADS; 2805 glBegin GL_QUADS;
2268 glTexCoord 0 , $t3->{t} * (1 - $ycut2); glVertex 0 , $h2; 2806 glTexCoord 0 , $t3->{t} * (1 - $ycut2); glVertex 0 , $h2;
2269 glTexCoord 0 , $t3->{t}; glVertex 0 , $self->{h}; 2807 glTexCoord 0 , $t3->{t}; glVertex 0 , $h3;
2270 glTexCoord $t3->{s}, $t3->{t}; glVertex $w, $self->{h}; 2808 glTexCoord $t3->{s}, $t3->{t}; glVertex $w, $h3;
2271 glTexCoord $t3->{s}, $t3->{t} * (1 - $ycut2); glVertex $w, $h2; 2809 glTexCoord $t3->{s}, $t3->{t} * (1 - $ycut2); glVertex $w, $h2;
2272 glEnd; 2810 glEnd;
2273 } 2811 }
2274 2812
2275 glDisable GL_BLEND; 2813 glDisable GL_BLEND;
2276 glDisable GL_TEXTURE_2D; 2814 glDisable GL_TEXTURE_2D;
2277} 2815}
2278 2816
2279############################################################################# 2817#############################################################################
2280 2818
2819package DC::UI::Progress;
2820
2821our @ISA = DC::UI::Label::;
2822
2823use DC::OpenGL;
2824
2825sub new {
2826 my ($class, %arg) = @_;
2827
2828 my $self = $class->SUPER::new (
2829 fg => [1, 1, 1],
2830 bg => [0, 0, 1, 0.2],
2831 bar => [0.7, 0.5, 0.1, 0.8],
2832 outline => [0.4, 0.3, 0],
2833 fontsize => 0.9,
2834 valign => 0,
2835 align => 0,
2836 can_events => 1,
2837 ellipsise => 1,
2838 label => "%d%%",
2839 %arg,
2840 );
2841
2842 $self->set_value ($arg{value} || -1);
2843
2844 $self
2845}
2846
2847sub set_label {
2848 my ($self, $label) = @_;
2849
2850 return if $self->{label} eq $label;
2851 $self->{label} = $label;
2852
2853 $self->DC::UI::Progress::set_value (0 + delete $self->{value});
2854}
2855
2856sub set_value {
2857 my ($self, $value) = @_;
2858
2859 if ($self->{value} ne $value) {
2860 $self->{value} = $value;
2861
2862 if ($value < 0) {
2863 $self->set_text ("-");
2864 } else {
2865 $self->set_text (sprintf $self->{label}, $value * 100);
2866 }
2867
2868 $self->update;
2869 }
2870}
2871
2872sub _draw {
2873 my ($self) = @_;
2874
2875 glEnable GL_BLEND;
2876 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2877
2878 if ($self->{value} >= 0) {
2879 my $s = int 2 + ($self->{w} - 4) * $self->{value};
2880
2881 glColor_premultiply @{$self->{bar}};
2882 glRect 2, 2, $s, $self->{h} - 2;
2883 glColor_premultiply @{$self->{bg}};
2884 glRect $s, 2, $self->{w} - 2, $self->{h} - 2;
2885 }
2886
2887 glColor_premultiply @{$self->{outline}};
2888 glRect_lineloop 1.5, 1.5, $self->{w} - 1.5, $self->{h} - 1.5;
2889
2890 glDisable GL_BLEND;
2891
2892 {
2893 local $self->{bg}; # do not draw background
2894 $self->SUPER::_draw;
2895 }
2896}
2897
2898#############################################################################
2899
2900package DC::UI::ExperienceProgress;
2901
2902our @ISA = DC::UI::Progress::;
2903
2904sub new {
2905 my ($class, %arg) = @_;
2906
2907 my $self = $class->SUPER::new (
2908 tooltip => sub {
2909 my ($self) = @_;
2910
2911 sprintf "level %d\n%s points\n%s next level\n%s to go",
2912 $self->{lvl},
2913 ::formsep ($self->{exp}),
2914 ::formsep ($self->{nxt}),
2915 ::formsep ($self->{nxt} - $self->{exp}),
2916 },
2917 %arg
2918 );
2919
2920 $::CONN->{on_exp_update}{$self+0} = sub { $self->set_value ($self->{value}) }
2921 if $::CONN;
2922
2923 $self
2924}
2925
2926sub DESTROY {
2927 my ($self) = @_;
2928
2929 delete $::CONN->{on_exp_update}{$self+0}
2930 if $::CONN;
2931
2932 $self->SUPER::DESTROY;
2933}
2934
2935sub set_value {
2936 my ($self, $lvl, $exp) = @_;
2937
2938 $self->{lvl} = $lvl;
2939 $self->{exp} = $exp;
2940
2941 my $v = -1;
2942
2943 if ($::CONN && (my $table = $::CONN->{exp_table})) {
2944 my $l0 = $table->[$lvl - 1];
2945 my $l1 = $table->[$lvl];
2946
2947 $self->{nxt} = $l1;
2948
2949 $v = ($exp - $l0) / ($l1 - $l0);
2950 }
2951
2952 $self->SUPER::set_value ($v);
2953}
2954
2955#############################################################################
2956
2281package CFClient::UI::Gauge; 2957package DC::UI::Gauge;
2282 2958
2283our @ISA = CFClient::UI::VBox::; 2959our @ISA = DC::UI::VBox::;
2284 2960
2285sub new { 2961sub new {
2286 my ($class, %arg) = @_; 2962 my ($class, %arg) = @_;
2287 2963
2288 my $self = $class->SUPER::new ( 2964 my $self = $class->SUPER::new (
2290 can_hover => 1, 2966 can_hover => 1,
2291 can_events => 1, 2967 can_events => 1,
2292 %arg, 2968 %arg,
2293 ); 2969 );
2294 2970
2295 $self->add ($self->{value} = new CFClient::UI::Label valign => +1, align => 0, template => "999"); 2971 $self->add ($self->{value} = new DC::UI::Label valign => +1, align => 0, template => "999");
2296 $self->add ($self->{gauge} = new CFClient::UI::VGauge type => $self->{type}, expand => 1, can_hover => 1); 2972 $self->add ($self->{gauge} = new DC::UI::VGauge type => $self->{type}, expand => 1, can_hover => 1);
2297 $self->add ($self->{max} = new CFClient::UI::Label valign => -1, align => 0, template => "999"); 2973 $self->add ($self->{max} = new DC::UI::Label valign => -1, align => 0, template => "999");
2298 2974
2299 $self 2975 $self
2300} 2976}
2301 2977
2302sub set_fontsize { 2978sub set_fontsize {
2323 $self->{value}->set_text ($val); 2999 $self->{value}->set_text ($val);
2324} 3000}
2325 3001
2326############################################################################# 3002#############################################################################
2327 3003
2328package CFClient::UI::Slider; 3004package DC::UI::Slider;
2329 3005
2330use strict; 3006use strict;
2331 3007
2332use CFClient::OpenGL; 3008use DC::OpenGL;
2333 3009
2334our @ISA = CFClient::UI::DrawBG::; 3010our @ISA = DC::UI::DrawBG::;
2335 3011
2336my @tex = 3012my @tex =
2337 map { new_from_file CFClient::Texture CFClient::find_rcfile $_ } 3013 map { new_from_file DC::Texture DC::find_rcfile $_ }
2338 qw(s1_slider.png s1_slider_bg.png); 3014 qw(s1_slider.png s1_slider_bg.png);
2339 3015
2340sub new { 3016sub new {
2341 my $class = shift; 3017 my $class = shift;
2342 3018
2410 3086
2411 $self->SUPER::invoke_button_down ($ev, $x, $y); 3087 $self->SUPER::invoke_button_down ($ev, $x, $y);
2412 3088
2413 $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x]; 3089 $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x];
2414 3090
2415 $self->invoke_mouse_motion ($ev, $x, $y) 3091 $self->invoke_mouse_motion ($ev, $x, $y);
3092
3093 1
2416} 3094}
2417 3095
2418sub invoke_mouse_motion { 3096sub invoke_mouse_motion {
2419 my ($self, $ev, $x, $y) = @_; 3097 my ($self, $ev, $x, $y) = @_;
2420 3098
2427 3105
2428 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo)); 3106 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo));
2429 } else { 3107 } else {
2430 return 0; 3108 return 0;
2431 } 3109 }
3110
3111 1
3112}
3113
3114sub invoke_mouse_wheel {
3115 my ($self, $ev) = @_;
3116
3117 my $delta = $self->{vertical} ? $ev->{dy} : $ev->{dx};
3118
3119 my $pagepart = $ev->{mod} & DC::KMOD_SHIFT ? 1 : 0.2;
3120
3121 $self->set_value ($self->{range}[0] + $delta * $self->{range}[3] * $pagepart);
2432 3122
2433 1 3123 1
2434} 3124}
2435 3125
2436sub update { 3126sub update {
2487 glDisable GL_TEXTURE_2D; 3177 glDisable GL_TEXTURE_2D;
2488} 3178}
2489 3179
2490############################################################################# 3180#############################################################################
2491 3181
2492package CFClient::UI::ValSlider; 3182package DC::UI::ValSlider;
2493 3183
2494our @ISA = CFClient::UI::HBox::; 3184our @ISA = DC::UI::HBox::;
2495 3185
2496sub new { 3186sub new {
2497 my ($class, %arg) = @_; 3187 my ($class, %arg) = @_;
2498 3188
2499 my $range = delete $arg{range}; 3189 my $range = delete $arg{range};
2500 3190
2501 my $self = $class->SUPER::new ( 3191 my $self = $class->SUPER::new (
2502 slider => (new CFClient::UI::Slider expand => 1, range => $range), 3192 slider => (new DC::UI::Slider expand => 1, range => $range),
2503 entry => (new CFClient::UI::Label text => "", template => delete $arg{template}), 3193 entry => (new DC::UI::Label text => "", template => delete $arg{template}),
2504 to_value => sub { shift }, 3194 to_value => sub { shift },
2505 from_value => sub { shift }, 3195 from_value => sub { shift },
2506 %arg, 3196 %arg,
2507 ); 3197 );
2508 3198
2528sub set_range { shift->{slider}->set_range (@_) } 3218sub set_range { shift->{slider}->set_range (@_) }
2529sub set_value { shift->{slider}->set_value (@_) } 3219sub set_value { shift->{slider}->set_value (@_) }
2530 3220
2531############################################################################# 3221#############################################################################
2532 3222
2533package CFClient::UI::TextScroller; 3223package DC::UI::TextScroller;
2534 3224
2535our @ISA = CFClient::UI::HBox::; 3225our @ISA = DC::UI::HBox::;
2536 3226
2537use CFClient::OpenGL; 3227use DC::OpenGL;
2538 3228
2539sub new { 3229sub new {
2540 my $class = shift; 3230 my $class = shift;
2541 3231
2542 my $self = $class->SUPER::new ( 3232 my $self = $class->SUPER::new (
2543 fontsize => 1, 3233 fontsize => 1,
2544 can_events => 0, 3234 can_events => 1,
2545 indent => 0, 3235 indent => 0,
2546 #font => default_font 3236 #font => default_font
2547 @_, 3237 @_,
2548 3238
2549 layout => (new CFClient::Layout 1), 3239 layout => (new DC::Layout),
2550 par => [], 3240 par => [],
3241 max_par => 0,
2551 height => 0, 3242 height => 0,
2552 children => [ 3243 children => [
2553 (new CFClient::UI::Empty expand => 1), 3244 (new DC::UI::Empty expand => 1),
2554 (new CFClient::UI::Slider vertical => 1), 3245 (new DC::UI::Slider vertical => 1),
2555 ], 3246 ],
2556 ); 3247 );
2557 3248
2558 $self->{children}[1]->connect (changed => sub { $self->update }); 3249 $self->{children}[1]->connect (changed => sub { $self->update });
2559 3250
2565 3256
2566 $self->{fontsize} = $fontsize; 3257 $self->{fontsize} = $fontsize;
2567 $self->reflow; 3258 $self->reflow;
2568} 3259}
2569 3260
3261sub size_request {
3262 my ($self) = @_;
3263
3264 my ($empty, $slider) = $self->visible_children;
3265
3266 local $self->{children} = [$empty, $slider];
3267 $self->SUPER::size_request
3268}
3269
2570sub invoke_size_allocate { 3270sub invoke_size_allocate {
2571 my ($self, $w, $h) = @_; 3271 my ($self, $w, $h) = @_;
2572 3272
3273 my ($empty, $slider, @other) = @{ $self->{children} };
3274 $_->configure (@$_{qw(x y req_w req_h)}) for @other;
3275
2573 $self->{layout}->set_font ($self->{font}) if $self->{font}; 3276 $self->{layout}->set_font ($self->{font}) if $self->{font};
2574 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 3277 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
2575 $self->{layout}->set_width ($self->{children}[0]{w}); 3278 $self->{layout}->set_width ($empty->{w});
2576 $self->{layout}->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent}); 3279 $self->{layout}->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent});
2577 3280
2578 $self->reflow; 3281 $self->reflow;
2579 3282
3283 local $self->{children} = [$empty, $slider];
2580 $self->SUPER::invoke_size_allocate ($w, $h) 3284 $self->SUPER::invoke_size_allocate ($w, $h)
2581} 3285}
2582 3286
2583sub text_size { 3287sub invoke_mouse_wheel {
2584 my ($self, $text, $indent) = @_; 3288 my ($self, $ev) = @_;
3289
3290 return 0 unless $ev->{dy}; # only vertical movements
3291
3292 $self->{children}[1]->emit (mouse_wheel => $ev);
3293
3294 1
3295}
3296
3297sub get_layout {
3298 my ($self, $para) = @_;
2585 3299
2586 my $layout = $self->{layout}; 3300 my $layout = $self->{layout};
2587 3301
3302 $layout->set_font ($self->{font}) if $self->{font};
3303 $layout->set_foreground (@{$para->{fg}});
2588 $layout->set_height ($self->{fontsize} * $::FONTSIZE); 3304 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2589 $layout->set_width ($self->{children}[0]{w} - $indent); 3305 $layout->set_width ($self->{children}[0]{w} - $para->{indent});
2590 $layout->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent}); 3306 $layout->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent});
2591 $layout->set_markup ($text); 3307 $layout->set_markup ($para->{markup});
3308
3309 $layout->set_shapes (
3310 map
3311 +(0, $_->baseline_shift +$_->{padding_y} - $_->{h}, $_->{w}, $_->{h}),
3312 @{$para->{widget}}
2592 3313 );
3314
2593 $layout->size 3315 $layout
2594} 3316}
2595 3317
2596sub reflow { 3318sub reflow {
2597 my ($self) = @_; 3319 my ($self) = @_;
2598 3320
2605 3327
2606 # todo: base offset on lines or so, not on pixels 3328 # todo: base offset on lines or so, not on pixels
2607 $self->{children}[1]->set_value ($offset); 3329 $self->{children}[1]->set_value ($offset);
2608} 3330}
2609 3331
3332sub current_paragraph {
3333 my ($self) = @_;
3334
3335 $self->{top_paragraph} - 1
3336}
3337
3338sub scroll_to {
3339 my ($self, $para) = @_;
3340
3341 $para = List::Util::max 0, List::Util::min $#{$self->{par}}, $para;
3342
3343 $self->{scroll_to} = $para;
3344 $self->update;
3345}
3346
2610sub clear { 3347sub clear {
2611 my ($self) = @_; 3348 my ($self) = @_;
3349
3350 my (undef, undef, @other) = @{ $self->{children} };
3351 $self->remove ($_) for @other;
2612 3352
2613 $self->{par} = []; 3353 $self->{par} = [];
2614 $self->{height} = 0; 3354 $self->{height} = 0;
2615 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]); 3355 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]);
2616} 3356}
2617 3357
2618sub add_paragraph { 3358sub add_paragraph {
2619 my ($self, $color, $text, $indent) = @_; 3359 my $self = shift;
2620 3360
2621 for my $line (split /\n/, $text) { 3361 for my $para (@_) {
2622 my ($w, $h) = $self->text_size ($line); 3362 $para = {
3363 fg => [1, 1, 1, 1],
3364 indent => 0,
3365 markup => "",
3366 widget => [],
3367 ref $para ? %$para : (markup => $para),
3368 w => 1e10,
3369 wrapped => 1,
3370 };
3371
3372 $self->add (@{ $para->{widget} }) if @{ $para->{widget} };
3373 push @{$self->{par}}, $para;
3374 }
3375
3376 if (my $max = $self->{max_par}) {
3377 shift @{$self->{par}} while @{$self->{par}} > $max;
3378 }
3379
3380 $self->{need_reflow}++;
3381 $self->update;
3382}
3383
3384sub scroll_to_bottom {
3385 my ($self) = @_;
3386
3387 $self->{scroll_to} = $#{$self->{par}};
3388 $self->update;
3389}
3390
3391sub force_uptodate {
3392 my ($self) = @_;
3393
3394 if (delete $self->{need_reflow}) {
3395 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
3396
3397 my $height = 0;
3398
3399 for my $para (@{$self->{par}}) {
3400 if ($para->{w} != $W && ($para->{wrapped} || $para->{w} > $W)) {
3401 my $layout = $self->get_layout ($para);
3402 my ($w, $h) = $layout->size;
3403
3404 $para->{w} = $w + $para->{indent};
3405 $para->{h} = $h;
3406 $para->{wrapped} = $layout->has_wrapped;
3407 }
3408
3409 $para->{y} = $height;
3410 $height += $para->{h};
3411 }
3412
2623 $self->{height} += $h; 3413 $self->{height} = $height;
2624 push @{$self->{par}}, [$w + $indent, $h, $color, $indent, $line]; 3414 $self->{children}[1]->set_range ([$self->{children}[1]{range}[0], 0, $height, $H, 1]);
2625 }
2626 3415
2627 $self->{children}[1]->set_range ([$self->{height}, 0, $self->{height}, $self->{h}, 1]); 3416 delete $self->{texture};
3417 }
3418
3419 if (my $paridx = delete $self->{scroll_to}) {
3420 $self->{children}[1]->set_value ($self->{par}[$paridx]{y});
3421 }
2628} 3422}
2629 3423
2630sub update { 3424sub update {
2631 my ($self) = @_; 3425 my ($self) = @_;
2632 3426
2635 return unless $self->{h} > 0; 3429 return unless $self->{h} > 0;
2636 3430
2637 delete $self->{texture}; 3431 delete $self->{texture};
2638 3432
2639 $ROOT->on_post_alloc ($self => sub { 3433 $ROOT->on_post_alloc ($self => sub {
3434 $self->force_uptodate;
3435
2640 my ($W, $H) = @{$self->{children}[0]}{qw(w h)}; 3436 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
2641 3437
2642 if (delete $self->{need_reflow}) {
2643 my $height = 0;
2644
2645 my $layout = $self->{layout};
2646
2647 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2648
2649 for (@{$self->{par}}) {
2650 if (1 || $_->[0] >= $W) { # TODO: works,but needs reconfigure etc. support
2651 $layout->set_width ($W - $_->[3]);
2652 $layout->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent});
2653 $layout->set_markup ($_->[4]);
2654 my ($w, $h) = $layout->size;
2655 $_->[0] = $w + $_->[3];
2656 $_->[1] = $h;
2657 }
2658
2659 $height += $_->[1];
2660 }
2661
2662 $self->{height} = $height;
2663
2664 $self->{children}[1]->set_range ([$height, 0, $height, $H, 1]);
2665
2666 delete $self->{texture};
2667 }
2668
2669 $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub { 3438 $self->{texture} ||= new_from_opengl DC::Texture $W, $H, sub {
2670 glClearColor 0, 0, 0, 0; 3439 glClearColor 0, 0, 0, 0;
2671 glClear GL_COLOR_BUFFER_BIT; 3440 glClear GL_COLOR_BUFFER_BIT;
2672 3441
3442 package DC::UI::Base;
3443 local ($draw_x, $draw_y, $draw_w, $draw_h) =
3444 (0, 0, $self->{w}, $self->{h});
3445
3446 my $top = int $self->{children}[1]{range}[0];
3447
3448 my $paridx = 0;
3449 my $top_paragraph;
2673 my $top = int $self->{children}[1]{range}[0]; 3450 my $top = int $self->{children}[1]{range}[0];
2674 3451
2675 my $y0 = $top; 3452 my $y0 = $top;
2676 my $y1 = $top + $H; 3453 my $y1 = $top + $H;
2677 3454
2678 my $y = 0;
2679
2680 my $layout = $self->{layout};
2681
2682 $layout->set_font ($self->{font}) if $self->{font};
2683
2684 glEnable GL_BLEND;
2685 #TODO# not correct in windows where rgba is forced off
2686 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2687
2688 for my $par (@{$self->{par}}) { 3455 for my $para (@{$self->{par}}) {
2689 my $h = $par->[1]; 3456 my $h = $para->{h};
3457 my $y = $para->{y};
2690 3458
2691 if ($y0 < $y + $h && $y < $y1) { 3459 if ($y0 < $y + $h && $y < $y1) {
2692 $layout->set_foreground (@{ $par->[2] }); 3460 my $layout = $self->get_layout ($para);
2693 $layout->set_width ($W - $par->[3]);
2694 $layout->set_indent ($self->{fontsize} * $::FONTSIZE * $self->{indent});
2695 $layout->set_markup ($par->[4]);
2696 3461
2697 my ($w, $h, $data, $format, $internalformat) = $layout->render; 3462 $layout->render ($para->{indent}, $y - $y0);
3463 $layout->draw;
2698 3464
2699 glRasterPos $par->[3], $y - $y0; 3465 if (my @w = @{ $para->{widget} }) {
2700 glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data; 3466 my @s = $layout->get_shapes;
3467
3468 for (@w) {
3469 my ($dx, $dy) = splice @s, 0, 2, ();
3470
3471 $_->{x} = $dx + $para->{indent};
3472 $_->{y} = $dy + $y - $y0;
3473
3474 $_->draw;
3475 }
3476 }
2701 } 3477 }
2702 3478
2703 $y += $h; 3479 $paridx++;
3480 $top_paragraph ||= $paridx if $y >= $top;
2704 } 3481 }
2705 3482
2706 glDisable GL_BLEND; 3483 $self->{top_paragraph} = $top_paragraph;
2707 }; 3484 };
2708 }); 3485 });
3486}
3487
3488sub reconfigure {
3489 my ($self) = @_;
3490
3491 $self->SUPER::reconfigure;
3492
3493 $_->{w} = 1e10 for @{ $self->{par} };
3494 $self->reflow;
2709} 3495}
2710 3496
2711sub _draw { 3497sub _draw {
2712 my ($self) = @_; 3498 my ($self) = @_;
2713 3499
2716 glColor 0, 0, 0, 1; 3502 glColor 0, 0, 0, 1;
2717 $self->{texture}->draw_quad_alpha_premultiplied (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h}); 3503 $self->{texture}->draw_quad_alpha_premultiplied (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h});
2718 glDisable GL_TEXTURE_2D; 3504 glDisable GL_TEXTURE_2D;
2719 3505
2720 $self->{children}[1]->draw; 3506 $self->{children}[1]->draw;
2721
2722} 3507}
2723 3508
2724############################################################################# 3509#############################################################################
2725 3510
2726package CFClient::UI::Animator; 3511package DC::UI::Animator;
2727 3512
2728use CFClient::OpenGL; 3513use DC::OpenGL;
2729 3514
2730our @ISA = CFClient::UI::Bin::; 3515our @ISA = DC::UI::Bin::;
2731 3516
2732sub moveto { 3517sub moveto {
2733 my ($self, $x, $y) = @_; 3518 my ($self, $x, $y) = @_;
2734 3519
2735 $self->{moveto} = [$self->{x}, $self->{y}, $x, $y]; 3520 $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
2763 glPopMatrix; 3548 glPopMatrix;
2764} 3549}
2765 3550
2766############################################################################# 3551#############################################################################
2767 3552
2768package CFClient::UI::Flopper; 3553package DC::UI::Flopper;
2769 3554
2770our @ISA = CFClient::UI::Button::; 3555our @ISA = DC::UI::Button::;
2771 3556
2772sub new { 3557sub new {
2773 my $class = shift; 3558 my $class = shift;
2774 3559
2775 my $self = $class->SUPER::new ( 3560 my $self = $class->SUPER::new (
2787 $self->{other}->toggle_visibility; 3572 $self->{other}->toggle_visibility;
2788} 3573}
2789 3574
2790############################################################################# 3575#############################################################################
2791 3576
2792package CFClient::UI::Tooltip; 3577package DC::UI::Tooltip;
2793 3578
2794our @ISA = CFClient::UI::Bin::; 3579our @ISA = DC::UI::Bin::;
2795 3580
2796use CFClient::OpenGL; 3581use DC::OpenGL;
2797 3582
2798sub new { 3583sub new {
2799 my $class = shift; 3584 my $class = shift;
2800 3585
2801 $class->SUPER::new ( 3586 $class->SUPER::new (
2805} 3590}
2806 3591
2807sub set_tooltip_from { 3592sub set_tooltip_from {
2808 my ($self, $widget) = @_; 3593 my ($self, $widget) = @_;
2809 3594
2810 my $tooltip = $widget->{tooltip}; 3595 my $tip = $widget->{tooltip};
3596 $tip = $tip->($widget) if "CODE" eq ref $tip;
3597
3598 $tip = DC::Pod::section_label tooltip => $1
3599 if $tip =~ /^#(.*)$/;
2811 3600
2812 if ($ENV{CFPLUS_DEBUG} & 2) { 3601 if ($ENV{CFPLUS_DEBUG} & 2) {
2813 $tooltip .= "\n\n" . (ref $widget) . "\n" 3602 $tip .= "\n\n" . (ref $widget) . "\n"
2814 . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n" 3603 . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n"
2815 . "req $widget->{req_w} $widget->{req_h}\n" 3604 . "req $widget->{req_w} $widget->{req_h}\n"
2816 . "visible $widget->{visible}"; 3605 . "visible $widget->{visible}";
2817 } 3606 }
2818 3607
2819 $tooltip =~ s/^\n+//; 3608 $tip =~ s/^\n+//;
2820 $tooltip =~ s/\n+$//; 3609 $tip =~ s/\n+$//;
2821 3610
2822 $self->add (new CFClient::UI::Label 3611 $self->add (new DC::UI::Label
2823 markup => $tooltip, 3612 markup => $tip,
2824 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH, 3613 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH,
2825 fontsize => 0.8, 3614 fontsize => 0.8,
2826 fg => [0, 0, 0, 1], 3615 style => 1, # FLAG_INVERSE
2827 ellipsise => 0, 3616 ellipsise => 0,
2828 font => ($widget->{tooltip_font} || $::FONT_PROP), 3617 font => ($widget->{tooltip_font} || $::FONT_PROP),
2829 ); 3618 );
2830} 3619}
2831 3620
2850 3639
2851 $self->{root}->on_post_alloc ("move_$self" => sub { 3640 $self->{root}->on_post_alloc ("move_$self" => sub {
2852 my $widget = $self->{owner} 3641 my $widget = $self->{owner}
2853 or return; 3642 or return;
2854 3643
3644 if ($widget->{visible}) {
2855 my ($x, $y) = $widget->coord2global ($widget->{w}, 0); 3645 my ($x, $y) = $widget->coord2global ($widget->{w}, 0);
2856 3646
2857 ($x, $y) = $widget->coord2global (-$self->{w}, 0) 3647 ($x, $y) = $widget->coord2global (-$self->{w}, 0)
2858 if $x + $self->{w} > $self->{root}{w}; 3648 if $x + $self->{w} > $self->{root}{w};
2859 3649
2860 $self->move_abs ($x, $y); 3650 $self->move_abs ($x, $y);
3651 } else {
3652 $self->hide;
3653 }
2861 }); 3654 });
2862} 3655}
2863 3656
2864sub _draw { 3657sub _draw {
2865 my ($self) = @_; 3658 my ($self) = @_;
2866 3659
2867 glTranslate 0.375, 0.375;
2868
2869 my ($w, $h) = @$self{qw(w h)}; 3660 my ($w, $h) = @$self{qw(w h)};
2870 3661
2871 glColor 1, 0.8, 0.4; 3662 glColor 1, 0.8, 0.4;
2872 glBegin GL_QUADS; 3663 glRect 0, 0, $w, $h;
2873 glVertex 0 , 0;
2874 glVertex 0 , $h;
2875 glVertex $w, $h;
2876 glVertex $w, 0;
2877 glEnd;
2878 3664
2879 glColor 0, 0, 0; 3665 glColor 0, 0, 0;
2880 glBegin GL_LINE_LOOP; 3666 glRect_lineloop .5, .5, $w + .5, $h + .5;
2881 glVertex 0 , 0;
2882 glVertex 0 , $h;
2883 glVertex $w, $h;
2884 glVertex $w, 0;
2885 glEnd;
2886 3667
2887 glTranslate 2 - 0.375, 2 - 0.375; 3668 glTranslate 2, 2;
2888 3669
2889 $self->SUPER::_draw; 3670 $self->SUPER::_draw;
2890} 3671}
2891 3672
2892############################################################################# 3673#############################################################################
2893 3674
2894package CFClient::UI::Face; 3675package DC::UI::Face;
2895 3676
2896our @ISA = CFClient::UI::Base::; 3677our @ISA = DC::UI::DrawBG::;
2897 3678
2898use CFClient::OpenGL; 3679use DC::OpenGL;
2899 3680
2900sub new { 3681sub new {
2901 my $class = shift; 3682 my $class = shift;
2902 3683
2903 my $self = $class->SUPER::new ( 3684 my $self = $class->SUPER::new (
3685 size_w => 32,
3686 size_h => 8,
2904 aspect => 1, 3687 aspect => 1,
2905 can_events => 0, 3688 can_events => 0,
2906 @_, 3689 @_,
2907 ); 3690 );
2908 3691
2909 if ($self->{anim} && $self->{animspeed}) { 3692 if ($self->{anim} && $self->{animspeed}) {
2910 Scalar::Util::weaken (my $widget = $self); 3693 DC::weaken (my $widget = $self);
2911 3694
2912 $self->{timer} = Event->timer ( 3695 $self->{animspeed} = List::Util::max 0.05, $self->{animspeed};
2913 at => $self->{animspeed} * int $::NOW / $self->{animspeed}, 3696 $self->{timer} = EV::periodic_ns 0, $self->{animspeed}, undef, sub {
2914 hard => 1, 3697 return unless $::CONN;
2915 interval => $self->{animspeed}, 3698
2916 cb => sub { 3699 my $w = $widget
3700 or return;
3701
2917 ++$widget->{frame}; 3702 ++$w->{frame};
3703 $w->update_face;
3704
3705 # somehow, $widget can go away
2918 $widget->update; 3706 $w->update;
2919 }, 3707 $w->update_timer;
2920 ); 3708 };
3709
3710 $self->update_face;
3711 $self->update_timer;
2921 } 3712 }
2922 3713
2923 $self 3714 $self
2924} 3715}
2925 3716
3717sub update_timer {
3718 my ($self) = @_;
3719
3720 return unless $self->{timer};
3721
3722 if ($self->{visible}) {
3723 $self->{timer}->start;
3724 } else {
3725 $self->{timer}->stop;
3726 }
3727}
3728
3729sub update_face {
3730 my ($self) = @_;
3731
3732 if ($::CONN) {
3733 if (my $anim = $::CONN->{anim}[$self->{anim}]) {
3734 if ($anim && @$anim) {
3735 $self->{face} = $anim->[ $self->{frame} % @$anim ];
3736 delete $self->{face_change_cb};
3737
3738 if (my $tex = $self->{tex} = $::CONN->{texture}[ $::CONN->{face}[$self->{face}]{id} ]) {
3739 unless ($tex->{name} || $tex->{loading}) {
3740 $tex->upload (sub { $self->reconfigure });
3741 }
3742 }
3743 }
3744 }
3745 }
3746}
3747
2926sub size_request { 3748sub size_request {
2927 (32, 8) 3749 my ($self) = @_;
3750
3751 if ($::CONN) {
3752 if (my $faceid = $::CONN->{face}[$self->{face}]{id}) {
3753 if (my $tex = $self->{tex} = $::CONN->{texture}[$faceid]) {
3754 if ($tex->{name}) {
3755 return ($self->{size_w} || $tex->{w}, $self->{size_h} || $tex->{h});
3756 } elsif (!$tex->{loading}) {
3757 $tex->upload (sub { $self->reconfigure });
3758 }
3759 }
3760
3761 $self->{face_change_cb} ||= $::CONN->on_face_change ($self->{face}, sub { $self->reconfigure });
3762 }
3763 }
3764
3765 ($self->{size_w} || 8, $self->{size_h} || 8)
2928} 3766}
2929 3767
2930sub update { 3768sub update {
2931 my ($self) = @_; 3769 my ($self) = @_;
2932 3770
2933 return unless $self->{visible}; 3771 return unless $self->{visible};
2934 3772
2935 $self->SUPER::update; 3773 $self->SUPER::update;
2936} 3774}
2937 3775
3776sub invoke_visibility_change {
3777 my ($self) = @_;
3778
3779 $self->update_timer;
3780
3781 0
3782}
3783
2938sub _draw { 3784sub _draw {
2939 my ($self) = @_; 3785 my ($self) = @_;
2940 3786
2941 return unless $::CONN; 3787 $self->SUPER::_draw;
2942 3788
2943 my $face; 3789 if (my $tex = $self->{tex}) {
2944
2945 if ($self->{frame}) {
2946 my $anim = $::CONN->{anim}[$self->{anim}];
2947
2948 $face = $anim->[ $self->{frame} % @$anim ]
2949 if $anim && @$anim;
2950 }
2951
2952 my $tex = $::CONN->{texture}[$::CONN->{faceid}[$face || $self->{face}]];
2953
2954 if ($tex) {
2955 glEnable GL_TEXTURE_2D; 3790 glEnable GL_TEXTURE_2D;
2956 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 3791 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2957 glColor 0, 0, 0, 1; 3792 glColor 0, 0, 0, 1;
2958 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h}); 3793 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2959 glDisable GL_TEXTURE_2D; 3794 glDisable GL_TEXTURE_2D;
2961} 3796}
2962 3797
2963sub destroy { 3798sub destroy {
2964 my ($self) = @_; 3799 my ($self) = @_;
2965 3800
2966 $self->{timer}->cancel 3801 (delete $self->{timer})->cancel
2967 if $self->{timer}; 3802 if $self->{timer};
2968 3803
2969 $self->SUPER::destroy; 3804 $self->SUPER::destroy;
2970} 3805}
2971 3806
2972############################################################################# 3807#############################################################################
2973 3808
2974package CFClient::UI::Buttonbar; 3809package DC::UI::Buttonbar;
2975 3810
2976our @ISA = CFClient::UI::HBox::; 3811our @ISA = DC::UI::HBox::;
2977 3812
2978# TODO: should actualyl wrap buttons and other goodies. 3813# TODO: should actually wrap buttons and other goodies.
2979 3814
2980############################################################################# 3815#############################################################################
2981 3816
2982package CFClient::UI::Menu; 3817package DC::UI::Menu;
2983 3818
2984our @ISA = CFClient::UI::FancyFrame::; 3819our @ISA = DC::UI::Toplevel::;
2985 3820
2986use CFClient::OpenGL; 3821use DC::OpenGL;
2987 3822
2988sub new { 3823sub new {
2989 my $class = shift; 3824 my $class = shift;
2990 3825
2991 my $self = $class->SUPER::new ( 3826 my $self = $class->SUPER::new (
2992 items => [], 3827 items => [],
2993 z => 100, 3828 z => 100,
2994 @_, 3829 @_,
2995 ); 3830 );
2996 3831
2997 $self->add ($self->{vbox} = new CFClient::UI::VBox); 3832 $self->add ($self->{vbox} = new DC::UI::VBox);
2998 3833
2999 for my $item (@{ $self->{items} }) { 3834 for my $item (@{ $self->{items} }) {
3000 my ($widget, $cb, $tooltip) = @$item; 3835 my ($widget, $cb, $tooltip) = @$item;
3001 3836
3002 # handle various types of items, only text for now 3837 # handle various types of items, only text for now
3003 if (!ref $widget) { 3838 if (!ref $widget) {
3839 if ($widget =~ /\t/) {
3840 my ($left, $right) = split /\t/, $widget, 2;
3841
3004 $widget = new CFClient::UI::Label 3842 $widget = new DC::UI::HBox
3005 can_hover => 1, 3843 can_hover => 1,
3006 can_events => 1, 3844 can_events => 1,
3845 tooltip => $tooltip,
3846 children => [
3847 (new DC::UI::Label markup => $left, expand => 1),
3848 (new DC::UI::Label markup => $right, align => +1),
3849 ],
3850 ;
3851
3852 } else {
3853 $widget = new DC::UI::Label
3854 can_hover => 1,
3855 can_events => 1,
3007 markup => $widget, 3856 markup => $widget,
3008 tooltip => $tooltip 3857 tooltip => $tooltip;
3858 }
3009 } 3859 }
3010 3860
3011 $self->{item}{$widget} = $item; 3861 $self->{item}{$widget} = $item;
3012 3862
3013 $self->{vbox}->add ($widget); 3863 $self->{vbox}->add ($widget);
3056 1 3906 1
3057} 3907}
3058 3908
3059############################################################################# 3909#############################################################################
3060 3910
3061package CFClient::UI::Multiplexer; 3911package DC::UI::Multiplexer;
3062 3912
3063our @ISA = CFClient::UI::Container::; 3913our @ISA = DC::UI::Container::;
3064 3914
3065sub new { 3915sub new {
3066 my $class = shift; 3916 my $class = shift;
3067 3917
3068 my $self = $class->SUPER::new ( 3918 my $self = $class->SUPER::new (
3082 3932
3083 $self->{current} = $self->{children}[0] 3933 $self->{current} = $self->{children}[0]
3084 if @{ $self->{children} }; 3934 if @{ $self->{children} };
3085} 3935}
3086 3936
3937sub get_current_page {
3938 my ($self) = @_;
3939
3940 $self->{current}
3941}
3942
3087sub set_current_page { 3943sub set_current_page {
3088 my ($self, $page_or_widget) = @_; 3944 my ($self, $page_or_widget) = @_;
3089 3945
3090 my $widget = ref $page_or_widget 3946 my $widget = ref $page_or_widget
3091 ? $page_or_widget 3947 ? $page_or_widget
3123 $self->{current}->draw; 3979 $self->{current}->draw;
3124} 3980}
3125 3981
3126############################################################################# 3982#############################################################################
3127 3983
3128package CFClient::UI::Notebook; 3984package DC::UI::Notebook;
3129 3985
3986use DC::OpenGL;
3987
3130our @ISA = CFClient::UI::VBox::; 3988our @ISA = DC::UI::VBox::;
3131 3989
3132sub new { 3990sub new {
3133 my $class = shift; 3991 my $class = shift;
3134 3992
3135 my $self = $class->SUPER::new ( 3993 my $self = $class->SUPER::new (
3136 buttonbar => (new CFClient::UI::Buttonbar), 3994 buttonbar => (new DC::UI::Buttonbar),
3137 multiplexer => (new CFClient::UI::Multiplexer expand => 1), 3995 multiplexer => (new DC::UI::Multiplexer expand => 1),
3996 active_outline => [.7, .7, 0.2],
3138 # filter => # will be put between multiplexer and $self 3997 # filter => # will be put between multiplexer and $self
3139 @_, 3998 @_,
3140 ); 3999 );
3141 4000
3142 $self->{filter}->add ($self->{multiplexer}) if $self->{filter}; 4001 $self->{filter}->add ($self->{multiplexer}) if $self->{filter};
3143 $self->SUPER::add ($self->{buttonbar}, $self->{filter} || $self->{multiplexer}); 4002 $self->SUPER::add ($self->{buttonbar}, $self->{filter} || $self->{multiplexer});
3144 4003
4004 {
4005 Scalar::Util::weaken (my $wself = $self);
4006
4007 $self->{multiplexer}->connect (c_add => sub {
4008 my ($mplex, $widgets) = @_;
4009
4010 for my $child (@$widgets) {
4011 Scalar::Util::weaken $child;
4012 $child->{c_tab_} ||= do {
4013 my $tab =
4014 (UNIVERSAL::isa $child->{c_tab}, "DC::UI::Base")
4015 ? $child->{c_tab}
4016 : new DC::UI::Button markup => $child->{c_tab}[0], tooltip => $child->{c_tab}[1];
4017
4018 $tab->connect (activate => sub {
4019 $wself->set_current_page ($child);
4020 });
4021
4022 $tab
4023 };
4024
4025 $self->{buttonbar}->add ($child->{c_tab_});
4026 }
4027 });
4028
4029 $self->{multiplexer}->connect (c_remove => sub {
4030 my ($mplex, $widgets) = @_;
4031
4032 for my $child (@$widgets) {
4033 $wself->{buttonbar}->remove ($child->{c_tab_});
4034 }
4035 });
4036 }
4037
3145 $self 4038 $self
3146} 4039}
3147 4040
3148sub add { 4041sub add {
4042 my ($self, @widgets) = @_;
4043
4044 $self->{multiplexer}->add (@widgets)
4045}
4046
4047sub remove {
4048 my ($self, @widgets) = @_;
4049
4050 $self->{multiplexer}->remove (@widgets)
4051}
4052
4053sub pages {
4054 my ($self) = @_;
4055 $self->{multiplexer}->children
4056}
4057
4058sub add_tab {
3149 my ($self, $title, $widget, $tooltip) = @_; 4059 my ($self, $title, $widget, $tooltip) = @_;
3150 4060
3151 Scalar::Util::weaken $self; 4061 $title = [$title, $tooltip] unless ref $title;
4062 $widget->{c_tab} = $title;
3152 4063
3153 $self->{buttonbar}->add (new CFClient::UI::Button
3154 markup => $title,
3155 tooltip => $tooltip,
3156 on_activate => sub { $self->set_current_page ($widget) },
3157 );
3158
3159 $self->{multiplexer}->add ($widget); 4064 $self->add ($widget);
4065}
4066
4067sub get_current_page {
4068 my ($self) = @_;
4069
4070 $self->{multiplexer}->get_current_page
3160} 4071}
3161 4072
3162sub set_current_page { 4073sub set_current_page {
3163 my ($self, $page) = @_; 4074 my ($self, $page) = @_;
3164 4075
3165 $self->{multiplexer}->set_current_page ($page); 4076 $self->{multiplexer}->set_current_page ($page);
3166 $self->emit (page_changed => $self->{multiplexer}{current}); 4077 $self->emit (page_changed => $self->{multiplexer}{current});
3167} 4078}
3168 4079
4080sub _draw {
4081 my ($self) = @_;
4082
4083 $self->SUPER::_draw ();
4084
4085 if (my $cur = $self->{multiplexer}{current}) {
4086 if ($cur = $cur->{c_tab_}) {
4087 glTranslate $self->{buttonbar}{x} + $cur->{x},
4088 $self->{buttonbar}{y} + $cur->{y};
4089 glLineWidth 3;
4090 #glEnable GL_BLEND;
4091 #glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
4092 glColor @{$self->{active_outline}};
4093 glRect_lineloop 1.5, 1.5, $cur->{w} - 1.5, $cur->{h} - 1.5;
4094 glLineWidth 1;
4095 #glDisable GL_BLEND;
4096 }
4097 }
4098}
4099
3169############################################################################# 4100#############################################################################
3170 4101
3171package CFClient::UI::Combobox; 4102package DC::UI::Selector;
3172 4103
3173use utf8; 4104use utf8;
3174 4105
3175our @ISA = CFClient::UI::Button::; 4106our @ISA = DC::UI::Button::;
3176 4107
3177sub new { 4108sub new {
3178 my $class = shift; 4109 my $class = shift;
3179 4110
3180 my $self = $class->SUPER::new ( 4111 my $self = $class->SUPER::new (
3197 my ($value, $title, $tooltip) = @$_; 4128 my ($value, $title, $tooltip) = @$_;
3198 4129
3199 push @menu_items, [$tooltip || $title, sub { $self->set_value ($value) }]; 4130 push @menu_items, [$tooltip || $title, sub { $self->set_value ($value) }];
3200 } 4131 }
3201 4132
3202 CFClient::UI::Menu->new (items => \@menu_items)->popup ($ev); 4133 DC::UI::Menu->new (items => \@menu_items)->popup ($ev);
3203} 4134}
3204 4135
3205sub _set_value { 4136sub _set_value {
3206 my ($self, $value) = @_; 4137 my ($self, $value) = @_;
3207 4138
3208 my ($item) = grep $_->[0] eq $value, @{ $self->{options} } 4139 my ($item) = grep $_->[0] eq $value, @{ $self->{options} };
4140 $item ||= $self->{options}[0]
3209 or return; 4141 or return;
3210 4142
3211 $self->{value} = $item->[0]; 4143 $self->{value} = $item->[0];
3212 $self->set_markup ("$item->[1] ⇓"); 4144 $self->set_markup ("$item->[1] ⇓");
3213 $self->set_tooltip ($item->[2]); 4145# $self->set_tooltip ($item->[2]);
3214} 4146}
3215 4147
3216sub set_value { 4148sub set_value {
3217 my ($self, $value) = @_; 4149 my ($self, $value) = @_;
3218 4150
3220 4152
3221 $self->_set_value ($value); 4153 $self->_set_value ($value);
3222 $self->emit (changed => $value); 4154 $self->emit (changed => $value);
3223} 4155}
3224 4156
4157sub set_options {
4158 my ($self, $options) = @_;
4159
4160 $self->{options} = $options;
4161 $self->_set_value ($self->{value});
4162}
4163
3225############################################################################# 4164#############################################################################
3226 4165
3227package CFClient::UI::Statusbox; 4166package DC::UI::Statusbox;
3228 4167
3229our @ISA = CFClient::UI::VBox::; 4168our @ISA = DC::UI::VBox::;
3230 4169
3231sub new { 4170sub new {
3232 my $class = shift; 4171 my $class = shift;
3233 4172
3234 my $self = $class->SUPER::new ( 4173 my $self = $class->SUPER::new (
3235 fontsize => 0.8, 4174 fontsize => 0.8,
3236 @_, 4175 @_,
3237 ); 4176 );
3238 4177
3239 Scalar::Util::weaken (my $this = $self); 4178 DC::weaken (my $this = $self);
3240 4179
3241 $self->{timer} = Event->timer (after => 1, interval => 1, cb => sub { $this->reorder }); 4180 $self->{timer} = EV::timer 1, 1, sub { $this->reorder };
3242 4181
3243 $self 4182 $self
3244} 4183}
3245 4184
3246sub reorder { 4185sub reorder {
3247 my ($self) = @_; 4186 my ($self) = @_;
3248 my $NOW = Time::HiRes::time; 4187 my $NOW = Time::HiRes::time;
3249 4188
3250 # freeze display when hovering over any label 4189 # freeze display when hovering over any label
3251 return if $CFClient::UI::TOOLTIP->{owner} 4190 return if $DC::UI::TOOLTIP->{owner}
3252 && grep $CFClient::UI::TOOLTIP->{owner} == $_->{label}, 4191 && grep $DC::UI::TOOLTIP->{owner} == $_->{label},
3253 values %{ $self->{item} }; 4192 values %{ $self->{item} };
3254 4193
3255 while (my ($k, $v) = each %{ $self->{item} }) { 4194 while (my ($k, $v) = each %{ $self->{item} }) {
3256 delete $self->{item}{$k} if $v->{timeout} < $NOW; 4195 delete $self->{item}{$k} if $v->{timeout} < $NOW;
3257 } 4196 }
4197
4198 $self->{timer}->set (1, 1);
3258 4199
3259 my @widgets; 4200 my @widgets;
3260 4201
3261 my @items = sort { 4202 my @items = sort {
3262 $a->{pri} <=> $b->{pri} 4203 $a->{pri} <=> $b->{pri}
3263 or $b->{id} <=> $a->{id} 4204 or $b->{id} <=> $a->{id}
3264 } values %{ $self->{item} }; 4205 } values %{ $self->{item} };
3265
3266 $self->{timer}->interval (1);
3267 4206
3268 my $count = 10 + 1; 4207 my $count = 10 + 1;
3269 for my $item (@items) { 4208 for my $item (@items) {
3270 last unless --$count; 4209 last unless --$count;
3271 4210
3278 for ($short) { 4217 for ($short) {
3279 s/^\s+//; 4218 s/^\s+//;
3280 s/\s+/ /g; 4219 s/\s+/ /g;
3281 } 4220 }
3282 4221
3283 new CFClient::UI::Label 4222 new DC::UI::Label
3284 markup => $short, 4223 markup => $short,
3285 tooltip => $item->{tooltip}, 4224 tooltip => $item->{tooltip},
3286 tooltip_font => $::FONT_PROP, 4225 tooltip_font => $::FONT_PROP,
3287 tooltip_width => 0.67, 4226 tooltip_width => 0.67,
3288 fontsize => $item->{fontsize} || $self->{fontsize}, 4227 fontsize => $item->{fontsize} || $self->{fontsize},
3295 if ((my $diff = $item->{timeout} - $NOW) < 2) { 4234 if ((my $diff = $item->{timeout} - $NOW) < 2) {
3296 $label->{fg}[3] = ($item->{fg}[3] || 1) * $diff / 2; 4235 $label->{fg}[3] = ($item->{fg}[3] || 1) * $diff / 2;
3297 $label->update; 4236 $label->update;
3298 $label->set_max_size (undef, $label->{req_h} * $diff) 4237 $label->set_max_size (undef, $label->{req_h} * $diff)
3299 if $diff < 1; 4238 if $diff < 1;
3300 $self->{timer}->interval (1/30); 4239 $self->{timer}->set (1/30, 1/30);
3301 } else { 4240 } else {
3302 $label->{fg}[3] = $item->{fg}[3] || 1; 4241 $label->{fg}[3] = $item->{fg}[3] || 1;
3303 } 4242 }
3304 4243
3305 push @widgets, $label; 4244 push @widgets, $label;
3342 count => 1, 4281 count => 1,
3343 %arg, 4282 %arg,
3344 }; 4283 };
3345 } 4284 }
3346 4285
4286 $ROOT->on_refresh (reorder => sub {
3347 $self->reorder; 4287 $self->reorder;
4288 });
3348} 4289}
3349 4290
3350sub reconfigure { 4291sub reconfigure {
3351 my ($self) = @_; 4292 my ($self) = @_;
3352 4293
3365 $self->SUPER::destroy; 4306 $self->SUPER::destroy;
3366} 4307}
3367 4308
3368############################################################################# 4309#############################################################################
3369 4310
3370package CFClient::UI::Inventory;
3371
3372our @ISA = CFClient::UI::ScrolledWindow::;
3373
3374sub new {
3375 my $class = shift;
3376
3377 my $self = $class->SUPER::new (
3378 child => (new CFClient::UI::Table col_expand => [0, 1, 0]),
3379 @_,
3380 );
3381
3382 $self
3383}
3384
3385sub set_items {
3386 my ($self, $items) = @_;
3387
3388 $self->{child}->clear;
3389 return unless $items;
3390
3391 my @items = sort {
3392 ($a->{type} <=> $b->{type})
3393 or ($a->{name} cmp $b->{name})
3394 } @$items;
3395
3396 $self->{real_items} = \@items;
3397
3398 my $row = 0;
3399 for my $item (@items) {
3400 CFClient::Item::update_widgets $item;
3401
3402 $self->{child}->add (0, $row, $item->{face_widget});
3403 $self->{child}->add (1, $row, $item->{desc_widget});
3404 $self->{child}->add (2, $row, $item->{weight_widget});
3405
3406 $row++;
3407 }
3408}
3409
3410#############################################################################
3411
3412package CFClient::UI::BindEditor;
3413
3414our @ISA = CFClient::UI::FancyFrame::;
3415
3416sub new {
3417 my $class = shift;
3418
3419 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3420
3421 $self->add (my $vb = new CFClient::UI::VBox);
3422
3423
3424 $vb->add ($self->{rec_btn} = new CFClient::UI::Button
3425 text => "start recording",
3426 tooltip => "Start/Stops recording of actions."
3427 ."All subsequent actions after the recording started will be captured."
3428 ."The actions are displayed after the record was stopped."
3429 ."To bind the action you have to click on the 'Bind' button",
3430 on_activate => sub {
3431 unless ($self->{recording}) {
3432 $self->start;
3433 } else {
3434 $self->stop;
3435 }
3436 });
3437
3438 $vb->add (new CFClient::UI::Label text => "Actions:");
3439 $vb->add ($self->{cmdbox} = new CFClient::UI::VBox);
3440
3441 $vb->add (new CFClient::UI::Label text => "Bound to: ");
3442 $vb->add (my $hb = new CFClient::UI::HBox);
3443 $hb->add ($self->{keylbl} = new CFClient::UI::Label expand => 1);
3444 $hb->add (new CFClient::UI::Button
3445 text => "bind",
3446 tooltip => "This opens a query where you have to press the key combination to bind the recorded actions",
3447 on_activate => sub {
3448 $self->ask_for_bind;
3449 });
3450
3451 $vb->add (my $hb = new CFClient::UI::HBox);
3452 $hb->add (new CFClient::UI::Button
3453 text => "ok",
3454 expand => 1,
3455 tooltip => "This closes the binding editor and saves the binding",
3456 on_activate => sub {
3457 $self->hide;
3458 $self->commit;
3459 });
3460
3461 $hb->add (new CFClient::UI::Button
3462 text => "cancel",
3463 expand => 1,
3464 tooltip => "This closes the binding editor without saving",
3465 on_activate => sub {
3466 $self->hide;
3467 $self->{binding_cancel}->()
3468 if $self->{binding_cancel};
3469 });
3470
3471 $self->update_binding_widgets;
3472
3473 $self
3474}
3475
3476sub commit {
3477 my ($self) = @_;
3478 my ($mod, $sym, $cmds) = $self->get_binding;
3479 if ($sym != 0 && @$cmds > 0) {
3480 $::STATUSBOX->add ("Bound actions to '".CFClient::Binder::keycombo_to_name ($mod, $sym)
3481 ."'. Don't forget 'Save Config'!");
3482 $self->{binding_change}->($mod, $sym, $cmds)
3483 if $self->{binding_change};
3484 } else {
3485 $::STATUSBOX->add ("No action bound, no key or action specified!");
3486 $self->{binding_cancel}->()
3487 if $self->{binding_cancel};
3488 }
3489}
3490
3491sub start {
3492 my ($self) = @_;
3493
3494 $self->{rec_btn}->set_text ("stop recording");
3495 $self->{recording} = 1;
3496 $self->clear_command_list;
3497 $::CONN->start_record if $::CONN;
3498}
3499
3500sub stop {
3501 my ($self) = @_;
3502
3503 $self->{rec_btn}->set_text ("start recording");
3504 $self->{recording} = 0;
3505
3506 my $rec;
3507 $rec = $::CONN->stop_record if $::CONN;
3508 return unless ref $rec eq 'ARRAY';
3509 $self->set_command_list ($rec);
3510}
3511
3512
3513sub ask_for_bind_and_commit {
3514 my ($self) = @_;
3515 $self->ask_for_bind (1);
3516}
3517
3518sub ask_for_bind {
3519 my ($self, $commit, $end_cb) = @_;
3520
3521 CFClient::Binder::open_binding_dialog (sub {
3522 my ($mod, $sym) = @_;
3523 $self->{binding} = [$mod, $sym]; # XXX: how to stop that memleak?
3524 $self->update_binding_widgets;
3525 $self->commit if $commit;
3526 $end_cb->() if $end_cb;
3527 });
3528}
3529
3530# $mod and $sym are the modifiers and key symbol
3531# $cmds is a array ref of strings (the commands)
3532# $cb is the callback that is executed on OK
3533# $ccb is the callback that is executed on CANCEL and
3534# when the binding was unsuccessful on OK
3535sub set_binding {
3536 my ($self, $mod, $sym, $cmds, $cb, $ccb) = @_;
3537
3538 $self->clear_command_list;
3539 $self->{recording} = 0;
3540 $self->{rec_btn}->set_text ("start recording");
3541
3542 $self->{binding} = [$mod, $sym];
3543 $self->{commands} = $cmds;
3544
3545 $self->{binding_change} = $cb;
3546 $self->{binding_cancel} = $ccb;
3547
3548 $self->update_binding_widgets;
3549}
3550
3551# this is a shortcut method that asks for a binding
3552# and then just binds it.
3553sub do_quick_binding {
3554 my ($self, $cmds, $end_cb) = @_;
3555 $self->set_binding (undef, undef, $cmds, sub {
3556 $::CFG->{bindings}->{$_[0]}->{$_[1]} = $_[2];
3557 });
3558 $self->ask_for_bind (1, $end_cb);
3559}
3560
3561sub update_binding_widgets {
3562 my ($self) = @_;
3563 my ($mod, $sym, $cmds) = $self->get_binding;
3564 $self->{keylbl}->set_text (CFClient::Binder::keycombo_to_name ($mod, $sym));
3565 $self->set_command_list ($cmds);
3566}
3567
3568sub get_binding {
3569 my ($self) = @_;
3570 return (
3571 $self->{binding}->[0],
3572 $self->{binding}->[1],
3573 [ grep { defined $_ } @{$self->{commands}} ]
3574 );
3575}
3576
3577sub clear_command_list {
3578 my ($self) = @_;
3579 $self->{cmdbox}->clear ();
3580}
3581
3582sub set_command_list {
3583 my ($self, $cmds) = @_;
3584
3585 $self->{cmdbox}->clear ();
3586 $self->{commands} = $cmds;
3587
3588 my $idx = 0;
3589
3590 for (@$cmds) {
3591 $self->{cmdbox}->add (my $hb = new CFClient::UI::HBox);
3592
3593 my $i = $idx;
3594 $hb->add (new CFClient::UI::Label text => $_);
3595 $hb->add (new CFClient::UI::Button
3596 text => "delete",
3597 tooltip => "Deletes the action from the record",
3598 on_activate => sub {
3599 $self->{cmdbox}->remove ($hb);
3600 $cmds->[$i] = undef;
3601 });
3602
3603
3604 $idx++
3605 }
3606}
3607
3608#############################################################################
3609
3610package CFClient::UI::SpellList;
3611
3612our @ISA = CFClient::UI::Table::;
3613
3614sub new {
3615 my $class = shift;
3616
3617 my $self = $class->SUPER::new (
3618 binding => [],
3619 commands => [],
3620 @_,
3621 )
3622}
3623
3624my $TOOLTIP_ALL = "\n\n<small>Left click - ready spell\nMiddle click - invoke spell\nRight click - further options</small>";
3625
3626my @TOOLTIP_NAME = (align => -1, can_events => 1, can_hover => 1, tooltip =>
3627 "<b>Name</b>. The name of the spell.$TOOLTIP_ALL");
3628my @TOOLTIP_SKILL = (align => -1, can_events => 1, can_hover => 1, tooltip =>
3629 "<b>Skill</b>. The skill (or magic school) required to be able to attempt casting this spell.$TOOLTIP_ALL");
3630my @TOOLTIP_LVL = (align => 1, can_events => 1, can_hover => 1, tooltip =>
3631 "<b>Level</b>. Minimum level the caster needs in the associated skill to be able to attempt casting this spell.$TOOLTIP_ALL");
3632my @TOOLTIP_SP = (align => 1, can_events => 1, can_hover => 1, tooltip =>
3633 "<b>Spell points / Grace points</b>. Amount of spell or grace points used by each invocation.$TOOLTIP_ALL");
3634my @TOOLTIP_DMG = (align => 1, can_events => 1, can_hover => 1, tooltip =>
3635 "<b>Damage</b>. The amount of damage the spell deals when it hits.$TOOLTIP_ALL");
3636
3637sub rebuild_spell_list {
3638 my ($self) = @_;
3639
3640 $CFClient::UI::ROOT->on_refresh ($self => sub {
3641 $self->clear;
3642
3643 return unless $::CONN;
3644
3645 $self->add (1, 0, new CFClient::UI::Label text => "Spell Name", @TOOLTIP_NAME);
3646 $self->add (2, 0, new CFClient::UI::Label text => "Skill", @TOOLTIP_SKILL);
3647 $self->add (3, 0, new CFClient::UI::Label text => "Lvl" , @TOOLTIP_LVL);
3648 $self->add (4, 0, new CFClient::UI::Label text => "Sp/Gp", @TOOLTIP_SP);
3649 $self->add (5, 0, new CFClient::UI::Label text => "Dmg" , @TOOLTIP_DMG);
3650
3651 my $row = 0;
3652
3653 for (sort { $a cmp $b } keys %{ $self->{spell} }) {
3654 my $spell = $self->{spell}{$_};
3655
3656 $row++;
3657
3658 my $spell_cb = sub {
3659 my ($widget, $ev) = @_;
3660
3661 if ($ev->{button} == 1) {
3662 $::CONN->user_send ("cast $spell->{name}");
3663 } elsif ($ev->{button} == 2) {
3664 $::CONN->user_send ("invoke $spell->{name}");
3665 } elsif ($ev->{button} == 3) {
3666 (new CFClient::UI::Menu
3667 items => [
3668 ["bind <i>cast $spell->{name}</i> to a key" => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) }],
3669 ["bind <i>invoke $spell->{name}</i> to a key" => sub { $::BIND_EDITOR->do_quick_binding (["invoke $spell->{name}"]) }],
3670 ],
3671 )->popup ($ev);
3672 } else {
3673 return 0;
3674 }
3675
3676 1
3677 };
3678
3679 my $tooltip = "$spell->{message}$TOOLTIP_ALL";
3680
3681 #TODO: add path info to tooltip
3682 #$self->add (6, $row, new CFClient::UI::Label text => $spell->{path});
3683
3684 $self->add (0, $row, new CFClient::UI::Face
3685 face => $spell->{face},
3686 can_hover => 1,
3687 can_events => 1,
3688 tooltip => $tooltip,
3689 on_button_down => $spell_cb,
3690 );
3691
3692 $self->add (1, $row, new CFClient::UI::Label
3693 expand => 1,
3694 text => $spell->{name},
3695 can_hover => 1,
3696 can_events => 1,
3697 tooltip => $tooltip,
3698 on_button_down => $spell_cb,
3699 );
3700
3701 $self->add (2, $row, new CFClient::UI::Label text => $::CONN->{skill_info}{$spell->{skill}}, @TOOLTIP_SKILL);
3702 $self->add (3, $row, new CFClient::UI::Label text => $spell->{level}, @TOOLTIP_LVL);
3703 $self->add (4, $row, new CFClient::UI::Label text => $spell->{mana} || $spell->{grace}, @TOOLTIP_SP);
3704 $self->add (5, $row, new CFClient::UI::Label text => $spell->{damage}, @TOOLTIP_DMG);
3705 }
3706 });
3707}
3708
3709sub add_spell {
3710 my ($self, $spell) = @_;
3711
3712 $self->{spell}->{$spell->{name}} = $spell;
3713 $self->rebuild_spell_list;
3714}
3715
3716sub remove_spell {
3717 my ($self, $spell) = @_;
3718
3719 delete $self->{spell}->{$spell->{name}};
3720 $self->rebuild_spell_list;
3721}
3722
3723sub clear_spells {
3724 my ($self) = @_;
3725
3726 $self->{spell} = {};
3727 $self->rebuild_spell_list;
3728}
3729
3730#############################################################################
3731
3732package CFClient::UI::Root; 4311package DC::UI::Root;
3733 4312
3734our @ISA = CFClient::UI::Container::; 4313our @ISA = DC::UI::Container::;
3735 4314
3736use List::Util qw(min max); 4315use List::Util qw(min max);
3737 4316
3738use CFClient::OpenGL; 4317use DC::OpenGL;
3739 4318
3740sub new { 4319sub new {
3741 my $class = shift; 4320 my $class = shift;
3742 4321
3743 my $self = $class->SUPER::new ( 4322 my $self = $class->SUPER::new (
3744 visible => 1, 4323 visible => 1,
3745 @_, 4324 @_,
3746 ); 4325 );
3747 4326
3748 Scalar::Util::weaken ($self->{root} = $self); 4327 DC::weaken ($self->{root} = $self);
3749 4328
3750 $self 4329 $self
3751} 4330}
3752 4331
3753sub size_request { 4332sub size_request {
3801} 4380}
3802 4381
3803sub update { 4382sub update {
3804 my ($self) = @_; 4383 my ($self) = @_;
3805 4384
3806 $::WANT_REFRESH++; 4385 $::WANT_REFRESH = 1;
3807} 4386}
3808 4387
3809sub add { 4388sub add {
3810 my ($self, @children) = @_; 4389 my ($self, @children) = @_;
3811 4390
3848 while ($self->{refresh_hook}) { 4427 while ($self->{refresh_hook}) {
3849 $_->() 4428 $_->()
3850 for values %{delete $self->{refresh_hook}}; 4429 for values %{delete $self->{refresh_hook}};
3851 } 4430 }
3852 4431
3853 if ($self->{realloc}) { 4432 while ($self->{realloc}) {
3854 my %queue; 4433 my %queue;
3855 my @queue; 4434 my @queue;
3856 my $widget; 4435 my $widget;
3857 4436
3858 outer: 4437 outer:
3905 } 4484 }
3906 } 4485 }
3907 4486
3908 delete $self->{realloc}{$widget+0}; 4487 delete $self->{realloc}{$widget+0};
3909 } 4488 }
3910 }
3911 4489
3912 while (my $size_alloc = delete $self->{size_alloc}) { 4490 while (my $size_alloc = delete $self->{size_alloc}) {
3913 my @queue = sort { $b->{visible} <=> $a->{visible} } 4491 my @queue = sort { $a->{visible} <=> $b->{visible} }
3914 values %$size_alloc; 4492 values %$size_alloc;
3915 4493
3916 while () { 4494 while () {
3917 my $widget = pop @queue || last; 4495 my $widget = pop @queue || last;
3918 4496
3919 my ($w, $h) = @$widget{qw(alloc_w alloc_h)}; 4497 my ($w, $h) = @$widget{qw(alloc_w alloc_h)};
3920 4498
3921 $w = 0 if $w < 0; 4499 $w = max $widget->{min_w}, $w;
3922 $h = 0 if $h < 0; 4500 $h = max $widget->{min_h}, $h;
3923 4501
4502# $w = min $self->{w} - $widget->{x}, $w if $self->{w};
4503# $h = min $self->{h} - $widget->{y}, $h if $self->{h};
4504
4505 $w = min $widget->{max_w}, $w if exists $widget->{max_w};
4506 $h = min $widget->{max_h}, $h if exists $widget->{max_h};
4507
3924 $w = int $w + 0.5; 4508 $w = int $w + 0.5;
3925 $h = int $h + 0.5; 4509 $h = int $h + 0.5;
3926 4510
3927 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) { 4511 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) {
3928 $widget->{old_w} = $widget->{w}; 4512 $widget->{old_w} = $widget->{w};
3929 $widget->{old_h} = $widget->{h}; 4513 $widget->{old_h} = $widget->{h};
3930 4514
3931 $widget->{w} = $w; 4515 $widget->{w} = $w;
3932 $widget->{h} = $h; 4516 $widget->{h} = $h;
3933 4517
3934 $widget->emit (size_allocate => $w, $h); 4518 $widget->emit (size_allocate => $w, $h);
4519 }
3935 } 4520 }
3936 } 4521 }
3937 } 4522 }
3938 4523
3939 while ($self->{post_alloc_hook}) { 4524 while ($self->{post_alloc_hook}) {
3940 $_->() 4525 $_->()
3941 for values %{delete $self->{post_alloc_hook}}; 4526 for values %{delete $self->{post_alloc_hook}};
3942 } 4527 }
3943
3944 4528
3945 glViewport 0, 0, $::WIDTH, $::HEIGHT; 4529 glViewport 0, 0, $::WIDTH, $::HEIGHT;
3946 glClearColor +($::CFG->{fow_intensity}) x 3, 1; 4530 glClearColor +($::CFG->{fow_intensity}) x 3, 1;
3947 glClear GL_COLOR_BUFFER_BIT; 4531 glClear GL_COLOR_BUFFER_BIT;
3948 4532
3951 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000; 4535 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000;
3952 glMatrixMode GL_MODELVIEW; 4536 glMatrixMode GL_MODELVIEW;
3953 glLoadIdentity; 4537 glLoadIdentity;
3954 4538
3955 { 4539 {
3956 package CFClient::UI::Base; 4540 package DC::UI::Base;
3957 4541
3958 ($draw_x, $draw_y, $draw_w, $draw_h) = 4542 local ($draw_x, $draw_y, $draw_w, $draw_h) =
3959 (0, 0, $self->{w}, $self->{h}); 4543 (0, 0, $self->{w}, $self->{h});
3960 }
3961 4544
3962 $self->_draw; 4545 $self->_draw;
4546 }
3963} 4547}
3964 4548
3965############################################################################# 4549#############################################################################
3966 4550
3967package CFClient::UI; 4551package DC::UI;
3968 4552
3969$ROOT = new CFClient::UI::Root; 4553$ROOT = new DC::UI::Root;
3970$TOOLTIP = new CFClient::UI::Tooltip z => 900; 4554$TOOLTIP = new DC::UI::Tooltip z => 900;
3971 4555
39721 45561
3973 4557

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines