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.312 by root, Fri Jun 23 23:54:30 2006 UTC vs.
Revision 1.479 by root, Thu Feb 4 20:01:09 2010 UTC

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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines