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.171 by root, Mon Apr 24 11:54:26 2006 UTC vs.
Revision 1.273 by root, Sat Jun 3 02:32:35 2006 UTC

1package CFClient::UI; 1package CFClient::UI;
2 2
3use utf8;
3use strict; 4use strict;
4 5
5use Scalar::Util (); 6use Scalar::Util ();
6use List::Util (); 7use List::Util ();
7 8
8use CFClient; 9use CFClient;
10use CFClient::Texture;
9 11
10our ($FOCUS, $HOVER, $GRAB); # various widgets 12our ($FOCUS, $HOVER, $GRAB); # various widgets
11 13
14our $LAYOUT;
12our $ROOT; 15our $ROOT;
13our $TOOLTIP; 16our $TOOLTIP;
14our $BUTTON_STATE; 17our $BUTTON_STATE;
15 18
19our %WIDGET; # all widgets, weak-referenced
20
21sub get_layout {
22 my $layout;
23
24 for (grep { $_->{name} } values %WIDGET) {
25 my $win = $layout->{$_->{name}} = { };
26
27 $win->{x} = ($_->{x} + $_->{w} * 0.5) / $::WIDTH if $_->{x} =~ /^[0-9.]+$/;
28 $win->{y} = ($_->{y} + $_->{h} * 0.5) / $::HEIGHT if $_->{y} =~ /^[0-9.]+$/;
29 $win->{w} = $_->{w} / $::WIDTH if defined $_->{w};
30 $win->{h} = $_->{h} / $::HEIGHT if defined $_->{h};
31
32 $win->{show} = $_->{visible} && $_->{is_toplevel};
33 }
34
35 $layout
36}
37
38sub set_layout {
39 my ($layout) = @_;
40
41 $LAYOUT = $layout;
42}
43
16sub check_tooltip { 44sub check_tooltip {
45 return if $ENV{CFPLUS_DEBUG} & 8;
46
17 if (!$GRAB) { 47 if (!$GRAB) {
18 for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) { 48 for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) {
19 if (length $widget->{tooltip}) { 49 if (length $widget->{tooltip}) {
20
21 if ($TOOLTIP->{owner} != $widget) { 50 if ($TOOLTIP->{owner} != $widget) {
51 $TOOLTIP->hide;
52
22 $TOOLTIP->{owner} = $widget; 53 $TOOLTIP->{owner} = $widget;
23 54
24 my $tip = $widget->{tooltip}; 55 my $tip = $widget->{tooltip};
25 56
26 $tip = $tip->($widget) if CODE:: eq ref $tip; 57 $tip = $tip->($widget) if CODE:: eq ref $tip;
27 58
28 $TOOLTIP->set_markup ($widget->{tooltip}); 59 $TOOLTIP->set_tooltip_from ($widget);
29 $TOOLTIP->move ($widget->coord2global ($widget->{w}, 0));
30 $TOOLTIP->show; 60 $TOOLTIP->show;
31 } 61 }
32 62
33 return; 63 return;
34 } 64 }
39 delete $TOOLTIP->{owner}; 69 delete $TOOLTIP->{owner};
40} 70}
41 71
42# class methods for events 72# class methods for events
43sub feed_sdl_key_down_event { 73sub feed_sdl_key_down_event {
44 $FOCUS->emit (key_down => $_[0]) || $FOCUS->key_down ($_[0]) 74 $FOCUS->emit (key_down => $_[0])
45 if $FOCUS; 75 if $FOCUS;
46} 76}
47 77
48sub feed_sdl_key_up_event { 78sub feed_sdl_key_up_event {
49 $FOCUS->emit (key_up => $_[0]) || $FOCUS->key_up ($_[0]) 79 $FOCUS->emit (key_up => $_[0])
50 if $FOCUS; 80 if $FOCUS;
51} 81}
52 82
53sub feed_sdl_button_down_event { 83sub feed_sdl_button_down_event {
54 my ($ev) = @_; 84 my ($ev) = @_;
63 check_tooltip; 93 check_tooltip;
64 } 94 }
65 95
66 $BUTTON_STATE |= 1 << ($ev->{button} - 1); 96 $BUTTON_STATE |= 1 << ($ev->{button} - 1);
67 97
68 if ($GRAB) { 98 $GRAB->emit (button_down => $ev, $GRAB->coord2local ($x, $y))
69 ($x, $y) = $GRAB->coord2local ($x, $y); 99 if $GRAB;
70 $GRAB->emit (button_down => $ev, $x, $y) || $GRAB->button_down ($ev, $x, $y);
71 }
72} 100}
73 101
74sub feed_sdl_button_up_event { 102sub feed_sdl_button_up_event {
75 my ($ev) = @_; 103 my ($ev) = @_;
76 my ($x, $y) = ($ev->{x}, $ev->{y}); 104 my ($x, $y) = ($ev->{x}, $ev->{y});
77 105
78 my $widget = $GRAB || $ROOT->find_widget ($x, $y); 106 my $widget = $GRAB || $ROOT->find_widget ($x, $y);
79 107
80 $BUTTON_STATE &= ~(1 << ($ev->{button} - 1)); 108 $BUTTON_STATE &= ~(1 << ($ev->{button} - 1));
81 109
82 if ($GRAB) { 110 $GRAB->emit (button_up => $ev, $GRAB->coord2local ($x, $y))
83 ($x, $y) = $GRAB->coord2local ($x, $y); 111 if $GRAB;
84 $GRAB->emit (button_up => $ev, $x, $y) || $GRAB->button_up ($ev, $x, $y);
85 }
86 112
87 if (!$BUTTON_STATE) { 113 if (!$BUTTON_STATE) {
88 my $grab = $GRAB; undef $GRAB; 114 my $grab = $GRAB; undef $GRAB;
89 $grab->update if $grab; 115 $grab->update if $grab;
90 $GRAB->update if $GRAB; 116 $GRAB->update if $GRAB;
106 $HOVER->update if $HOVER && $HOVER->{can_hover}; 132 $HOVER->update if $HOVER && $HOVER->{can_hover};
107 133
108 check_tooltip; 134 check_tooltip;
109 } 135 }
110 136
111 if ($HOVER) {
112 ($x, $y) = $HOVER->coord2local ($x, $y);
113 $HOVER->emit (mouse_motion => $ev, $x, $y) || $HOVER->mouse_motion ($ev, $x, $y); 137 $HOVER->emit (mouse_motion => $ev, $HOVER->coord2local ($x, $y))
114 } 138 if $HOVER;
115} 139}
116 140
117# convert position array to integers 141# convert position array to integers
118sub harmonize { 142sub harmonize {
119 my ($vals) = @_; 143 my ($vals) = @_;
125 $rem += $_ - $i; 149 $rem += $_ - $i;
126 $_ = $i; 150 $_ = $i;
127 } 151 }
128} 152}
129 153
154sub full_refresh {
155 # make a copy, otherwise for complains about freed values.
156 my @widgets = values %WIDGET;
157
158 $_->update
159 for @widgets;
160}
161
162sub reconfigure_widgets {
163 # make a copy, otherwise C<for> complains about freed values.
164 my @widgets = values %WIDGET;
165
166 $_->reconfigure
167 for @widgets;
168}
169
170# call when resolution changes etc.
171sub rescale_widgets {
172 my ($sx, $sy) = @_;
173
174 for my $widget (values %WIDGET) {
175 if ($widget->{is_toplevel}) {
176 $widget->{x} += int $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/;
177 $widget->{y} += int $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/;
178
179 $widget->{x} = int 0.5 + $widget->{x} * $sx if $widget->{x} =~ /^[0-9.]+$/;
180 $widget->{w} = int 0.5 + $widget->{w} * $sx if exists $widget->{w};
181 $widget->{force_w} = int 0.5 + $widget->{force_w} * $sx if exists $widget->{force_w};
182 $widget->{y} = int 0.5 + $widget->{y} * $sy if $widget->{y} =~ /^[0-9.]+$/;
183 $widget->{h} = int 0.5 + $widget->{h} * $sy if exists $widget->{h};
184 $widget->{force_h} = int 0.5 + $widget->{force_h} * $sy if exists $widget->{force_h};
185
186 $widget->{x} -= int $widget->{w} * 0.5 if $widget->{x} =~ /^[0-9.]+$/;
187 $widget->{y} -= int $widget->{h} * 0.5 if $widget->{y} =~ /^[0-9.]+$/;
188
189 }
190 }
191
192 reconfigure_widgets;
193}
194
130############################################################################# 195#############################################################################
131 196
132package CFClient::UI::Base; 197package CFClient::UI::Base;
133 198
134use strict; 199use strict;
137 202
138sub new { 203sub new {
139 my $class = shift; 204 my $class = shift;
140 205
141 my $self = bless { 206 my $self = bless {
142 x => 0, 207 x => "center",
143 y => 0, 208 y => "center",
144 z => 0, 209 z => 0,
210 w => undef,
211 h => undef,
145 can_events => 1, 212 can_events => 1,
146 @_ 213 @_
147 }, $class; 214 }, $class;
148 215
216 Scalar::Util::weaken ($CFClient::UI::WIDGET{$self+0} = $self);
217
149 for (keys %$self) { 218 for (keys %$self) {
150 if (/^connect_(.*)$/) { 219 if (/^on_(.*)$/) {
151 $self->connect ($1 => delete $self->{$_}); 220 $self->connect ($1 => delete $self->{$_});
152 } 221 }
153 } 222 }
154 223
224 if (my $layout = $CFClient::UI::LAYOUT->{$self->{name}}) {
225 $self->{x} = $layout->{x} * $CFClient::UI::ROOT->{alloc_w} if exists $layout->{x};
226 $self->{y} = $layout->{y} * $CFClient::UI::ROOT->{alloc_h} if exists $layout->{y};
227 $self->{force_w} = $layout->{w} * $CFClient::UI::ROOT->{alloc_w} if exists $layout->{w};
228 $self->{force_h} = $layout->{h} * $CFClient::UI::ROOT->{alloc_h} if exists $layout->{h};
229
230 $self->{x} -= $self->{force_w} * 0.5 if exists $layout->{x};
231 $self->{y} -= $self->{force_h} * 0.5 if exists $layout->{y};
232
233 $self->show if $layout->{show};
234 }
235
155 $self 236 $self
156} 237}
157 238
158sub destroy { 239sub destroy {
159 my ($self) = @_; 240 my ($self) = @_;
168 return if $self->{parent}; 249 return if $self->{parent};
169 250
170 $CFClient::UI::ROOT->add ($self); 251 $CFClient::UI::ROOT->add ($self);
171} 252}
172 253
173sub hide { 254sub set_visible {
174 my ($self) = @_; 255 my ($self) = @_;
256
257 return if $self->{visible};
258
259 $self->{root} = $self->{parent}{root};
260 $self->{visible} = $self->{parent}{visible} + 1;
261
262 $self->emit (visibility_change => 1);
263
264 $self->realloc if !exists $self->{req_w};
265
266 $_->set_visible for $self->children;
267}
268
269sub set_invisible {
270 my ($self) = @_;
271
272 return unless $self->{visible};
273
274 $_->set_invisible for $self->children;
275
276 delete $self->{root};
277 delete $self->{visible};
175 278
176 undef $GRAB if $GRAB == $self; 279 undef $GRAB if $GRAB == $self;
177 undef $HOVER if $HOVER == $self; 280 undef $HOVER if $HOVER == $self;
178 281
282 CFClient::UI::check_tooltip
283 if $TOOLTIP->{owner} == $self;
284
285 $self->focus_out;
286
287 $self->emit (visibility_change => 0);
288}
289
290sub set_visibility {
291 my ($self, $visible) = @_;
292
293 return if $self->{visible} == $visible;
294
295 $visible ? $self->hide
296 : $self->show;
297}
298
299sub toggle_visibility {
300 my ($self) = @_;
301
302 $self->{visible}
303 ? $self->hide
304 : $self->show;
305}
306
307sub hide {
308 my ($self) = @_;
309
310 $self->set_invisible;
311
179 $self->{parent}->remove ($self) 312 $self->{parent}->remove ($self)
180 if $self->{parent}; 313 if $self->{parent};
181} 314}
182 315
183sub move { 316sub move_abs {
184 my ($self, $x, $y, $z) = @_; 317 my ($self, $x, $y, $z) = @_;
185 318
186 $self->{x} = int $x; 319 $self->{x} = List::Util::max 0, int $x;
187 $self->{y} = int $y; 320 $self->{y} = List::Util::max 0, int $y;
188 $self->{z} = $z if defined $z; 321 $self->{z} = $z if defined $z;
189 322
190 $self->update; 323 $self->update;
191} 324}
192 325
193sub set_size { 326sub set_size {
194 my ($self, $w, $h) = @_; 327 my ($self, $w, $h) = @_;
195 328
196 $self->{user_w} = $w; 329 $self->{force_w} = $w;
197 $self->{user_h} = $h; 330 $self->{force_h} = $h;
198 331
199 $self->check_size; 332 $self->realloc;
200} 333}
201 334
202sub size_request { 335sub size_request {
203 require Carp; 336 require Carp;
204 Carp::confess "size_request is abstract"; 337 Carp::confess "size_request is abstract";
206 339
207sub configure { 340sub configure {
208 my ($self, $x, $y, $w, $h) = @_; 341 my ($self, $x, $y, $w, $h) = @_;
209 342
210 if ($self->{aspect}) { 343 if ($self->{aspect}) {
344 my ($ow, $oh) = ($w, $h);
345
211 my $w2 = List::Util::min $w, int $h * $self->{aspect}; 346 $w = List::Util::min $w, int $h * $self->{aspect};
212 my $h2 = List::Util::min $h, int $w / $self->{aspect}; 347 $h = List::Util::min $h, int $w / $self->{aspect};
213 348
214 # use alignment to adjust x, y 349 # use alignment to adjust x, y
215 350
216 $x += int +($w - $w2) * 0.5; 351 $x += int 0.5 * ($ow - $w);
217 $y += int +($h - $h2) * 0.5; 352 $y += int 0.5 * ($oh - $h);
218
219 ($w, $h) = ($w2, $h2);
220 } 353 }
221 354
222 if ($self->{x} != $x || $self->{y} != $y) { 355 if ($self->{x} ne $x || $self->{y} ne $y) {
223 $self->{x} = $x; 356 $self->{x} = $x;
224 $self->{y} = $y; 357 $self->{y} = $y;
225 $self->update; 358 $self->update;
226 } 359 }
227 360
228 if ($self->{w} != $w || $self->{h} != $h) { 361 if ($self->{alloc_w} != $w || $self->{alloc_h} != $h) {
362 return unless $self->{visible};
363
229 $self->{w} = $w; 364 $self->{alloc_w} = $w;
230 $self->{h} = $h; 365 $self->{alloc_h} = $h;
231 366
232 $self->size_allocate ($w, $h); 367 $self->{root}{size_alloc}{$self+0} = $self;
233 $self->update;
234 } 368 }
235} 369}
236 370
237sub size_allocate { 371sub size_allocate {
238 # nothing to be done 372 # nothing to be done
239} 373}
240 374
375sub children {
376 # nop
377}
378
379sub visible_children {
380 $_[0]->children
381}
382
241sub set_max_size { 383sub set_max_size {
242 my ($self, $w, $h) = @_; 384 my ($self, $w, $h) = @_;
243 385
244 delete $self->{max_w}; $self->{max_w} = $w if $w; 386 delete $self->{max_w}; $self->{max_w} = $w if $w;
245 delete $self->{max_h}; $self->{max_h} = $h if $h; 387 delete $self->{max_h}; $self->{max_h} = $h if $h;
246} 388}
247 389
248# return top left coordinates 390sub set_tooltip {
249sub _topleft {
250 my ($self, $x, $y) = @_; 391 my ($self, $tooltip) = @_;
251 392
252 $self->{parent} 393 $tooltip =~ s/^\s+//;
253 or Carp::confess "no parent widget in _topleft\n";#d# 394 $tooltip =~ s/\s+$//;
254 395
255 $self->{parent}->_topleft ($x + $self->{x}, $y + $self->{y}); 396 return if $self->{tooltip} eq $tooltip;
397
398 $self->{tooltip} = $tooltip;
399
400 if ($CFClient::UI::TOOLTIP->{owner} == $self) {
401 delete $CFClient::UI::TOOLTIP->{owner};
402 CFClient::UI::check_tooltip;
403 }
256} 404}
257 405
258# translate global coordinates to local coordinate system 406# translate global coordinates to local coordinate system
259sub coord2local { 407sub coord2local {
260 my ($self, $x, $y) = @_; 408 my ($self, $x, $y) = @_;
261 409
262 my ($X, $Y) = $self->_topleft; 410 $self->{parent}->coord2local ($x - $self->{x}, $y - $self->{y})
263 ($x - $X, $y - $Y)
264} 411}
265 412
266# translate local coordinates to global coordinate system 413# translate local coordinates to global coordinate system
267sub coord2global { 414sub coord2global {
268 my ($self, $x, $y) = @_; 415 my ($self, $x, $y) = @_;
269 416
270 my ($X, $Y) = $self->_topleft; 417 $self->{parent}->coord2global ($x + $self->{x}, $y + $self->{y})
271 ($x + $X, $y + $Y)
272} 418}
273 419
274sub focus_in { 420sub focus_in {
275 my ($self) = @_; 421 my ($self) = @_;
276 422
277 return if $FOCUS == $self; 423 return if $FOCUS == $self;
278 return unless $self->{can_focus}; 424 return unless $self->{can_focus};
279 425
280 my $focus = $FOCUS; $FOCUS = $self; 426 my $focus = $FOCUS; $FOCUS = $self;
281 427
282 $self->emit (focus_in => $focus); 428 $self->_emit (focus_in => $focus);
283 429
284 $focus->update if $focus; 430 $focus->update if $focus;
285 $FOCUS->update; 431 $FOCUS->update;
286} 432}
287 433
290 436
291 return unless $FOCUS == $self; 437 return unless $FOCUS == $self;
292 438
293 my $focus = $FOCUS; undef $FOCUS; 439 my $focus = $FOCUS; undef $FOCUS;
294 440
295 $self->emit (focus_out => $focus); 441 $self->_emit (focus_out => $focus);
296 442
297 $focus->update if $focus; #? 443 $focus->update if $focus; #?
298}
299 444
445 $::MAPWIDGET->focus_in #d# focus mapwidget if no other widget has focus
446 unless $FOCUS;
447}
448
300sub mouse_motion { } 449sub mouse_motion { 0 }
301sub button_up { } 450sub button_up { 0 }
302sub key_down { } 451sub key_down { 0 }
303sub key_up { } 452sub key_up { 0 }
304 453
305sub button_down { 454sub button_down {
306 my ($self, $ev, $x, $y) = @_; 455 my ($self, $ev, $x, $y) = @_;
307 456
308 $self->focus_in; 457 $self->focus_in;
309}
310 458
311sub w { $_[0]{w} = $_[1] if @_ > 1; $_[0]{w} } 459 0
312sub h { $_[0]{h} = $_[1] if @_ > 1; $_[0]{h} } 460}
313sub x { $_[0]{x} = $_[1] if @_ > 1; $_[0]{x} } 461
314sub y { $_[0]{y} = $_[1] if @_ > 1; $_[0]{y} } 462sub find_widget {
315sub z { $_[0]{z} = $_[1] if @_ > 1; $_[0]{z} } 463 my ($self, $x, $y) = @_;
464
465 return () unless $self->{can_events};
466
467 return $self
468 if $x >= $self->{x} && $x < $self->{x} + $self->{w}
469 && $y >= $self->{y} && $y < $self->{y} + $self->{h};
470
471 ()
472}
473
474sub set_parent {
475 my ($self, $parent) = @_;
476
477 Scalar::Util::weaken ($self->{parent} = $parent);
478 $self->set_visible if $parent->{visible};
479}
480
481sub connect {
482 my ($self, $signal, $cb) = @_;
483
484 push @{ $self->{signal_cb}{$signal} }, $cb;
485}
486
487sub _emit {
488 my ($self, $signal, @args) = @_;
489
490 List::Util::sum map $_->($self, @args), @{$self->{signal_cb}{$signal} || []}
491}
492
493sub emit {
494 my ($self, $signal, @args) = @_;
495
496 $self->_emit ($signal, @args)
497 || $self->$signal (@args);
498}
499
500sub visibility_change {
501 #my ($self, $visible) = @_;
502}
503
504sub realloc {
505 my ($self) = @_;
506
507 if ($self->{visible}) {
508 return if $self->{root}{realloc}{$self+0};
509
510 $self->{root}{realloc}{$self+0} = $self;
511 $self->{root}->update;
512 } else {
513 delete $self->{req_w};
514 delete $self->{req_h};
515 }
516}
517
518sub update {
519 my ($self) = @_;
520
521 $self->{parent}->update
522 if $self->{parent};
523}
524
525sub reconfigure {
526 my ($self) = @_;
527
528 $self->realloc;
529 $self->update;
530}
531
532# using global variables seems a bit hacky, but passing through all drawing
533# functions seems pointless.
534our ($draw_x, $draw_y, $draw_w, $draw_h); # screen rectangle being drawn
316 535
317sub draw { 536sub draw {
318 my ($self) = @_; 537 my ($self) = @_;
319 538
320 return unless $self->{h} && $self->{w}; 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 local $draw_w = $draw_x + $self->{w};
545 local $draw_h = $draw_y + $self->{h};
546
547 # skip widgets that are entirely outside the drawing area
548 return if ($draw_x + $self->{w} < 0) || ($draw_x >= $draw_w)
549 || ($draw_y + $self->{h} < 0) || ($draw_y >= $draw_h);
321 550
322 glPushMatrix; 551 glPushMatrix;
323 glTranslate $self->{x}, $self->{y}, 0; 552 glTranslate $self->{x}, $self->{y}, 0;
324 $self->_draw; 553 $self->_draw;
325 glPopMatrix; 554 glPopMatrix;
337 glVertex $x , $y + $self->{h}; 566 glVertex $x , $y + $self->{h};
338 glEnd; 567 glEnd;
339 glDisable GL_BLEND; 568 glDisable GL_BLEND;
340 } 569 }
341 570
342 if ($ENV{PCLIENT_DEBUG}) { 571 if ($ENV{CFPLUS_DEBUG} & 1) {
343 glPushMatrix; 572 glPushMatrix;
344 glColor 1, 1, 0, 1; 573 glColor 1, 1, 0, 1;
345 glTranslate $self->{x} + 0.375, $self->{y} + 0.375; 574 glTranslate $self->{x} + 0.375, $self->{y} + 0.375;
346 glBegin GL_LINE_LOOP; 575 glBegin GL_LINE_LOOP;
347 glVertex 0 , 0; 576 glVertex 0 , 0;
348 glVertex $self->{w}, 0; 577 glVertex $self->{w} - 1, 0;
349 glVertex $self->{w}, $self->{h}; 578 glVertex $self->{w} - 1, $self->{h} - 1;
350 glVertex 0 , $self->{h}; 579 glVertex 0 , $self->{h} - 1;
351 glEnd; 580 glEnd;
352 glPopMatrix; 581 glPopMatrix;
353 CFClient::UI::Label->new (w => $self->{w}, h => $self->{h}, text => $self, fontsize => 0)->_draw; 582 #CFClient::UI::Label->new (w => $self->{w}, h => $self->{h}, text => $self, fontsize => 0)->_draw;
354 } 583 }
355} 584}
356 585
357sub _draw { 586sub _draw {
358 my ($self) = @_; 587 my ($self) = @_;
359 588
360 warn "no draw defined for $self\n"; 589 warn "no draw defined for $self\n";
361} 590}
362 591
363sub find_widget {
364 my ($self, $x, $y) = @_;
365
366 return () unless $self->{can_events};
367
368 return $self
369 if $x >= $self->{x} && $x < $self->{x} + $self->{w}
370 && $y >= $self->{y} && $y < $self->{y} + $self->{h};
371
372 ()
373}
374
375sub set_parent {
376 my ($self, $parent) = @_;
377
378 Scalar::Util::weaken ($self->{parent} = $parent);
379}
380
381sub check_size {
382 my ($self) = @_;
383
384 $self->{parent}
385 or return 1;
386
387 my ($w, $h) = $self->{user_w} && $self->{user_h}
388 ? @$self{qw(user_w user_h)}
389 : $self->size_request;
390
391 if ($w != $self->{req_w} || $h != $self->{req_h}) {
392 $self->{req_w} = $w;
393 $self->{req_h} = $h;
394
395 $self->{parent}->check_size
396 or $self->size_allocate (
397 (List::Util::max $self->{w}, $w),
398 (List::Util::max $self->{h}, $h),
399 );
400
401 1
402 } else {
403 0
404 }
405}
406
407sub update {
408 my ($self) = @_;
409
410 $self->{parent}->update
411 if $self->{parent};
412}
413
414sub connect {
415 my ($self, $signal, $cb) = @_;
416
417 push @{ $self->{signal_cb}{$signal} }, $cb;
418}
419
420sub emit {
421 my ($self, $signal, @args) = @_;
422
423 List::Util::sum map $_->($self, @args), @{$self->{signal_cb}{$signal} || []}
424}
425
426sub DESTROY { 592sub DESTROY {
427 my ($self) = @_; 593 my ($self) = @_;
428 594
595 delete $WIDGET{$self+0};
429 #$self->deactivate; 596 #$self->deactivate;
430} 597}
431 598
432############################################################################# 599#############################################################################
433 600
442 my $class = shift; 609 my $class = shift;
443 610
444 # range [value, low, high, page] 611 # range [value, low, high, page]
445 612
446 $class->SUPER::new ( 613 $class->SUPER::new (
447 bg => [0, 0, 0, 0.2], 614 #bg => [0, 0, 0, 0.2],
448 active_bg => [1, 1, 1, 0.5], 615 #active_bg => [1, 1, 1, 0.5],
449 @_ 616 @_
450 ) 617 )
451} 618}
452 619
453sub _draw { 620sub _draw {
454 my ($self) = @_; 621 my ($self) = @_;
455 622
623 my $color = $FOCUS == $self && $self->{active_bg}
624 ? $self->{active_bg}
625 : $self->{bg};
626
627 if ($color && (@$color < 4 || $color->[3])) {
456 my ($w, $h) = @$self{qw(w h)}; 628 my ($w, $h) = @$self{qw(w h)};
457 629
458 glEnable GL_BLEND; 630 glEnable GL_BLEND;
459 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 631 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
460 glColor @{ $FOCUS == $self ? $self->{active_bg} : $self->{bg} }; 632 glColor @$color;
461 633
462 glBegin GL_QUADS; 634 glBegin GL_QUADS;
463 glVertex 0 , 0; 635 glVertex 0 , 0;
464 glVertex 0 , $h; 636 glVertex 0 , $h;
465 glVertex $w, $h; 637 glVertex $w, $h;
466 glVertex $w, 0; 638 glVertex $w, 0;
467 glEnd; 639 glEnd;
468 640
469 glDisable GL_BLEND; 641 glDisable GL_BLEND;
642 }
470} 643}
471 644
472############################################################################# 645#############################################################################
473 646
474package CFClient::UI::Empty; 647package CFClient::UI::Empty;
479 my ($class, %arg) = @_; 652 my ($class, %arg) = @_;
480 $class->SUPER::new (can_events => 0, %arg); 653 $class->SUPER::new (can_events => 0, %arg);
481} 654}
482 655
483sub size_request { 656sub size_request {
484 (0, 0) 657 my ($self) = @_;
658
659 ($self->{w} + 0, $self->{h} + 0)
485} 660}
486 661
487sub draw { } 662sub draw { }
488 663
489############################################################################# 664#############################################################################
493our @ISA = CFClient::UI::Base::; 668our @ISA = CFClient::UI::Base::;
494 669
495sub new { 670sub new {
496 my ($class, %arg) = @_; 671 my ($class, %arg) = @_;
497 672
498 my $children = delete $arg{children} || []; 673 my $children = delete $arg{children};
499 674
500 my $self = $class->SUPER::new ( 675 my $self = $class->SUPER::new (
501 children => [], 676 children => [],
502 can_events => 0, 677 can_events => 0,
503 %arg, 678 %arg,
504 ); 679 );
680
505 $self->add ($_) for @$children; 681 $self->add (@$children)
682 if $children;
506 683
507 $self 684 $self
508} 685}
509 686
510sub add { 687sub add {
511 my ($self, $child) = @_; 688 my ($self, @widgets) = @_;
512 689
513 $child->set_parent ($self); 690 $_->set_parent ($self)
691 for @widgets;
514 692
515 use sort 'stable'; 693 use sort 'stable';
516 694
517 $self->{children} = [ 695 $self->{children} = [
518 sort { $a->{z} <=> $b->{z} } 696 sort { $a->{z} <=> $b->{z} }
519 @{$self->{children}}, $child 697 @{$self->{children}}, @widgets
520 ]; 698 ];
521 699
522 $child->check_size; 700 $self->realloc;
701}
702
703sub children {
704 @{ $_[0]{children} }
523} 705}
524 706
525sub remove { 707sub remove {
526 my ($self, $child) = @_; 708 my ($self, $child) = @_;
527 709
528 delete $child->{parent}; 710 delete $child->{parent};
529 $child->hide; 711 $child->hide;
530 712
531 $self->{children} = [ grep $_ != $child, @{ $self->{children} } ]; 713 $self->{children} = [ grep $_ != $child, @{ $self->{children} } ];
532 714
533 $self->check_size; 715 $self->realloc;
534 $self->update;
535} 716}
536 717
537sub clear { 718sub clear {
538 my ($self) = @_; 719 my ($self) = @_;
539 720
542 723
543 for (@$children) { 724 for (@$children) {
544 delete $_->{parent}; 725 delete $_->{parent};
545 $_->hide; 726 $_->hide;
546 } 727 }
728
729 $self->realloc;
547} 730}
548 731
549sub find_widget { 732sub find_widget {
550 my ($self, $x, $y) = @_; 733 my ($self, $x, $y) = @_;
551 734
552 $x -= $self->{x}; 735 $x -= $self->{x};
553 $y -= $self->{y}; 736 $y -= $self->{y};
554 737
555 my $res; 738 my $res;
556 739
557 for (reverse @{ $self->{children} }) { 740 for (reverse $self->visible_children) {
558 $res = $_->find_widget ($x, $y) 741 $res = $_->find_widget ($x, $y)
559 and return $res; 742 and return $res;
560 } 743 }
561 744
562 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y}) 745 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
626} 809}
627 810
628sub update { 811sub update {
629 my ($self) = @_; 812 my ($self) = @_;
630 813
631 # we want to do this delayed... 814 $ROOT->on_post_alloc ($self => sub { $self->render_child });
632 $self->render_chld;
633 $self->SUPER::update; 815 $self->SUPER::update;
634}
635
636sub render_chld {
637 my ($self) = @_;
638
639 $self->{texture} = new_from_opengl CFClient::Texture $self->{w}, $self->{h}, sub {
640 glClearColor 0, 0, 0, 1;
641 glClear GL_COLOR_BUFFER_BIT;
642 $self->child->draw;
643 };
644} 816}
645 817
646sub size_allocate { 818sub size_allocate {
647 my ($self, $w, $h) = @_; 819 my ($self, $w, $h) = @_;
648 820
649 $self->child->configure (0, 0, $w, $h); 821 $self->SUPER::size_allocate ($w, $h);
822 $self->update;
823}
650 824
825sub _render {
826 my ($self) = @_;
827
828 $self->{children}[0]->draw;
829}
830
831sub render_child {
832 my ($self) = @_;
833
834 $self->{texture} = new_from_opengl CFClient::Texture $self->{w}, $self->{h}, sub {
835 glClearColor 0, 0, 0, 0;
836 glClear GL_COLOR_BUFFER_BIT;
837
838 {
839 package CFClient::UI::Base;
840
841 ($draw_x, $draw_y, $draw_w, $draw_h) =
842 (0, 0, $self->{w}, $self->{h});
843 }
844
651 $self->render_chld; 845 $self->_render;
846 };
652} 847}
653 848
654sub _draw { 849sub _draw {
655 my ($self) = @_; 850 my ($self) = @_;
656 851
657 my ($w, $h) = ($self->w, $self->h); 852 my ($w, $h) = @$self{qw(w h)};
658 853
659 my $tex = $self->{texture} 854 my $tex = $self->{texture}
660 or return; 855 or return;
661 856
662 glEnable GL_BLEND;
663 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
664 glEnable GL_TEXTURE_2D; 857 glEnable GL_TEXTURE_2D;
665 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 858 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
859 glColor 1, 1, 1, 1;
666 860
667 $tex->draw_quad (0, 0, $w, $h); 861 $tex->draw_quad_alpha_premultiplied (0, 0, $w, $h);
668 862
669 glDisable GL_BLEND;
670 glDisable GL_TEXTURE_2D; 863 glDisable GL_TEXTURE_2D;
671} 864}
672 865
673############################################################################# 866#############################################################################
674 867
675package CFClient::UI::ViewPort; 868package CFClient::UI::ViewPort;
676 869
677our @ISA = CFClient::UI::Window::; 870our @ISA = CFClient::UI::Window::;
678
679sub new { die }
680
681sub size_request {
682 my ($self) = @_;
683
684 @$self{qw(child_w child_h)} = @{$self->child}{qw(req_w req_h)};
685 $self->child->size_allocate (0, 0, @$self{qw(child_w child_h)});
686
687 @$self{qw(child_w child_h)}
688}
689
690sub _draw {
691 my ($self) = @_;
692
693 $self->{children}[1]->draw;
694}
695
696
697#############################################################################
698
699package CFClient::UI::Frame;
700
701our @ISA = CFClient::UI::Bin::;
702
703use CFClient::OpenGL;
704 871
705sub new { 872sub new {
706 my $class = shift; 873 my $class = shift;
707 874
875 $class->SUPER::new (
876 scroll_x => 0,
877 scroll_y => 1,
878 @_,
879 )
880}
881
882sub size_request {
883 my ($self) = @_;
884
885 my ($w, $h) = @{$self->child}{qw(req_w req_h)};
886
887 $w = 10 if $self->{scroll_x};
888 $h = 10 if $self->{scroll_y};
889
890 ($w, $h)
891}
892
893sub size_allocate {
894 my ($self, $w, $h) = @_;
895
896 my $child = $self->child;
897
898 $w = $child->{req_w} if $self->{scroll_x} && $child->{req_w};
899 $h = $child->{req_h} if $self->{scroll_y} && $child->{req_h};
900
901 $self->child->configure (0, 0, $w, $h);
902 $self->update;
903}
904
905sub set_offset {
906 my ($self, $x, $y) = @_;
907
908 $self->{view_x} = int $x;
909 $self->{view_y} = int $y;
910
911 $self->update;
912}
913
914# hmm, this does not work for topleft of $self... but we should not ask for that
915sub coord2local {
916 my ($self, $x, $y) = @_;
917
918 $self->SUPER::coord2local ($x + $self->{view_x}, $y + $self->{view_y})
919}
920
921sub coord2global {
922 my ($self, $x, $y) = @_;
923
924 $x = List::Util::min $self->{w}, $x - $self->{view_x};
925 $y = List::Util::min $self->{h}, $y - $self->{view_y};
926
927 $self->SUPER::coord2global ($x, $y)
928}
929
930sub find_widget {
931 my ($self, $x, $y) = @_;
932
933 if ( $x >= $self->{x} && $x < $self->{x} + $self->{w}
934 && $y >= $self->{y} && $y < $self->{y} + $self->{h}
935 ) {
936 $self->child->find_widget ($x + $self->{view_x}, $y + $self->{view_y})
937 } else {
938 $self->CFClient::UI::Base::find_widget ($x, $y)
939 }
940}
941
942sub _render {
943 my ($self) = @_;
944
945 local $CFClient::UI::Base::draw_x = $CFClient::UI::Base::draw_x - $self->{view_x};
946 local $CFClient::UI::Base::draw_y = $CFClient::UI::Base::draw_y - $self->{view_y};
947
948 CFClient::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y};
949
950 $self->SUPER::_render;
951}
952
953#############################################################################
954
955package CFClient::UI::ScrolledWindow;
956
957our @ISA = CFClient::UI::HBox::;
958
959sub new {
960 my ($class, %arg) = @_;
961
962 my $child = delete $arg{child};
963
964 my $self;
965
966 my $slider = new CFClient::UI::Slider
967 vertical => 1,
968 range => [0, 0, 1, 0.01], # HACK fix
969 on_changed => sub {
970 $self->{vp}->set_offset (0, $_[1]);
971 },
972 ;
973
708 my $self = $class->SUPER::new ( 974 $self = $class->SUPER::new (
709 bg => [1, 1, 1, 1], 975 vp => (new CFClient::UI::ViewPort expand => 1),
710 border_bg => [1, 1, 1, 1], 976 slider => $slider,
711 border => 0.8, 977 %arg,
712 @_
713 ); 978 );
714 979
980 $self->SUPER::add ($self->{vp}, $self->{slider});
981 $self->add ($child) if $child;
982
715 $self 983 $self
716} 984}
717 985
718sub _draw { 986sub add {
987 my ($self, $widget) = @_;
988
989 $self->{vp}->add ($self->{child} = $widget);
990}
991
992sub update {
993 my ($self) = @_;
994
995 $self->SUPER::update;
996
997 # todo: overwrite size_allocate of child
998 my $child = $self->{vp}->child;
999 $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $child->{h}, $self->{vp}{h}, 1]);
1000}
1001
1002sub size_allocate {
719 my ($self) = @_; 1003 my ($self, $w, $h) = @_;
720 1004
721 my ($w, $h) = ($self->{w}, $self->{h}); 1005 $self->SUPER::size_allocate ($w, $h);
722 1006
723 glEnable GL_BLEND; 1007 my $child = $self->{vp}->child;
724 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 1008 $self->{slider}->set_range ([$self->{slider}{range}[0], 0, $child->{h}, $self->{vp}{h}, 1]);
725 glEnable GL_TEXTURE_2D;
726 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
727
728# glBegin GL_QUADS;
729# glColor 0, 0, 0, 0;
730# glVertex 0 , 0;
731# glVertex 0 , $h;
732# glVertex $w, $h;
733# glVertex $w, 0;
734# glEnd;
735
736
737 $self->child->draw;
738 glDisable GL_BLEND;
739 glDisable GL_TEXTURE_2D;
740} 1009}
1010
1011#TODO# update range on size_allocate depending on child
1012# update viewport offset on scroll
741 1013
742############################################################################# 1014#############################################################################
743 1015
744package CFClient::UI::FancyFrame; 1016package CFClient::UI::Frame;
745 1017
746our @ISA = CFClient::UI::Bin::; 1018our @ISA = CFClient::UI::Bin::;
747 1019
748use CFClient::OpenGL; 1020use CFClient::OpenGL;
749
750my @tex =
751 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
752 qw(d1_bg.png d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png);
753 1021
754sub new { 1022sub new {
755 my $class = shift; 1023 my $class = shift;
756 1024
757 # TODO: user_x, user_y, overwrite moveto? 1025 $class->SUPER::new (
1026 bg => undef,
1027 @_,
1028 )
1029}
1030
1031sub _draw {
1032 my ($self) = @_;
1033
1034 if ($self->{bg}) {
1035 my ($w, $h) = @$self{qw(w h)};
1036
1037 glEnable GL_BLEND;
1038 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1039 glColor @{ $self->{bg} };
1040
1041 glBegin GL_QUADS;
1042 glVertex 0 , 0;
1043 glVertex 0 , $h;
1044 glVertex $w, $h;
1045 glVertex $w, 0;
1046 glEnd;
1047
1048 glDisable GL_BLEND;
1049 }
1050
1051 $self->SUPER::_draw;
1052}
1053
1054#############################################################################
1055
1056package CFClient::UI::FancyFrame;
1057
1058our @ISA = CFClient::UI::Bin::;
1059
1060use CFClient::OpenGL;
1061
1062my $bg =
1063 new_from_file CFClient::Texture CFClient::find_rcfile "d1_bg.png",
1064 mipmap => 1, wrap => 1;
1065
1066my @border =
1067 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1068 qw(d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png);
1069
1070sub new {
1071 my ($class, %arg) = @_;
1072
1073 my $title = delete $arg{title};
758 1074
759 my $self = $class->SUPER::new ( 1075 my $self = $class->SUPER::new (
760 bg => [1, 1, 1, 1], 1076 bg => [1, 1, 1, 1],
761 border_bg => [1, 1, 1, 1], 1077 border_bg => [1, 1, 1, 1],
762 border => 0.8, 1078 border => 0.6,
763 can_events => 1, 1079 can_events => 1,
764 @_ 1080 min_w => 16,
1081 min_h => 16,
1082 %arg,
765 ); 1083 );
766 1084
767 $self->{title} &&= new CFClient::UI::Label 1085 $self->{title} = new CFClient::UI::Label
768 align => 0, 1086 align => 0,
769 valign => 1, 1087 valign => 1,
770 text => $self->{title}, 1088 text => $title,
771 fontsize => 1; 1089 fontsize => $self->{border}
1090 if defined $title;
772 1091
773 $self 1092 $self
1093}
1094
1095sub add {
1096 my ($self, @widgets) = @_;
1097
1098 $self->SUPER::add (@widgets);
1099 $self->CFClient::UI::Container::add ($self->{title}) if $self->{title};
774} 1100}
775 1101
776sub border { 1102sub border {
777 int $_[0]{border} * $::FONTSIZE 1103 int $_[0]{border} * $::FONTSIZE
778} 1104}
779 1105
780sub size_request { 1106sub size_request {
781 my ($self) = @_; 1107 my ($self) = @_;
1108
1109 $self->{title}->size_request
1110 if $self->{title};
782 1111
783 my ($w, $h) = $self->SUPER::size_request; 1112 my ($w, $h) = $self->SUPER::size_request;
784 1113
785 ( 1114 (
786 $w + $self->border * 2, 1115 $w + $self->border * 2,
789} 1118}
790 1119
791sub size_allocate { 1120sub size_allocate {
792 my ($self, $w, $h) = @_; 1121 my ($self, $w, $h) = @_;
793 1122
1123 if ($self->{title}) {
1124 $self->{title}{w} = $w;
1125 $self->{title}{h} = $h;
1126 $self->{title}->size_allocate ($w, $h);
1127 }
1128
1129 my $border = $self->border;
1130
794 $h -= List::Util::max 0, $self->border * 2; 1131 $h -= List::Util::max 0, $border * 2;
795 $w -= List::Util::max 0, $self->border * 2; 1132 $w -= List::Util::max 0, $border * 2;
796 1133
797 $self->{title}->configure ($self->border, $self->border - $::FONTSIZE * 2, $w, $::FONTSIZE * 2)
798 if $self->{title};
799
800 $self->child->configure ($self->border, $self->border, $w, $h); 1134 $self->child->configure ($border, $border, $w, $h);
801} 1135}
802 1136
803sub button_down { 1137sub button_down {
804 my ($self, $ev, $x, $y) = @_; 1138 my ($self, $ev, $x, $y) = @_;
805 1139
1140 my ($w, $h) = @$self{qw(w h)};
806 my $border = $self->border; 1141 my $border = $self->border;
807 1142
808 if ($x < $self->{w} && $x >= $self->{w} - $border 1143 my $lr = ($x >= 0 && $x < $border) || ($x > $w - $border && $x < $w);
809 && $y < $self->{h} && $y >= $self->{h} - $border) { 1144 my $td = ($y >= 0 && $y < $border) || ($y > $h - $border && $y < $h);
810 1145
1146 if ($lr & $td) {
1147 my ($wx, $wy) = ($self->{x}, $self->{y});
811 my ($ox, $oy) = ($ev->{x}, $ev->{y}); 1148 my ($ox, $oy) = ($ev->{x}, $ev->{y});
812 my ($bw, $bh) = ($self->{w}, $self->{h}); 1149 my ($bw, $bh) = ($self->{w}, $self->{h});
813 1150
1151 my $mx = $x < $border;
1152 my $my = $y < $border;
1153
814 $self->{motion} = sub { 1154 $self->{motion} = sub {
815 my ($ev, $x, $y) = @_; 1155 my ($ev, $x, $y) = @_;
816 1156
817 ($x, $y) = ($ev->{x}, $ev->{y}); 1157 my $dx = $ev->{x} - $ox;
1158 my $dy = $ev->{y} - $oy;
818 1159
819 $self->{user_w} = $bw + $x - $ox; 1160 $self->{force_w} = $bw + $dx * ($mx ? -1 : 1);
820 $self->{user_h} = $bh + $y - $oy; 1161 $self->{force_h} = $bh + $dy * ($my ? -1 : 1);
821 $self->check_size; 1162
1163 $self->realloc;
1164 $self->move_abs ($wx + $dx * $mx, $wy + $dy * $my);
822 }; 1165 };
823 1166
824 } elsif ($x >= 0 && $x < $self->{w} 1167 } elsif ($lr ^ $td) {
825 && $y >= 0 && $y < $border) {
826
827 my ($ox, $oy) = ($ev->{x}, $ev->{y}); 1168 my ($ox, $oy) = ($ev->{x}, $ev->{y});
828 my ($bx, $by) = ($self->{x}, $self->{y}); 1169 my ($bx, $by) = ($self->{x}, $self->{y});
829 1170
830 $self->{motion} = sub { 1171 $self->{motion} = sub {
831 my ($ev, $x, $y) = @_; 1172 my ($ev, $x, $y) = @_;
832 1173
833 ($x, $y) = ($ev->{x}, $ev->{y}); 1174 ($x, $y) = ($ev->{x}, $ev->{y});
834 1175
835 $self->move ($bx + $x - $ox, $by + $y - $oy); 1176 $self->move_abs ($bx + $x - $ox, $by + $y - $oy);
836 $self->update;
837 }; 1177 };
1178 } else {
1179 return 0;
1180 }
1181
838 } 1182 1
839} 1183}
840 1184
841sub button_up { 1185sub button_up {
842 my ($self, $ev, $x, $y) = @_; 1186 my ($self, $ev, $x, $y) = @_;
843 1187
844 delete $self->{motion}; 1188 !!delete $self->{motion}
845} 1189}
846 1190
847sub mouse_motion { 1191sub mouse_motion {
848 my ($self, $ev, $x, $y) = @_; 1192 my ($self, $ev, $x, $y) = @_;
849 1193
850 $self->{motion}->($ev, $x, $y) if $self->{motion}; 1194 $self->{motion}->($ev, $x, $y) if $self->{motion};
1195
1196 !!$self->{motion}
851} 1197}
852 1198
853sub _draw { 1199sub _draw {
854 my ($self) = @_; 1200 my ($self) = @_;
855 1201
1202 my $child = $self->{children}[0];
1203
856 my ($w, $h ) = ($self->{w}, $self->{h}); 1204 my ($w, $h ) = ($self->{w}, $self->{h});
857 my ($cw, $ch) = ($self->child->{w}, $self->child->{h}); 1205 my ($cw, $ch) = ($child->{w}, $child->{h});
858 1206
859 glEnable GL_BLEND;
860 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
861 glEnable GL_TEXTURE_2D; 1207 glEnable GL_TEXTURE_2D;
862 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE; 1208 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
863 1209
864 my $border = $self->border; 1210 my $border = $self->border;
865 1211
866 glColor @{ $self->{border_bg} }; 1212 glColor @{ $self->{border_bg} };
867 $tex[1]->draw_quad (0, 0, $w, $border); 1213 $border[0]->draw_quad_alpha (0, 0, $w, $border);
868 $tex[3]->draw_quad (0, $border, $border, $ch); 1214 $border[1]->draw_quad_alpha (0, $border, $border, $ch);
869 $tex[2]->draw_quad ($w - $border, $border, $border, $ch); 1215 $border[2]->draw_quad_alpha ($w - $border, $border, $border, $ch);
870 $tex[4]->draw_quad (0, $h - $border, $w, $border); 1216 $border[3]->draw_quad_alpha (0, $h - $border, $w, $border);
871 1217
872 my $bg = $tex[0]; 1218 if (@{$self->{bg}} < 4 || $self->{bg}[3]) {
1219 glColor @{ $self->{bg} };
873 1220
874 # TODO: repeat texture not scale 1221 # TODO: repeat texture not scale
875 my $rep_x = $cw / $bg->{w}; 1222 # solve this better(?)
876 my $rep_y = $ch / $bg->{h}; 1223 $bg->{s} = $cw / $bg->{w};
877 1224 $bg->{t} = $ch / $bg->{h};
878 glColor @{ $self->{bg} };
879
880 $bg->{s} = $rep_x;
881 $bg->{t} = $rep_y;
882 $bg->{wrap_mode} = 1;
883 $bg->draw_quad ($border, $border, $cw, $ch); 1225 $bg->draw_quad_alpha ($border, $border, $cw, $ch);
1226 }
884 1227
885 glDisable GL_TEXTURE_2D; 1228 glDisable GL_TEXTURE_2D;
886 glDisable GL_BLEND;
887 1229
888 $self->{title}->draw if $self->{title};
889 $self->child->draw; 1230 $child->draw;
1231
1232 if ($self->{title}) {
1233 glTranslate 0, $border - $self->{h};
1234 $self->{title}->_draw;
1235 }
890} 1236}
891 1237
892############################################################################# 1238#############################################################################
893 1239
894package CFClient::UI::Table; 1240package CFClient::UI::Table;
902sub new { 1248sub new {
903 my $class = shift; 1249 my $class = shift;
904 1250
905 $class->SUPER::new ( 1251 $class->SUPER::new (
906 col_expand => [], 1252 col_expand => [],
907 @_ 1253 @_,
908 ) 1254 )
1255}
1256
1257sub children {
1258 grep $_, map @$_, grep $_, @{ $_[0]{children} }
909} 1259}
910 1260
911sub add { 1261sub add {
912 my ($self, $x, $y, $child) = @_; 1262 my ($self, $x, $y, $child) = @_;
913 1263
914 $child->set_parent ($self); 1264 $child->set_parent ($self);
915 $self->{children}[$y][$x] = $child; 1265 $self->{children}[$y][$x] = $child;
916 1266
917 $child->check_size; 1267 $self->realloc;
918} 1268}
919 1269
920# TODO: move to container class maybe? send childs a signal on removal? 1270# TODO: move to container class maybe? send children a signal on removal?
921sub clear { 1271sub clear {
922 my ($self) = @_; 1272 my ($self) = @_;
923 1273
924 my $children = delete $self->{children}; 1274 my @children = $self->children;
1275 delete $self->{children};
925 1276
926 for (grep $_, map @$_, grep $_, @$children) { 1277 for (@children) {
927 delete $_->{parent}; 1278 delete $_->{parent};
928 $_->hide; 1279 $_->hide;
929 } 1280 }
930 1281
931 $self->update; 1282 $self->realloc;
932} 1283}
933 1284
934sub get_wh { 1285sub get_wh {
935 my ($self) = @_; 1286 my ($self) = @_;
936 1287
967sub size_allocate { 1318sub size_allocate {
968 my ($self, $w, $h) = @_; 1319 my ($self, $w, $h) = @_;
969 1320
970 my ($ws, $hs) = $self->get_wh; 1321 my ($ws, $hs) = $self->get_wh;
971 1322
972 my $req_w = sum @$ws; 1323 my $req_w = (sum @$ws) || 1;
973 my $req_h = sum @$hs; 1324 my $req_h = (sum @$hs) || 1;
974 1325
975 # TODO: nicer code && do row_expand 1326 # TODO: nicer code && do row_expand
976 my @col_expand = @{$self->{col_expand}}; 1327 my @col_expand = @{$self->{col_expand}};
977 @col_expand = (1) x @$ws unless @col_expand; 1328 @col_expand = (1) x @$ws unless @col_expand;
978 my $col_expand = (sum @col_expand) || 1; 1329 my $col_expand = (sum @col_expand) || 1;
1032 } 1383 }
1033} 1384}
1034 1385
1035############################################################################# 1386#############################################################################
1036 1387
1037package CFClient::UI::HBox; 1388package CFClient::UI::Box;
1038
1039# TODO: wrap into common Box base class
1040 1389
1041our @ISA = CFClient::UI::Container::; 1390our @ISA = CFClient::UI::Container::;
1042 1391
1043sub size_request { 1392sub size_request {
1044 my ($self) = @_; 1393 my ($self) = @_;
1045 1394
1046 my @alloc = map [$_->size_request], @{$self->{children}}; 1395 $self->{vertical}
1047 1396 ? (
1048 ( 1397 (List::Util::max map $_->{req_w}, @{$self->{children}}),
1049 (List::Util::sum map $_->[0], @alloc), 1398 (List::Util::sum map $_->{req_h}, @{$self->{children}}),
1050 (List::Util::max map $_->[1], @alloc), 1399 )
1051 ) 1400 : (
1401 (List::Util::sum map $_->{req_w}, @{$self->{children}}),
1402 (List::Util::max map $_->{req_h}, @{$self->{children}}),
1403 )
1052} 1404}
1053 1405
1054sub size_allocate { 1406sub size_allocate {
1055 my ($self, $w, $h) = @_; 1407 my ($self, $w, $h) = @_;
1056 1408
1057 ($h, $w) = ($w, $h); 1409 my $space = $self->{vertical} ? $h : $w;
1058
1059 my $children = $self->{children}; 1410 my $children = $self->{children};
1060 1411
1061 my @h = map $_->{req_w}, @$children; 1412 my @req;
1062 1413
1063 my $req_h = List::Util::sum @h; 1414 if ($self->{homogeneous}) {
1064 1415 @req = ($space / (@$children || 1)) x @$children;
1065 if ($req_h > $h) {
1066 # ah well, not enough space
1067 $_ *= $h / $req_h for @h;
1068 } else { 1416 } else {
1417 @req = map $_->{$self->{vertical} ? "req_h" : "req_w"}, @$children;
1418 my $req = List::Util::sum @req;
1419
1420 if ($req > $space) {
1421 # ah well, not enough space
1422 $_ *= $space / $req for @req;
1423 } else {
1069 my $exp = List::Util::sum map $_->{expand}, @$children; 1424 my $expand = (List::Util::sum map $_->{expand}, @$children) || 1;
1070 $exp ||= 1;
1071 1425
1426 $space = ($space - $req) / $expand; # remaining space to give away
1427
1428 $req[$_] += $space * $children->[$_]{expand}
1072 for (0 .. $#$children) { 1429 for 0 .. $#$children;
1073 my $child = $children->[$_];
1074
1075 my $alloc_h = $h[$_];
1076 $alloc_h += ($h - $req_h) * $child->{expand} / $exp;
1077 $h[$_] = $alloc_h;
1078 } 1430 }
1079 } 1431 }
1080 1432
1081 CFClient::UI::harmonize \@h; 1433 CFClient::UI::harmonize \@req;
1082 1434
1083 my $y = 0; 1435 my $pos = 0;
1084 for (0 .. $#$children) { 1436 for (0 .. $#$children) {
1085 my $child = $children->[$_];
1086 my $h = $h[$_]; 1437 my $alloc = $req[$_];
1087 $child->configure ($y, 0, $h, $w); 1438 $children->[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h));
1088 1439
1089 $y += $h; 1440 $pos += $alloc;
1090 } 1441 }
1091 1442
1092 1 1443 1
1093} 1444}
1094 1445
1095############################################################################# 1446#############################################################################
1096 1447
1448package CFClient::UI::HBox;
1449
1450our @ISA = CFClient::UI::Box::;
1451
1452sub new {
1453 my $class = shift;
1454
1455 $class->SUPER::new (
1456 vertical => 0,
1457 @_,
1458 )
1459}
1460
1461#############################################################################
1462
1097package CFClient::UI::VBox; 1463package CFClient::UI::VBox;
1098 1464
1099# TODO: wrap into common Box base class
1100
1101our @ISA = CFClient::UI::Container::; 1465our @ISA = CFClient::UI::Box::;
1102 1466
1103sub size_request { 1467sub new {
1104 my ($self) = @_; 1468 my $class = shift;
1105 1469
1106 my @alloc = map [$_->size_request], @{$self->{children}}; 1470 $class->SUPER::new (
1107 1471 vertical => 1,
1108 ( 1472 @_,
1109 (List::Util::max map $_->[0], @alloc),
1110 (List::Util::sum map $_->[1], @alloc),
1111 ) 1473 )
1112} 1474}
1113 1475
1114sub size_allocate {
1115 my ($self, $w, $h) = @_;
1116
1117 my $children = $self->{children};
1118
1119 my @h = map $_->{req_h}, @$children;
1120
1121 my $req_h = List::Util::sum @h;
1122
1123 if ($req_h > $h) {
1124 # ah well, not enough space
1125 $_ *= $h / $req_h for @h;
1126 } else {
1127 my $exp = List::Util::sum map $_->{expand}, @$children;
1128 $exp ||= 1;
1129
1130 for (0 .. $#$children) {
1131 my $child = $children->[$_];
1132
1133 $h[$_] += ($h - $req_h) * $child->{expand} / $exp;
1134 }
1135 }
1136
1137 CFClient::UI::harmonize \@h;
1138
1139 my $y = 0;
1140 for (0 .. $#$children) {
1141 my $child = $children->[$_];
1142 my $h = $h[$_];
1143 $child->configure (0, $y, $w, $h);
1144
1145 $y += $h;
1146 }
1147
1148 1
1149}
1150
1151############################################################################# 1476#############################################################################
1152 1477
1153package CFClient::UI::Label; 1478package CFClient::UI::Label;
1154 1479
1155our @ISA = CFClient::UI::Base::; 1480our @ISA = CFClient::UI::DrawBG::;
1156 1481
1157use CFClient::OpenGL; 1482use CFClient::OpenGL;
1158 1483
1159sub new { 1484sub new {
1160 my ($class, %arg) = @_; 1485 my ($class, %arg) = @_;
1161 1486
1162 my $self = $class->SUPER::new ( 1487 my $self = $class->SUPER::new (
1163 fg => [1, 1, 1], 1488 fg => [1, 1, 1],
1489 #bg => none
1490 #active_bg => none
1164 #font => default_font 1491 #font => default_font
1492 #text => initial text
1493 #markup => initial narkup
1494 #max_w => maximum pixel width
1495 ellipsise => 3, # end
1496 layout => (new CFClient::Layout),
1165 fontsize => 1, 1497 fontsize => 1,
1166 text => "",
1167 align => -1, 1498 align => -1,
1168 valign => -1, 1499 valign => -1,
1169 padding => 2, 1500 padding_x => 2,
1170 layout => new CFClient::Layout, 1501 padding_y => 2,
1171 can_events => 0, 1502 can_events => 0,
1172 %arg 1503 %arg
1173 ); 1504 );
1174 1505
1175 if (exists $self->{template}) { 1506 if (exists $self->{template}) {
1176 my $layout = new CFClient::Layout; 1507 my $layout = new CFClient::Layout;
1177 $layout->set_text (delete $self->{template}); 1508 $layout->set_text (delete $self->{template});
1178 $self->{template} = $layout; 1509 $self->{template} = $layout;
1179 } 1510 }
1180 1511
1181 $self->set_text (delete $self->{text}) if exists $self->{text}; 1512 if (exists $self->{markup}) {
1182 $self->set_markup (delete $self->{markup}) if exists $self->{markup}; 1513 $self->set_markup (delete $self->{markup});
1514 } else {
1515 $self->set_text (delete $self->{text});
1516 }
1183 1517
1184 $self 1518 $self
1185} 1519}
1186 1520
1187sub escape { 1521sub escape($) {
1188 local $_ = $_[1]; 1522 local $_ = $_[0];
1189 1523
1190 s/&/&amp;/g; 1524 s/&/&amp;/g;
1191 s/>/&gt;/g; 1525 s/>/&gt;/g;
1192 s/</&lt;/g; 1526 s/</&lt;/g;
1193 1527
1194 $_[1] 1528 $_
1529}
1530
1531sub update {
1532 my ($self) = @_;
1533
1534 delete $self->{texture};
1535 $self->SUPER::update;
1195} 1536}
1196 1537
1197sub set_text { 1538sub set_text {
1198 my ($self, $text) = @_; 1539 my ($self, $text) = @_;
1199 1540
1541 return if $self->{text} eq "T$text";
1542 $self->{text} = "T$text";
1543
1544 $self->{layout} = new CFClient::Layout if $self->{layout}->is_rgba;
1200 $self->{layout}->set_text ($text); 1545 $self->{layout}->set_text ($text);
1201 1546
1202 delete $self->{texture}; 1547 $self->realloc;
1203 $self->check_size;
1204 $self->update; 1548 $self->update;
1205} 1549}
1206 1550
1207sub set_markup { 1551sub set_markup {
1208 my ($self, $markup) = @_; 1552 my ($self, $markup) = @_;
1209 1553
1554 return if $self->{text} eq "M$markup";
1555 $self->{text} = "M$markup";
1556
1557 my $rgba = $markup =~ /span.*(?:foreground|background)/;
1558
1559 $self->{layout} = new CFClient::Layout $rgba if $self->{layout}->is_rgba != $rgba;
1210 $self->{layout}->set_markup ($markup); 1560 $self->{layout}->set_markup ($markup);
1211 1561
1212 delete $self->{texture}; 1562 $self->realloc;
1213 $self->check_size;
1214 $self->update; 1563 $self->update;
1215} 1564}
1216 1565
1217sub size_request { 1566sub size_request {
1218 my ($self) = @_; 1567 my ($self) = @_;
1219 1568
1220 $self->{layout}->set_font ($self->{font}) if $self->{font}; 1569 $self->{layout}->set_font ($self->{font}) if $self->{font};
1221 $self->{layout}->set_width ($self->{max_w} || -1); 1570 $self->{layout}->set_width ($self->{max_w} || -1);
1571 $self->{layout}->set_ellipsise ($self->{ellipsise});
1572 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1222 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE); 1573 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1223 1574
1224 my ($w, $h) = $self->{layout}->size; 1575 my ($w, $h) = $self->{layout}->size;
1225 1576
1226 if (exists $self->{template}) { 1577 if (exists $self->{template}) {
1231 1582
1232 $w = List::Util::max $w, $w2; 1583 $w = List::Util::max $w, $w2;
1233 $h = List::Util::max $h, $h2; 1584 $h = List::Util::max $h, $h2;
1234 } 1585 }
1235 1586
1236 ( 1587 ($w, $h)
1237 $w + $self->{padding} * 2,
1238 $h + $self->{padding} * 2,
1239 )
1240} 1588}
1241 1589
1242sub size_allocate { 1590sub size_allocate {
1243 my ($self, $w, $h) = @_; 1591 my ($self, $w, $h) = @_;
1244 1592
1593 delete $self->{ox};
1594
1245 delete $self->{texture}; 1595 delete $self->{texture}
1596 unless $w >= $self->{req_w} && $self->{old_w} >= $self->{req_w};
1246} 1597}
1247 1598
1248sub set_fontsize { 1599sub set_fontsize {
1249 my ($self, $fontsize) = @_; 1600 my ($self, $fontsize) = @_;
1250 1601
1251 $self->{fontsize} = $fontsize; 1602 $self->{fontsize} = $fontsize;
1252 delete $self->{texture}; 1603 delete $self->{texture};
1253 $self->check_size; 1604
1254 $self->update; 1605 $self->realloc;
1255} 1606}
1256 1607
1257sub _draw { 1608sub _draw {
1258 my ($self) = @_; 1609 my ($self) = @_;
1259 1610
1611 $self->SUPER::_draw; # draw background, if applicable
1612
1260 my $tex = $self->{texture} ||= do { 1613 my $tex = $self->{texture} ||= do {
1614 $self->{layout}->set_foreground (@{$self->{fg}});
1261 $self->{layout}->set_font ($self->{font}) if $self->{font}; 1615 $self->{layout}->set_font ($self->{font}) if $self->{font};
1262 $self->{layout}->set_width ($self->{w}); 1616 $self->{layout}->set_width ($self->{w});
1617 $self->{layout}->set_ellipsise ($self->{ellipsise});
1618 $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1263 $self->{layout}->set_height (List::Util::min $self->{h}, $self->{fontsize} * $::FONTSIZE); 1619 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1620
1264 new_from_layout CFClient::Texture $self->{layout} 1621 new_from_layout CFClient::Texture $self->{layout}
1265 }; 1622 };
1266 1623
1267 glEnable GL_BLEND; 1624 unless (exists $self->{ox}) {
1268 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 1625 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x}
1626 : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding_x}
1627 : ($self->{w} - $tex->{w}) * 0.5);
1628
1629 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y}
1630 : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding_y}
1631 : ($self->{h} - $tex->{h}) * 0.5);
1632 };
1633
1269 glEnable GL_TEXTURE_2D; 1634 glEnable GL_TEXTURE_2D;
1270 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 1635 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1271 1636
1637 if ($tex->{format} == GL_ALPHA) {
1272 glColor @{$self->{fg}}; 1638 glColor @{$self->{fg}};
1273
1274 $self->{ox} = int (
1275 $self->{align} < 0 ? $self->{padding}
1276 : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding}
1277 : ($self->{w} - $tex->{w}) * 0.5
1278 );
1279
1280 $self->{oy} = int (
1281 $self->{valign} < 0 ? $self->{padding}
1282 : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding}
1283 : ($self->{h} - $tex->{h}) * 0.5
1284 );
1285
1286 $tex->draw_quad ($self->{ox}, $self->{oy}); 1639 $tex->draw_quad_alpha ($self->{ox}, $self->{oy});
1640 } else {
1641 $tex->draw_quad_alpha_premultiplied ($self->{ox}, $self->{oy});
1642 }
1287 1643
1288 glDisable GL_TEXTURE_2D; 1644 glDisable GL_TEXTURE_2D;
1289 glDisable GL_BLEND;
1290} 1645}
1291 1646
1292############################################################################# 1647#############################################################################
1293 1648
1294package CFClient::UI::EntryBase; 1649package CFClient::UI::EntryBase;
1307 active_fg => [0, 0, 0], 1662 active_fg => [0, 0, 0],
1308 can_hover => 1, 1663 can_hover => 1,
1309 can_focus => 1, 1664 can_focus => 1,
1310 valign => 0, 1665 valign => 0,
1311 can_events => 1, 1666 can_events => 1,
1667 #text => ...
1312 @_ 1668 @_
1313 ) 1669 )
1314} 1670}
1315 1671
1316sub _set_text { 1672sub _set_text {
1326 $self->{text} = $text; 1682 $self->{text} = $text;
1327 1683
1328 $text =~ s/./*/g if $self->{hidden}; 1684 $text =~ s/./*/g if $self->{hidden};
1329 $self->{layout}->set_text ("$text "); 1685 $self->{layout}->set_text ("$text ");
1330 1686
1331 $self->emit (changed => $self->{text}); 1687 $self->_emit (changed => $self->{text});
1688}
1689
1690sub set_text {
1691 my ($self, $text) = @_;
1692
1693 $self->{cursor} = length $text;
1694 $self->_set_text ($text);
1695
1696 $self->realloc;
1332} 1697}
1333 1698
1334sub get_text { 1699sub get_text {
1335 $_[0]{text} 1700 $_[0]{text}
1336} 1701}
1339 my ($self) = @_; 1704 my ($self) = @_;
1340 1705
1341 my ($w, $h) = $self->SUPER::size_request; 1706 my ($w, $h) = $self->SUPER::size_request;
1342 1707
1343 ($w + 1, $h) # add 1 for cursor 1708 ($w + 1, $h) # add 1 for cursor
1344}
1345
1346sub size_allocate {
1347 my ($self, $w, $h) = @_;
1348
1349 $self->_set_text (delete $self->{text});#d# don't check for == inside _set_text
1350}
1351
1352sub set_text {
1353 my ($self, $text) = @_;
1354
1355 $self->{cursor} = length $text;
1356 $self->_set_text ($text);
1357 $self->update;
1358} 1709}
1359 1710
1360sub key_down { 1711sub key_down {
1361 my ($self, $ev) = @_; 1712 my ($self, $ev) = @_;
1362 1713
1364 my $sym = $ev->{sym}; 1715 my $sym = $ev->{sym};
1365 my $uni = $ev->{unicode}; 1716 my $uni = $ev->{unicode};
1366 1717
1367 my $text = $self->get_text; 1718 my $text = $self->get_text;
1368 1719
1369 if ($sym == 8) { 1720 if ($uni == 8) {
1370 substr $text, --$self->{cursor}, 1, "" if $self->{cursor}; 1721 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1371 } elsif ($sym == 127) { 1722 } elsif ($uni == 127) {
1372 substr $text, $self->{cursor}, 1, ""; 1723 substr $text, $self->{cursor}, 1, "";
1373 } elsif ($sym == CFClient::SDLK_LEFT) { 1724 } elsif ($sym == CFClient::SDLK_LEFT) {
1374 --$self->{cursor} if $self->{cursor}; 1725 --$self->{cursor} if $self->{cursor};
1375 } elsif ($sym == CFClient::SDLK_RIGHT) { 1726 } elsif ($sym == CFClient::SDLK_RIGHT) {
1376 ++$self->{cursor} if $self->{cursor} < length $self->{text}; 1727 ++$self->{cursor} if $self->{cursor} < length $self->{text};
1377 } elsif ($sym == CFClient::SDLK_HOME) { 1728 } elsif ($sym == CFClient::SDLK_HOME) {
1378 $self->{cursor} = 0; 1729 $self->{cursor} = 0;
1379 } elsif ($sym == CFClient::SDLK_END) { 1730 } elsif ($sym == CFClient::SDLK_END) {
1380 $self->{cursor} = length $text; 1731 $self->{cursor} = length $text;
1381 } elsif ($sym == 27) { 1732 } elsif ($uni == 27) {
1382 $self->emit ('escape'); 1733 $self->_emit ('escape');
1383 } elsif ($uni) { 1734 } elsif ($uni) {
1384 substr $text, $self->{cursor}++, 0, chr $uni; 1735 substr $text, $self->{cursor}++, 0, chr $uni;
1736 } else {
1737 return 0;
1385 } 1738 }
1386 1739
1387 $self->_set_text ($text); 1740 $self->_set_text ($text);
1388 $self->update; 1741
1742 $self->realloc;
1743
1744 1
1389} 1745}
1390 1746
1391sub focus_in { 1747sub focus_in {
1392 my ($self) = @_; 1748 my ($self) = @_;
1393 1749
1408 utf8::encode $text; 1764 utf8::encode $text;
1409 $self->{cursor} = length substr $text, 0, $idx; 1765 $self->{cursor} = length substr $text, 0, $idx;
1410 1766
1411 $self->_set_text ($self->{text}); 1767 $self->_set_text ($self->{text});
1412 $self->update; 1768 $self->update;
1769
1770 1
1413} 1771}
1414 1772
1415sub mouse_motion { 1773sub mouse_motion {
1416 my ($self, $ev, $x, $y) = @_; 1774 my ($self, $ev, $x, $y) = @_;
1417# printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d# 1775# printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d#
1776
1777 0
1418} 1778}
1419 1779
1420sub _draw { 1780sub _draw {
1421 my ($self) = @_; 1781 my ($self) = @_;
1422 1782
1473 if ($sym == 13) { 1833 if ($sym == 13) {
1474 unshift @{$self->{history}}, 1834 unshift @{$self->{history}},
1475 my $txt = $self->get_text; 1835 my $txt = $self->get_text;
1476 $self->{history_pointer} = -1; 1836 $self->{history_pointer} = -1;
1477 $self->{history_saveback} = ''; 1837 $self->{history_saveback} = '';
1478 $self->emit (activate => $txt); 1838 $self->_emit (activate => $txt);
1479 $self->update; 1839 $self->update;
1480 1840
1481 } elsif ($sym == CFClient::SDLK_UP) { 1841 } elsif ($sym == CFClient::SDLK_UP) {
1482 if ($self->{history_pointer} < 0) { 1842 if ($self->{history_pointer} < 0) {
1483 $self->{history_saveback} = $self->get_text; 1843 $self->{history_saveback} = $self->get_text;
1499 } else { 1859 } else {
1500 $self->set_text ($self->{history_saveback}); 1860 $self->set_text ($self->{history_saveback});
1501 } 1861 }
1502 1862
1503 } else { 1863 } else {
1504 $self->SUPER::key_down ($ev); 1864 return $self->SUPER::key_down ($ev)
1865 }
1866
1505 } 1867 1
1506
1507} 1868}
1508 1869
1509############################################################################# 1870#############################################################################
1510 1871
1511package CFClient::UI::Button; 1872package CFClient::UI::Button;
1520 1881
1521sub new { 1882sub new {
1522 my $class = shift; 1883 my $class = shift;
1523 1884
1524 $class->SUPER::new ( 1885 $class->SUPER::new (
1525 padding => 4, 1886 padding_x => 4,
1887 padding_y => 4,
1526 fg => [1, 1, 1], 1888 fg => [1, 1, 1],
1527 bg => [1, 1, 1, 0.2],
1528 active_fg => [0, 0, 1], 1889 active_fg => [0, 0, 1],
1529 can_hover => 1, 1890 can_hover => 1,
1530 align => 0, 1891 align => 0,
1531 valign => 0, 1892 valign => 0,
1532 can_events => 1, 1893 can_events => 1,
1533 @_ 1894 @_
1534 ) 1895 )
1535} 1896}
1536 1897
1898sub activate { }
1899
1537sub button_up { 1900sub button_up {
1538 my ($self, $ev, $x, $y) = @_; 1901 my ($self, $ev, $x, $y) = @_;
1539 1902
1903 $self->emit ("activate")
1540 if ($x >= 0 && $x < $self->{w} 1904 if $x >= 0 && $x < $self->{w}
1541 && $y >= 0 && $y < $self->{h}) { 1905 && $y >= 0 && $y < $self->{h};
1542 $self->emit ("activate"); 1906
1543 } 1907 1
1544} 1908}
1545 1909
1546sub _draw { 1910sub _draw {
1547 my ($self) = @_; 1911 my ($self) = @_;
1548 1912
1550 1914
1551 if ($GRAB == $self) { 1915 if ($GRAB == $self) {
1552 $self->{fg} = $self->{active_fg}; 1916 $self->{fg} = $self->{active_fg};
1553 } 1917 }
1554 1918
1555 glEnable GL_BLEND;
1556 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1557 glEnable GL_TEXTURE_2D; 1919 glEnable GL_TEXTURE_2D;
1558 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 1920 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1559 glColor 0, 0, 0, 1; 1921 glColor 0, 0, 0, 1;
1560 1922
1561 $tex[0]->draw_quad (0, 0, $self->{w}, $self->{h}); 1923 $tex[0]->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
1562 1924
1563 glDisable GL_TEXTURE_2D; 1925 glDisable GL_TEXTURE_2D;
1564 glDisable GL_BLEND;
1565 1926
1566 $self->SUPER::_draw; 1927 $self->SUPER::_draw;
1567} 1928}
1568 1929
1569############################################################################# 1930#############################################################################
1580 1941
1581sub new { 1942sub new {
1582 my $class = shift; 1943 my $class = shift;
1583 1944
1584 $class->SUPER::new ( 1945 $class->SUPER::new (
1585 padding => 2, 1946 padding_x => 2,
1947 padding_y => 2,
1586 fg => [1, 1, 1], 1948 fg => [1, 1, 1],
1587 active_fg => [1, 1, 0], 1949 active_fg => [1, 1, 0],
1950 bg => [0, 0, 0, 0.2],
1951 active_bg => [1, 1, 1, 0.5],
1588 state => 0, 1952 state => 0,
1589 can_hover => 1, 1953 can_hover => 1,
1590 @_ 1954 @_
1591 ) 1955 )
1592} 1956}
1593 1957
1594sub size_request { 1958sub size_request {
1595 my ($self) = @_; 1959 my ($self) = @_;
1596 1960
1597 ($self->{padding} * 2 + 6) x 2 1961 (6) x 2
1598} 1962}
1599 1963
1600sub button_down { 1964sub button_down {
1601 my ($self, $ev, $x, $y) = @_; 1965 my ($self, $ev, $x, $y) = @_;
1602 1966
1603 if ($x >= $self->{padding} && $x < $self->{w} - $self->{padding} 1967 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
1604 && $y >= $self->{padding} && $y < $self->{h} - $self->{padding}) { 1968 && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
1605 $self->{state} = !$self->{state}; 1969 $self->{state} = !$self->{state};
1606 $self->emit (changed => $self->{state}); 1970 $self->_emit (changed => $self->{state});
1971 } else {
1972 return 0
1973 }
1974
1607 } 1975 1
1608} 1976}
1609 1977
1610sub _draw { 1978sub _draw {
1611 my ($self) = @_; 1979 my ($self) = @_;
1612 1980
1613 $self->SUPER::_draw; 1981 $self->SUPER::_draw;
1614 1982
1615 glTranslate $self->{padding} + 0.375, $self->{padding} + 0.375, 0; 1983 glTranslate $self->{padding_x} + 0.375, $self->{padding_y} + 0.375, 0;
1616 1984
1617 my $s = (List::Util::min @$self{qw(w h)}) - $self->{padding} * 2; 1985 my ($w, $h) = @$self{qw(w h)};
1986
1987 my $s = List::Util::min $w - $self->{padding_x} * 2, $h - $self->{padding_y} * 2;
1618 1988
1619 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} }; 1989 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };
1620 1990
1621 glEnable GL_BLEND; 1991 my $tex = $self->{state} ? $tex[1] : $tex[0];
1992
1622 glEnable GL_TEXTURE_2D; 1993 glEnable GL_TEXTURE_2D;
1623 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1624
1625 my $tex = $self->{state} ? $tex[1] : $tex[0];
1626
1627 $tex->draw_quad (0, 0, $s, $s); 1994 $tex->draw_quad_alpha (0, 0, $s, $s);
1628
1629 glDisable GL_TEXTURE_2D; 1995 glDisable GL_TEXTURE_2D;
1630 glDisable GL_BLEND;
1631} 1996}
1632 1997
1633############################################################################# 1998#############################################################################
1634 1999
1635package CFClient::UI::Image; 2000package CFClient::UI::Image;
1678 glTranslate 0, -$self->{w}, 0; 2043 glTranslate 0, -$self->{w}, 0;
1679 2044
1680 ($w, $h) = ($h, $w); 2045 ($w, $h) = ($h, $w);
1681 } 2046 }
1682 2047
1683 glEnable GL_BLEND;
1684 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1685 glEnable GL_TEXTURE_2D; 2048 glEnable GL_TEXTURE_2D;
1686 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 2049 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1687 2050
1688 $tex->draw_quad (0, 0, $w, $h); 2051 $tex->draw_quad_alpha (0, 0, $w, $h);
1689 2052
1690 glDisable GL_BLEND;
1691 glDisable GL_TEXTURE_2D; 2053 glDisable GL_TEXTURE_2D;
1692} 2054}
1693 2055
1694############################################################################# 2056#############################################################################
1695 2057
1743} 2105}
1744 2106
1745sub set_max { 2107sub set_max {
1746 my ($self, $max) = @_; 2108 my ($self, $max) = @_;
1747 2109
2110 return if $self->{max_val} == $max;
2111
1748 $self->{max_val} = $max; 2112 $self->{max_val} = $max;
2113 $self->update;
1749} 2114}
1750 2115
1751sub set_value { 2116sub set_value {
1752 my ($self, $val, $max) = @_; 2117 my ($self, $val, $max) = @_;
1753 2118
1754 $self->set_max ($max) 2119 $self->set_max ($max)
1755 if defined $max; 2120 if defined $max;
1756 2121
1757 $max = $self->{max_val}; 2122 return if $self->{val} == $val;
2123
1758 $self->{val} = $val; 2124 $self->{val} = $val;
1759
1760 $self->update; 2125 $self->update;
1761} 2126}
1762 2127
1763sub _draw { 2128sub _draw {
1764 my ($self) = @_; 2129 my ($self) = @_;
1847 2212
1848 $self->{value}->set_fontsize ($fsize); 2213 $self->{value}->set_fontsize ($fsize);
1849 $self->{max} ->set_fontsize ($fsize); 2214 $self->{max} ->set_fontsize ($fsize);
1850} 2215}
1851 2216
2217sub set_max {
2218 my ($self, $max) = @_;
2219
2220 $self->{gauge}->set_max ($max);
2221 $self->{max}->set_text ($max);
2222}
2223
1852sub set_value { 2224sub set_value {
1853 my ($self, $val, $max) = @_; 2225 my ($self, $val, $max) = @_;
1854 2226
1855 $self->set_max ($max) 2227 $self->set_max ($max)
1856 if defined $max; 2228 if defined $max;
1857 2229
1858 $self->{gauge}->set_value ($val, $max); 2230 $self->{gauge}->set_value ($val, $max);
1859 $self->{value}->set_text ($val); 2231 $self->{value}->set_text ($val);
1860}
1861
1862sub set_max {
1863 my ($self, $max) = @_;
1864
1865 $self->{gauge}->set_max ($max);
1866 $self->{max}->set_text ($max);
1867} 2232}
1868 2233
1869############################################################################# 2234#############################################################################
1870 2235
1871package CFClient::UI::Slider; 2236package CFClient::UI::Slider;
1881 qw(s1_slider.png s1_slider_bg.png); 2246 qw(s1_slider.png s1_slider_bg.png);
1882 2247
1883sub new { 2248sub new {
1884 my $class = shift; 2249 my $class = shift;
1885 2250
1886 # range [value, low, high, page] 2251 # range [value, low, high, page, unit]
1887 2252
1888 # TODO: 0-width page 2253 # TODO: 0-width page
1889 # TODO: req_w/h are wrong with vertical 2254 # TODO: req_w/h are wrong with vertical
1890 # TODO: calculations are off 2255 # TODO: calculations are off
1891 my $self = $class->SUPER::new ( 2256 my $self = $class->SUPER::new (
1892 fg => [1, 1, 1], 2257 fg => [1, 1, 1],
1893 active_fg => [0, 0, 0], 2258 active_fg => [0, 0, 0],
2259 bg => [0, 0, 0, 0.2],
2260 active_bg => [1, 1, 1, 0.5],
1894 range => [0, 0, 100, 10], 2261 range => [0, 0, 100, 10, 0],
1895 req_w => 20, 2262 min_w => $::WIDTH / 80,
1896 req_h => 20, 2263 min_h => $::WIDTH / 80,
1897 vertical => 0, 2264 vertical => 0,
1898 can_hover => 1, 2265 can_hover => 1,
1899 inner_pad => 5, 2266 inner_pad => 0.02,
1900 @_ 2267 @_
1901 ); 2268 );
1902 2269
2270 $self->set_value ($self->{range}[0]);
2271 $self->update;
2272
1903 $self 2273 $self
1904} 2274}
1905 2275
2276sub changed { }
2277
2278sub set_range {
2279 my ($self, $range) = @_;
2280
2281 ($range, $self->{range}) = ($self->{range}, $range);
2282
2283 $self->update
2284 if "@$range" ne "@{$self->{range}}";
2285}
2286
2287sub set_value {
2288 my ($self, $value) = @_;
2289
2290 my ($old_value, $lo, $hi, $page, $unit) = @{$self->{range}};
2291
2292 $hi = $lo + 1 if $hi <= $lo;
2293
2294 $page = $hi - $lo if $page > $hi - $lo;
2295
2296 $value = $lo if $value < $lo;
2297 $value = $hi - $page if $value > $hi - $page;
2298
2299 $value = $lo + $unit * int +($value - $lo + $unit * 0.5) / $unit
2300 if $unit;
2301
2302 @{$self->{range}} = ($value, $lo, $hi, $page, $unit);
2303
2304 if ($value != $old_value) {
2305 $self->_emit (changed => $value);
2306 $self->update;
2307 }
2308}
2309
1906sub size_request { 2310sub size_request {
1907 my ($self) = @_; 2311 my ($self) = @_;
1908 2312
1909 my $w = $self->{req_w}; 2313 ($self->{req_w}, $self->{req_h})
1910 my $h = $self->{req_h};
1911
1912 $self->{vertical} ? ($h, $w) : ($w, $h)
1913} 2314}
1914 2315
1915sub button_down { 2316sub button_down {
1916 my ($self, $ev, $x, $y) = @_; 2317 my ($self, $ev, $x, $y) = @_;
1917 2318
1918 $self->SUPER::button_down ($ev, $x, $y); 2319 $self->SUPER::button_down ($ev, $x, $y);
2320
2321 $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x];
2322
1919 $self->mouse_motion ($ev, $x, $y); 2323 $self->mouse_motion ($ev, $x, $y)
1920} 2324}
1921 2325
1922sub mouse_motion { 2326sub mouse_motion {
1923 my ($self, $ev, $x, $y) = @_; 2327 my ($self, $ev, $x, $y) = @_;
1924 2328
1925 if ($GRAB == $self) { 2329 if ($GRAB == $self) {
2330 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w});
2331
2332 my (undef, $lo, $hi, $page) = @{$self->{range}};
2333
2334 $x = ($x - $self->{click}[1]) / ($w * $self->{scale});
2335
2336 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo));
2337 } else {
2338 return 0;
2339 }
2340
2341 1
2342}
2343
2344sub update {
2345 my ($self) = @_;
2346
2347 $CFClient::UI::ROOT->on_post_alloc ($self => sub {
2348 $self->set_value ($self->{range}[0]);
2349
1926 my ($value, $lo, $hi, $page) = @{$self->{range}}; 2350 my ($value, $lo, $hi, $page) = @{$self->{range}};
2351 my $range = ($hi - $page - $lo) || 1e-100;
1927 2352
1928 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w}); 2353 my $knob_w = List::Util::min 1, $page / ($hi - $lo) || 0.1;
1929 2354
1930 my $inner_pad_px = $self->_calc_inner_pad_px ($w); 2355 $self->{offset} = List::Util::max $self->{inner_pad}, $knob_w * 0.5;
1931 my $inner_w = $w - $inner_pad_px * 2; # * 2 for left & right 2356 $self->{scale} = 1 - 2 * $self->{offset} || 1e-100;
1932 2357
1933 $x -= $inner_pad_px; # substract the padding 2358 $value = ($value - $lo) / $range;
1934 $x = $x * ($hi - $lo) / $inner_w + $lo; 2359 $value = $value * $self->{scale} + $self->{offset};
1935 $x = $lo if $x < $lo;
1936 $x = $hi - $page if $x > $hi - $page;
1937 $self->{range}[0] = $x;
1938 2360
1939 $self->emit (changed => $x); 2361 $self->{knob_x} = $value - $knob_w * 0.5;
1940 $self->update; 2362 $self->{knob_w} = $knob_w;
1941 } 2363 });
1942}
1943 2364
1944# the inner_* stuff is for generating a padding for the slider handle, 2365 $self->SUPER::update;
1945# so that the handle doesn't leave the texture. This calculation isn't 100%
1946# correct propably, but it does the job for now
1947sub _calc_inner_pad_px {
1948 my ($self, $w) = @_;
1949 ($w / 100) * $self->{inner_pad} # % to pixels
1950} 2366}
1951 2367
1952sub _draw { 2368sub _draw {
1953 my ($self) = @_; 2369 my ($self) = @_;
1954 2370
1955 $self->SUPER::_draw (); 2371 $self->SUPER::_draw ();
1956 2372
1957 my ($w, $h) = @$self{qw(w h)}; 2373 glScale $self->{w}, $self->{h};
1958 2374
1959 if ($self->{vertical}) { 2375 if ($self->{vertical}) {
1960 # draw a vertical slider like a rotated horizontal slider 2376 # draw a vertical slider like a rotated horizontal slider
1961 2377
2378 glTranslate 1, 0, 0;
1962 glRotate 90, 0, 0, 1; 2379 glRotate 90, 0, 0, 1;
1963 glTranslate 0, -$self->{w}, 0;
1964
1965 ($w, $h) = ($h, $w);
1966 } 2380 }
1967 2381
1968 my $fg = $FOCUS == $self ? $self->{active_fg} : $self->{fg}; 2382 my $fg = $FOCUS == $self ? $self->{active_fg} : $self->{fg};
1969 my $bg = $FOCUS == $self ? $self->{active_bg} : $self->{bg}; 2383 my $bg = $FOCUS == $self ? $self->{active_bg} : $self->{bg};
1970 2384
1971 my ($value, $lo, $hi, $page) = @{$self->{range}};
1972
1973 $hi = $value + 1 if $lo == $hi;
1974
1975 my $inner_pad_px = $self->_calc_inner_pad_px ($w);
1976 my $inner_w = $w - $inner_pad_px * 2; # * 2 for left & right
1977
1978 $page = int $page * $inner_w / ($hi - $lo);
1979 $value = int +($value - $lo) * $inner_w / ($hi - $lo);
1980
1981 $w -= $page;
1982 $page &= ~1;
1983 glTranslate $page * 0.5, 0, 0;
1984 $page ||= 2;
1985
1986 my $knob_a = $inner_pad_px + ($value - $page * 0.5);
1987 my $knob_b = $inner_pad_px + ($value + $page * 0.5);
1988
1989 glEnable GL_BLEND;
1990 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1991 glEnable GL_TEXTURE_2D; 2385 glEnable GL_TEXTURE_2D;
1992 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 2386 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1993 2387
1994 # draw background 2388 # draw background
1995 $tex[1]->draw_quad (0, 0, $w, $h); 2389 $tex[1]->draw_quad_alpha (0, 0, 1, 1);
1996 2390
1997 # draw handle 2391 # draw handle
1998 $tex[0]->draw_quad ($knob_a, 0, $knob_b - $knob_a, $h); 2392 $tex[0]->draw_quad_alpha ($self->{knob_x}, 0, $self->{knob_w}, 1);
1999 2393
2000 glDisable GL_BLEND;
2001 glDisable GL_TEXTURE_2D; 2394 glDisable GL_TEXTURE_2D;
2002} 2395}
2396
2397#############################################################################
2398
2399package CFClient::UI::ValSlider;
2400
2401our @ISA = CFClient::UI::HBox::;
2402
2403sub new {
2404 my ($class, %arg) = @_;
2405
2406 my $range = delete $arg{range};
2407
2408 my $self = $class->SUPER::new (
2409 slider => (new CFClient::UI::Slider expand => 1, range => $range),
2410 entry => (new CFClient::UI::Label text => "", template => delete $arg{template}),
2411 to_value => sub { shift },
2412 from_value => sub { shift },
2413 %arg,
2414 );
2415
2416 $self->{slider}->connect (changed => sub {
2417 my ($self, $value) = @_;
2418 $self->{parent}{entry}->set_text ($self->{parent}{to_value}->($value));
2419 $self->{parent}->emit (changed => $value);
2420 });
2421
2422# $self->{entry}->connect (changed => sub {
2423# my ($self, $value) = @_;
2424# $self->{parent}{slider}->set_value ($self->{parent}{from_value}->($value));
2425# $self->{parent}->emit (changed => $value);
2426# });
2427
2428 $self->add ($self->{slider}, $self->{entry});
2429
2430 $self->{slider}->emit (changed => $self->{slider}{range}[0]);
2431
2432 $self
2433}
2434
2435sub set_range { shift->{slider}->set_range (@_) }
2436sub set_value { shift->{slider}->set_value (@_) }
2003 2437
2004############################################################################# 2438#############################################################################
2005 2439
2006package CFClient::UI::TextView; 2440package CFClient::UI::TextView;
2007 2441
2016 fontsize => 1, 2450 fontsize => 1,
2017 can_events => 0, 2451 can_events => 0,
2018 #font => default_font 2452 #font => default_font
2019 @_, 2453 @_,
2020 2454
2021 layout => (new CFClient::Layout), 2455 layout => (new CFClient::Layout 1),
2022 par => [], 2456 par => [],
2023 height => 0, 2457 height => 0,
2024 children => [ 2458 children => [
2025 (new CFClient::UI::Empty expand => 1), 2459 (new CFClient::UI::Empty expand => 1),
2026 (new CFClient::UI::Slider vertical => 1), 2460 (new CFClient::UI::Slider vertical => 1),
2027 ], 2461 ],
2028 ); 2462 );
2029 2463
2030 $self->{children}[1]->connect (changed => sub { 2464 $self->{children}[1]->connect (changed => sub { $self->update });
2031 $self->update;
2032 });
2033 2465
2034 $self 2466 $self
2035} 2467}
2036 2468
2037sub set_fontsize { 2469sub set_fontsize {
2038 my ($self, $fontsize) = @_; 2470 my ($self, $fontsize) = @_;
2039 2471
2040 $self->{fontsize} = $fontsize; 2472 $self->{fontsize} = $fontsize;
2041 $self->reflow; 2473 $self->reflow;
2042}
2043
2044sub text_height {
2045 my ($self, $text) = @_;
2046
2047 my $layout = $self->{layout};
2048
2049 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2050 $layout->set_width ($self->{w});
2051 $layout->set_text ($text);
2052
2053 ($layout->size)[1]
2054}
2055
2056sub reflow {
2057 my ($self) = @_;
2058
2059 $self->{need_reflow}++;
2060 $self->update;
2061} 2474}
2062 2475
2063sub size_allocate { 2476sub size_allocate {
2064 my ($self, $w, $h) = @_; 2477 my ($self, $w, $h) = @_;
2065 2478
2070 $self->{layout}->set_width ($self->{children}[0]{w}); 2483 $self->{layout}->set_width ($self->{children}[0]{w});
2071 2484
2072 $self->reflow; 2485 $self->reflow;
2073} 2486}
2074 2487
2488sub text_size {
2489 my ($self, $text, $indent) = @_;
2490
2491 my $layout = $self->{layout};
2492
2493 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2494 $layout->set_width ($self->{children}[0]{w} - $indent);
2495 $layout->set_markup ($text);
2496
2497 $layout->size
2498}
2499
2500sub reflow {
2501 my ($self) = @_;
2502
2503 $self->{need_reflow}++;
2504 $self->update;
2505}
2506
2507sub set_offset {
2508 my ($self, $offset) = @_;
2509
2510 # todo: base offset on lines or so, not on pixels
2511 $self->{children}[1]->set_value ($offset);
2512}
2513
2514sub clear {
2515 my ($self) = @_;
2516
2517 $self->{par} = [];
2518 $self->{height} = 0;
2519 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]);
2520}
2521
2075sub add_paragraph { 2522sub add_paragraph {
2076 my ($self, $color, $text) = @_; 2523 my ($self, $color, $text, $indent) = @_;
2077 2524
2078 #TODO: intelligently "reformat" paragraph 2525 for my $line (split /\n/, $text) {
2079 2526 my ($w, $h) = $self->text_size ($line);
2080 my $height = $self->text_height ($text);
2081
2082 $self->{height} += $height; 2527 $self->{height} += $h;
2528 push @{$self->{par}}, [$w + $indent, $h, $color, $indent, $line];
2529 }
2083 2530
2084 push @{$self->{par}}, [$height, $color, $text];
2085
2086 $self->{children}[1]{range} = [$self->{height} - $self->{h}, 0, $self->{height}, $self->{h}]; 2531 $self->{children}[1]->set_range ([$self->{height}, 0, $self->{height}, $self->{h}, 1]);
2087 $self->{children}[1]->update;
2088} 2532}
2089 2533
2090sub update { 2534sub update {
2091 my ($self) = @_; 2535 my ($self) = @_;
2092 2536
2094 2538
2095 return unless $self->{h} > 0; 2539 return unless $self->{h} > 0;
2096 2540
2097 delete $self->{texture}; 2541 delete $self->{texture};
2098 2542
2099 $ROOT->on_refresh ($self, sub { 2543 $ROOT->on_post_alloc ($self, sub {
2544 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
2545
2100 if (delete $self->{need_reflow}) { 2546 if (delete $self->{need_reflow}) {
2101 my $height = 0; 2547 my $height = 0;
2102 2548
2103 $height += $_->[0] = $self->text_height ($_->[2]) 2549 my $layout = $self->{layout};
2550
2551 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2552
2104 for @{$self->{par}}; 2553 for (@{$self->{par}}) {
2554 if (1 || $_->[0] >= $W) { # TODO: works,but needs reconfigure etc. support
2555 $layout->set_width ($W - $_->[3]);
2556 $layout->set_markup ($_->[4]);
2557 my ($w, $h) = $layout->size;
2558 $_->[0] = $w + $_->[3];
2559 $_->[1] = $h;
2560 }
2561
2562 $height += $_->[1];
2563 }
2105 2564
2106 $self->{height} = $height; 2565 $self->{height} = $height;
2107 2566
2108 $self->{children}[1]{range} = [$height - $self->{h}, 0, $height, $self->{h}]; 2567 $self->{children}[1]->set_range ([$height, 0, $height, $H, 1]);
2109 2568
2110 delete $self->{texture}; 2569 delete $self->{texture};
2111 } 2570 }
2112 2571
2113 $self->{texture} ||= new_from_opengl CFClient::Texture $self->{w}, $self->{h}, sub { 2572 $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub {
2114 glClearColor 0, 0, 0, 1; 2573 glClearColor 0.5, 0.5, 0.5, 0;
2115 glClear GL_COLOR_BUFFER_BIT; 2574 glClear GL_COLOR_BUFFER_BIT;
2116 2575
2576 my $top = int $self->{children}[1]{range}[0];
2577
2578 my $y0 = $top;
2579 my $y1 = $top + $H;
2580
2581 my $y = 0;
2582
2583 my $layout = $self->{layout};
2584
2585 $layout->set_font ($self->{font}) if $self->{font};
2586
2117 glEnable GL_BLEND; 2587 glEnable GL_BLEND;
2588 #TODO# not correct in windows where rgba is forced off
2118 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA; 2589 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2119 glEnable GL_TEXTURE_2D;
2120 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2121
2122 my $top = int $self->{children}[1]{range}[0];
2123
2124 my $y0 = $top;
2125 my $y1 = $top + $self->{h};
2126
2127 my $y = 0;
2128
2129 my $layout = $self->{layout};
2130
2131 $layout->set_font ($self->{font}) if $self->{font};
2132 2590
2133 for my $par (@{$self->{par}}) { 2591 for my $par (@{$self->{par}}) {
2134 my $h = $par->[0]; 2592 my $h = $par->[1];
2135 2593
2136 if ($y0 < $y + $h && $y < $y1) { 2594 if ($y0 < $y + $h && $y < $y1) {
2595 $layout->set_foreground (@{ $par->[2] });
2596 $layout->set_width ($W - $par->[3]);
2137 $layout->set_text ($par->[2]); 2597 $layout->set_markup ($par->[4]);
2138 2598
2139 glColor @{ $par->[1] }; 2599 my ($w, $h, $data, $format, $internalformat) = $layout->render;
2140 my ($W, $H) = $layout->size; 2600
2141 CFClient::Texture->new_from_layout ($layout)->draw_quad (0, $y - $y0); 2601 glRasterPos $par->[3], $y - $y0;
2602 glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data;
2142 } 2603 }
2143 2604
2144 $y += $h; 2605 $y += $h;
2145 } 2606 }
2146 2607
2147 glDisable GL_TEXTURE_2D;
2148 glDisable GL_BLEND; 2608 glDisable GL_BLEND;
2149 }; 2609 };
2150 }); 2610 });
2151} 2611}
2152 2612
2153sub _draw { 2613sub _draw {
2154 my ($self) = @_; 2614 my ($self) = @_;
2155 2615
2156 if ($self->{texture}) {
2157 glEnable GL_TEXTURE_2D; 2616 glEnable GL_TEXTURE_2D;
2158 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 2617 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2618 glColor 1, 1, 1, 1;
2159 $self->{texture}->draw_quad (0, 0, $self->{w}, $self->{h}); 2619 $self->{texture}->draw_quad_alpha (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h});
2160 glDisable GL_TEXTURE_2D; 2620 glDisable GL_TEXTURE_2D;
2161 }
2162 2621
2163 $self->{children}[1]->draw; 2622 $self->{children}[1]->draw;
2164 2623
2165} 2624}
2166 2625
2214 2673
2215sub new { 2674sub new {
2216 my $class = shift; 2675 my $class = shift;
2217 2676
2218 my $self = $class->SUPER::new ( 2677 my $self = $class->SUPER::new (
2219 state => 0, 2678 state => 0,
2220 connect_activate => \&toggle_flopper, 2679 on_activate => \&toggle_flopper,
2221 @_ 2680 @_
2222 ); 2681 );
2223 2682
2224 if ($self->{state}) {
2225 $self->{state} = 0;
2226 $self->toggle_flopper;
2227 }
2228
2229 $self 2683 $self
2230} 2684}
2231 2685
2232sub toggle_flopper { 2686sub toggle_flopper {
2233 my ($self) = @_; 2687 my ($self) = @_;
2234 2688
2235 # TODO: use animation 2689 $self->{other}->toggle_visibility;
2236 if ($self->{state} = !$self->{state}) {
2237 $CFClient::UI::ROOT->add ($self->{other});
2238 $self->{other}->move ($self->coord2global (0, $self->{h}));
2239 $self->emit ("open");
2240 } else {
2241 $CFClient::UI::ROOT->remove ($self->{other});
2242 $self->emit ("close");
2243 }
2244
2245 $self->emit (changed => $self->{state});
2246} 2690}
2247 2691
2248############################################################################# 2692#############################################################################
2249 2693
2250package CFClient::UI::Tooltip; 2694package CFClient::UI::Tooltip;
2260 @_, 2704 @_,
2261 can_events => 0, 2705 can_events => 0,
2262 ) 2706 )
2263} 2707}
2264 2708
2265sub set_markup { 2709sub set_tooltip_from {
2266 my ($self, $text) = @_; 2710 my ($self, $widget) = @_;
2267 2711
2268 $self->{label} ||= new CFClient::UI::Label fontsize => 0.8, fg => [0, 0, 0]; 2712 my $tooltip = $widget->{tooltip};
2269 $self->{label}->set_markup ($text); 2713
2270 $self->add ($self->{label}); 2714 if ($ENV{CFPLUS_DEBUG} & 2) {
2715 $tooltip .= "\n\n" . (ref $widget) . "\n"
2716 . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n"
2717 . "req $widget->{req_w} $widget->{req_h}\n"
2718 . "visible $widget->{visible}";
2719 }
2720
2721 $self->add (new CFClient::UI::Label
2722 markup => $tooltip,
2723 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH,
2724 fontsize => 0.8,
2725 fg => [0, 0, 0, 1],
2726 ellipsise => 0,
2727 font => ($widget->{tooltip_font} || $::FONT_PROP),
2728 );
2271} 2729}
2272 2730
2273sub size_request { 2731sub size_request {
2274 my ($self) = @_; 2732 my ($self) = @_;
2275
2276 $self->child->set_max_size ($::WIDTH * 0.3);
2277 2733
2278 my ($w, $h) = @{$self->child}{qw(req_w req_h)}; 2734 my ($w, $h) = @{$self->child}{qw(req_w req_h)};
2279 2735
2280 ($w + 4, $h + 4) 2736 ($w + 4, $h + 4)
2281} 2737}
2284 my ($self, $w, $h) = @_; 2740 my ($self, $w, $h) = @_;
2285 2741
2286 $self->SUPER::size_allocate ($w - 4, $h - 4); 2742 $self->SUPER::size_allocate ($w - 4, $h - 4);
2287} 2743}
2288 2744
2745sub visibility_change {
2746 my ($self, $visible) = @_;
2747
2748 return unless $visible;
2749
2750 $self->{root}->on_post_alloc ("move_$self" => sub {
2751 my $widget = $self->{owner}
2752 or return;
2753
2754 my ($x, $y) = $widget->coord2global ($widget->{w}, 0);
2755
2756 ($x, $y) = $widget->coord2global (-$self->{w}, 0)
2757 if $x + $self->{w} > $::WIDTH;
2758
2759 $self->move_abs ($x, $y);
2760 });
2761}
2762
2289sub _draw { 2763sub _draw {
2290 my ($self) = @_; 2764 my ($self) = @_;
2291 2765
2292 glPushMatrix;
2293 glTranslate 0.375, 0.375; 2766 glTranslate 0.375, 0.375;
2294 2767
2295 my ($w, $h) = @$self{qw(w h)}; 2768 my ($w, $h) = @$self{qw(w h)};
2296 2769
2297 glColor 1, 0.8, 0.4; 2770 glColor 1, 0.8, 0.4;
2308 glVertex 0 , $h; 2781 glVertex 0 , $h;
2309 glVertex $w, $h; 2782 glVertex $w, $h;
2310 glVertex $w, 0; 2783 glVertex $w, 0;
2311 glEnd; 2784 glEnd;
2312 2785
2313 glPopMatrix; 2786 glTranslate 2 - 0.375, 2 - 0.375;
2314 2787
2315 glTranslate 2, 2;
2316 $self->SUPER::_draw; 2788 $self->SUPER::_draw;
2317} 2789}
2318 2790
2319############################################################################# 2791#############################################################################
2320 2792
2325use CFClient::OpenGL; 2797use CFClient::OpenGL;
2326 2798
2327sub new { 2799sub new {
2328 my $class = shift; 2800 my $class = shift;
2329 2801
2330 $class->SUPER::new ( 2802 my $self = $class->SUPER::new (
2331 aspect => 1, 2803 aspect => 1,
2804 can_events => 0,
2332 @_, 2805 @_,
2333 ) 2806 );
2807
2808 if ($self->{anim} && $self->{animspeed}) {
2809 Scalar::Util::weaken (my $widget = $self);
2810
2811 $self->{timer} = Event->timer (
2812 at => $self->{animspeed} * int $::NOW / $self->{animspeed},
2813 hard => 1,
2814 interval => $self->{animspeed},
2815 cb => sub {
2816 ++$widget->{frame};
2817 $widget->update;
2818 },
2819 );
2820 }
2821
2822 $self
2334} 2823}
2335 2824
2336sub size_request { 2825sub size_request {
2337 (32, 8) 2826 (32, 8)
2338} 2827}
2339 2828
2829sub update {
2830 my ($self) = @_;
2831
2832 return unless $self->{visible};
2833
2834 $self->SUPER::update;
2835}
2836
2340sub draw { 2837sub _draw {
2341 my ($self) = @_; 2838 my ($self) = @_;
2342 2839
2840 return unless $::CONN;
2841
2842 my $face;
2843
2844 if ($self->{frame}) {
2845 my $anim = $::CONN->{anim}[$self->{anim}];
2846
2847 $face = $anim->[ $self->{frame} % @$anim ]
2848 if $anim && @$anim;
2849 }
2850
2343 my $tex = $::CONN->{texture}[$::CONN->{faceid}[$self->{face}]]; 2851 my $tex = $::CONN->{texture}[$::CONN->{faceid}[$face || $self->{face}]];
2344 2852
2345 if ($tex) { 2853 if ($tex) {
2346 glEnable GL_BLEND;
2347 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
2348 glEnable GL_TEXTURE_2D; 2854 glEnable GL_TEXTURE_2D;
2349 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE; 2855 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2350 glColor 1, 1, 1, 1; 2856 glColor 1, 1, 1, 1;
2351 $tex->draw_quad (0, 0, $self->{w}, $self->{h}); 2857 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2352 glDisable GL_TEXTURE_2D; 2858 glDisable GL_TEXTURE_2D;
2353 glDisable GL_BLEND;
2354 } 2859 }
2860}
2861
2862sub DESTROY {
2863 my ($self) = @_;
2864
2865 $self->{timer}->cancel
2866 if $self->{timer};
2867
2868 $self->SUPER::DESTROY;
2355} 2869}
2356 2870
2357############################################################################# 2871#############################################################################
2358 2872
2873package CFClient::UI::Buttonbar;
2874
2875our @ISA = CFClient::UI::HBox::;
2876
2877# TODO: should actualyl wrap buttons and other goodies.
2878
2879#############################################################################
2880
2881package CFClient::UI::Menu;
2882
2883our @ISA = CFClient::UI::FancyFrame::;
2884
2885use CFClient::OpenGL;
2886
2887sub new {
2888 my $class = shift;
2889
2890 my $self = $class->SUPER::new (
2891 items => [],
2892 z => 100,
2893 @_,
2894 );
2895
2896 $self->add ($self->{vbox} = new CFClient::UI::VBox);
2897
2898 for my $item (@{ $self->{items} }) {
2899 my ($widget, $cb) = @$item;
2900
2901 # handle various types of items, only text for now
2902 if (!ref $widget) {
2903 $widget = new CFClient::UI::Label
2904 can_hover => 1,
2905 can_events => 1,
2906 text => $widget;
2907 }
2908
2909 $self->{item}{$widget} = $item;
2910
2911 $self->{vbox}->add ($widget);
2912 }
2913
2914 $self
2915}
2916
2917# popup given the event (must be a mouse button down event currently)
2918sub popup {
2919 my ($self, $ev) = @_;
2920
2921 $self->_emit ("popdown");
2922
2923 # maybe save $GRAB? must be careful about events...
2924 $GRAB = $self;
2925 $self->{button} = $ev->{button};
2926
2927 $self->show;
2928 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5);
2929}
2930
2931sub mouse_motion {
2932 my ($self, $ev, $x, $y) = @_;
2933
2934 # TODO: should use vbox->find_widget or so
2935 $HOVER = $ROOT->find_widget ($ev->{x}, $ev->{y});
2936 $self->{hover} = $self->{item}{$HOVER};
2937
2938 0
2939}
2940
2941sub button_up {
2942 my ($self, $ev, $x, $y) = @_;
2943
2944 if ($ev->{button} == $self->{button}) {
2945 undef $GRAB;
2946 $self->hide;
2947
2948 $self->_emit ("popdown");
2949 $self->{hover}[1]->() if $self->{hover};
2950 } else {
2951 return 0
2952 }
2953
2954 1
2955}
2956
2957#############################################################################
2958
2959package CFClient::UI::Multiplexer;
2960
2961our @ISA = CFClient::UI::Container::;
2962
2963sub new {
2964 my $class = shift;
2965
2966 my $self = $class->SUPER::new (
2967 @_,
2968 );
2969
2970 $self->{current} = $self->{children}[0]
2971 if @{ $self->{children} };
2972
2973 $self
2974}
2975
2976sub add {
2977 my ($self, @widgets) = @_;
2978
2979 $self->SUPER::add (@widgets);
2980
2981 $self->{current} = $self->{children}[0]
2982 if @{ $self->{children} };
2983}
2984
2985sub set_current_page {
2986 my ($self, $page_or_widget) = @_;
2987
2988 my $widget = ref $page_or_widget
2989 ? $page_or_widget
2990 : $self->{children}[$page_or_widget];
2991
2992 $self->{current} = $widget;
2993 $self->{current}->configure (0, 0, $self->{w}, $self->{h});
2994
2995 $self->_emit (page_changed => $self->{current});
2996
2997 $self->realloc;
2998}
2999
3000sub visible_children {
3001 $_[0]{current}
3002}
3003
3004sub size_request {
3005 my ($self) = @_;
3006
3007 $self->{current}->size_request
3008}
3009
3010sub size_allocate {
3011 my ($self, $w, $h) = @_;
3012
3013 $self->{current}->configure (0, 0, $w, $h);
3014}
3015
3016sub _draw {
3017 my ($self) = @_;
3018
3019 $self->{current}->draw;
3020}
3021
3022#############################################################################
3023
3024package CFClient::UI::Notebook;
3025
3026our @ISA = CFClient::UI::VBox::;
3027
3028sub new {
3029 my $class = shift;
3030
3031 my $self = $class->SUPER::new (
3032 buttonbar => (new CFClient::UI::Buttonbar),
3033 multiplexer => (new CFClient::UI::Multiplexer expand => 1),
3034 # filter => # will be put between multiplexer and $self
3035 @_,
3036 );
3037
3038 $self->{filter}->add ($self->{multiplexer}) if $self->{filter};
3039 $self->SUPER::add ($self->{buttonbar}, $self->{filter} || $self->{multiplexer});
3040
3041 $self
3042}
3043
3044sub add {
3045 my ($self, $title, $widget, $tooltip) = @_;
3046
3047 Scalar::Util::weaken $self;
3048
3049 $self->{buttonbar}->add (new CFClient::UI::Button
3050 markup => $title,
3051 tooltip => $tooltip,
3052 on_activate => sub { $self->set_current_page ($widget) },
3053 );
3054
3055 $self->{multiplexer}->add ($widget);
3056}
3057
3058sub set_current_page {
3059 my ($self, $page) = @_;
3060
3061 $self->{multiplexer}->set_current_page ($page);
3062 $self->_emit (page_changed => $self->{multiplexer}{current});
3063}
3064
3065#############################################################################
3066
3067package CFClient::UI::Statusbox;
3068
3069our @ISA = CFClient::UI::VBox::;
3070
3071sub new {
3072 my $class = shift;
3073
3074 $class->SUPER::new (
3075 fontsize => 0.8,
3076 @_,
3077 )
3078}
3079
3080sub reorder {
3081 my ($self) = @_;
3082 my $NOW = time;
3083
3084 while (my ($k, $v) = each %{ $self->{item} }) {
3085 delete $self->{item}{$k} if $v->{timeout} < $NOW;
3086 }
3087
3088 my @widgets;
3089
3090 my @items = sort {
3091 $a->{pri} <=> $b->{pri}
3092 or $b->{id} <=> $a->{id}
3093 } values %{ $self->{item} };
3094
3095 my $count = 10 + 1;
3096 for my $item (@items) {
3097 last unless --$count;
3098
3099 push @widgets, $item->{label} ||= do {
3100 # TODO: doesn't handle markup well (read as: at all)
3101 my $short = $item->{count} > 1
3102 ? "<b>$item->{count} ×</b> $item->{text}"
3103 : $item->{text};
3104
3105 for ($short) {
3106 s/^\s+//;
3107 s/\s+/ /g;
3108 }
3109
3110 new CFClient::UI::Label
3111 markup => $short,
3112 tooltip => $item->{tooltip},
3113 tooltip_font => $::FONT_PROP,
3114 tooltip_width => 0.67,
3115 fontsize => $item->{fontsize} || $self->{fontsize},
3116 max_w => $::WIDTH * 0.44,
3117 fg => $item->{fg},
3118 can_events => 1,
3119 can_hover => 1
3120 };
3121 }
3122
3123 $self->clear;
3124 $self->SUPER::add (reverse @widgets);
3125}
3126
3127sub add {
3128 my ($self, $text, %arg) = @_;
3129
3130 $text =~ s/^\s+//;
3131 $text =~ s/\s+$//;
3132
3133 return unless $text;
3134
3135 my $timeout = time + ((delete $arg{timeout}) || 60);
3136
3137 my $group = exists $arg{group} ? $arg{group} : ++$self->{id};
3138
3139 if (my $item = $self->{item}{$group}) {
3140 if ($item->{text} eq $text) {
3141 $item->{count}++;
3142 } else {
3143 $item->{count} = 1;
3144 $item->{text} = $item->{tooltip} = $text;
3145 }
3146 $item->{id} = ++$self->{id};
3147 $item->{timeout} = $timeout;
3148 delete $item->{label};
3149 } else {
3150 $self->{item}{$group} = {
3151 id => ++$self->{id},
3152 text => $text,
3153 timeout => $timeout,
3154 tooltip => $text,
3155 fg => [0.8, 0.8, 0.8, 0.8],
3156 pri => 0,
3157 count => 1,
3158 %arg,
3159 };
3160 }
3161
3162 $self->reorder;
3163}
3164
3165sub reconfigure {
3166 my ($self) = @_;
3167
3168 delete $_->{label}
3169 for values %{ $self->{item} || {} };
3170
3171 $self->reorder;
3172 $self->SUPER::reconfigure;
3173}
3174
3175#############################################################################
3176
3177package CFClient::UI::Inventory;
3178
3179our @ISA = CFClient::UI::ScrolledWindow::;
3180
3181sub new {
3182 my $class = shift;
3183
3184 my $self = $class->SUPER::new (
3185 child => (new CFClient::UI::Table col_expand => [0, 1, 0]),
3186 @_,
3187 );
3188
3189 $self
3190}
3191
3192sub set_items {
3193 my ($self, $items) = @_;
3194
3195 $self->{child}->clear;
3196 return unless $items;
3197
3198 my @items = sort {
3199 ($a->{type} <=> $b->{type})
3200 or ($a->{name} cmp $b->{name})
3201 } @$items;
3202
3203 $self->{real_items} = \@items;
3204
3205 my $row = 0;
3206 for my $item (@items) {
3207 CFClient::Item::update_widgets $item;
3208
3209 $self->{child}->add (0, $row, $item->{face_widget});
3210 $self->{child}->add (1, $row, $item->{desc_widget});
3211 $self->{child}->add (2, $row, $item->{weight_widget});
3212
3213 $row++;
3214 }
3215}
3216
3217#############################################################################
3218
3219package CFClient::UI::BindEditor;
3220
3221our @ISA = CFClient::UI::FancyFrame::;
3222
3223sub new {
3224 my $class = shift;
3225
3226 my $self = $class->SUPER::new (binding => [], commands => [], @_);
3227
3228 $self->add (my $vb = new CFClient::UI::VBox);
3229
3230
3231 $vb->add ($self->{rec_btn} = new CFClient::UI::Button
3232 text => "start recording",
3233 tooltip => "Start/Stops recording of actions."
3234 ."All subsequent actions after the recording started will be captured."
3235 ."The actions are displayed after the record was stopped."
3236 ."To bind the action you have to click on the 'Bind' button",
3237 on_activate => sub {
3238 unless ($self->{recording}) {
3239 $self->start;
3240 } else {
3241 $self->stop;
3242 }
3243 });
3244
3245 $vb->add (new CFClient::UI::Label text => "Actions:");
3246 $vb->add ($self->{cmdbox} = new CFClient::UI::VBox);
3247
3248 $vb->add (new CFClient::UI::Label text => "Bound to: ");
3249 $vb->add (my $hb = new CFClient::UI::HBox);
3250 $hb->add ($self->{keylbl} = new CFClient::UI::Label expand => 1);
3251 $hb->add (new CFClient::UI::Button
3252 text => "bind",
3253 tooltip => "This opens a query where you have to press the key combination to bind the recorded actions",
3254 on_activate => sub {
3255 $self->ask_for_bind;
3256 });
3257
3258 $vb->add (my $hb = new CFClient::UI::HBox);
3259 $hb->add (new CFClient::UI::Button
3260 text => "ok",
3261 expand => 1,
3262 tooltip => "This closes the binding editor and saves the binding",
3263 on_activate => sub {
3264 $self->hide;
3265 $self->commit;
3266 });
3267
3268 $hb->add (new CFClient::UI::Button
3269 text => "cancel",
3270 expand => 1,
3271 tooltip => "This closes the binding editor without saving",
3272 on_activate => sub {
3273 $self->hide;
3274 $self->{binding_cancel}->()
3275 if $self->{binding_cancel};
3276 });
3277
3278 $self->update_binding_widgets;
3279
3280 $self
3281}
3282
3283sub commit {
3284 my ($self) = @_;
3285 my ($mod, $sym, $cmds) = $self->get_binding;
3286 if ($sym != 0 && @$cmds > 0) {
3287 $::STATUSBOX->add ("Bound actions to '".CFClient::Binder::keycombo_to_name ($mod, $sym)
3288 ."'. Don't forget 'Save Config'!");
3289 $self->{binding_change}->($mod, $sym, $cmds)
3290 if $self->{binding_change};
3291 } else {
3292 $::STATUSBOX->add ("No action bound, no key or action specified!");
3293 $self->{binding_cancel}->()
3294 if $self->{binding_cancel};
3295 }
3296}
3297
3298sub start {
3299 my ($self) = @_;
3300
3301 $self->{rec_btn}->set_text ("stop recording");
3302 $self->{recording} = 1;
3303 $self->clear_command_list;
3304 $::CONN->start_record if $::CONN;
3305}
3306
3307sub stop {
3308 my ($self) = @_;
3309
3310 $self->{rec_btn}->set_text ("start recording");
3311 $self->{recording} = 0;
3312
3313 my $rec;
3314 $rec = $::CONN->stop_record if $::CONN;
3315 return unless ref $rec eq 'ARRAY';
3316 $self->set_command_list ($rec);
3317}
3318
3319
3320sub ask_for_bind_and_commit {
3321 my ($self) = @_;
3322 $self->ask_for_bind (1);
3323}
3324
3325sub ask_for_bind {
3326 my ($self, $commit) = @_;
3327
3328 CFClient::Binder::open_binding_dialog (sub {
3329 my ($mod, $sym) = @_;
3330 $self->{binding} = [$mod, $sym]; # XXX: how to stop that memleak?
3331 $self->update_binding_widgets;
3332 $self->commit if $commit;
3333 });
3334}
3335
3336# $mod and $sym are the modifiers and key symbol
3337# $cmds is a array ref of strings (the commands)
3338# $cb is the callback that is executed on OK
3339# $ccb is the callback that is executed on CANCEL and
3340# when the binding was unsuccessful on OK
3341sub set_binding {
3342 my ($self, $mod, $sym, $cmds, $cb, $ccb) = @_;
3343
3344 $self->clear_command_list;
3345 $self->{recording} = 0;
3346 $self->{rec_btn}->set_text ("start recording");
3347
3348 $self->{binding} = [$mod, $sym];
3349 $self->{commands} = $cmds;
3350
3351 $self->{binding_change} = $cb;
3352 $self->{binding_cancel} = $ccb;
3353
3354 $self->update_binding_widgets;
3355}
3356
3357# this is a shortcut method that asks for a binding
3358# and then just binds it.
3359sub do_quick_binding {
3360 my ($self, $cmds) = @_;
3361 $self->set_binding (undef, undef, $cmds, sub {
3362 $::CFG->{bindings}->{$_[0]}->{$_[1]} = $_[2];
3363 });
3364 $self->ask_for_bind (1);
3365}
3366
3367sub update_binding_widgets {
3368 my ($self) = @_;
3369 my ($mod, $sym, $cmds) = $self->get_binding;
3370 $self->{keylbl}->set_text (CFClient::Binder::keycombo_to_name ($mod, $sym));
3371 $self->set_command_list ($cmds);
3372}
3373
3374sub get_binding {
3375 my ($self) = @_;
3376 return (
3377 $self->{binding}->[0],
3378 $self->{binding}->[1],
3379 [ grep { defined $_ } @{$self->{commands}} ]
3380 );
3381}
3382
3383sub clear_command_list {
3384 my ($self) = @_;
3385 $self->{cmdbox}->clear ();
3386}
3387
3388sub set_command_list {
3389 my ($self, $cmds) = @_;
3390
3391 $self->{cmdbox}->clear ();
3392 $self->{commands} = $cmds;
3393
3394 my $idx = 0;
3395
3396 for (@$cmds) {
3397 $self->{cmdbox}->add (my $hb = new CFClient::UI::HBox);
3398
3399 my $i = $idx;
3400 $hb->add (new CFClient::UI::Label text => $_);
3401 $hb->add (new CFClient::UI::Button
3402 text => "delete",
3403 tooltip => "Deletes the action from the record",
3404 on_activate => sub {
3405 $self->{cmdbox}->remove ($hb);
3406 $cmds->[$i] = undef;
3407 });
3408
3409
3410 $idx++
3411 }
3412}
3413
3414#############################################################################
3415
3416package CFClient::UI::SpellList;
3417
3418our @ISA = CFClient::UI::Table::;
3419
3420sub new {
3421 my $class = shift;
3422
3423 my $self = $class->SUPER::new (
3424 binding => [],
3425 commands => [],
3426 @_,
3427 )
3428}
3429
3430# XXX: Do sorting? Argl...
3431sub add_spell {
3432 my ($self, $spell) = @_;
3433 $self->{spells}->{$spell->{name}} = $spell;
3434
3435 $self->add (0, $self->{tbl_idx}, new CFClient::UI::Face
3436 face => $spell->{face},
3437 can_hover => 1,
3438 can_events => 1,
3439 tooltip => $spell->{message});
3440
3441 $self->add (1, $self->{tbl_idx}, new CFClient::UI::Label
3442 text => $spell->{name},
3443 can_hover => 1,
3444 can_events => 1,
3445 tooltip => $spell->{message},
3446 expand => 1);
3447
3448 $self->add (2, $self->{tbl_idx}, new CFClient::UI::Label
3449 text => (sprintf "lvl: %2d sp: %2d dmg: %2d",
3450 $spell->{level}, ($spell->{mana} || $spell->{grace}), $spell->{damage}),
3451 expand => 1);
3452
3453 $self->add (3, $self->{tbl_idx}++, new CFClient::UI::Button
3454 text => "bind to key",
3455 on_activate => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) });
3456}
3457
3458sub rebuild_spell_list {
3459 my ($self) = @_;
3460 $self->{tbl_idx} = 0;
3461 $self->add_spell ($_) for values %{$self->{spells}};
3462}
3463
3464sub remove_spell {
3465 my ($self, $spell) = @_;
3466 delete $self->{spells}->{$spell->{name}};
3467 $self->rebuild_spell_list;
3468}
3469
3470#############################################################################
3471
2359package CFClient::UI::Root; 3472package CFClient::UI::Root;
2360 3473
2361our @ISA = CFClient::UI::Container::; 3474our @ISA = CFClient::UI::Container::;
2362 3475
2363use CFClient::OpenGL; 3476use CFClient::OpenGL;
2364 3477
2365sub check_size { 3478sub new {
2366 my ($self) = @_; 3479 my $class = shift;
2367 3480
2368 $self->configure (0, 0, $::WIDTH, $::HEIGHT); 3481 my $self = $class->SUPER::new (
3482 visible => 1,
3483 @_,
3484 );
3485
3486 Scalar::Util::weaken ($self->{root} = $self);
3487
3488 $self
2369} 3489}
2370 3490
2371sub size_request { 3491sub size_request {
2372 ($::WIDTH, $::HEIGHT) 3492 my ($self) = @_;
2373}
2374 3493
2375sub configure { 3494 ($self->{w}, $self->{h})
3495}
3496
3497sub _to_pixel {
3498 my ($coord, $size, $max) = @_;
3499
3500 $coord =
3501 $coord eq "center" ? ($max - $size) * 0.5
3502 : $coord eq "max" ? $max
3503 : $coord;
3504
3505 $coord = 0 if $coord < 0;
3506 $coord = $max - $size if $coord > $max - $size;
3507
3508 int $coord + 0.5
3509}
3510
3511sub size_allocate {
2376 my ($self, $x, $y, $w, $h) = @_; 3512 my ($self, $w, $h) = @_;
2377 3513
2378 $self->SUPER::configure ($x, $y, $w, $h);
2379
2380 for my $child (@{$self->{children}}) { 3514 for my $child ($self->children) {
2381 my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)}; 3515 my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)};
2382 3516
2383 $X = List::Util::max 0, List::Util::min $w - $W, $X; 3517 $X = $child->{force_x} if exists $child->{force_x};
2384 $Y = List::Util::max 0, List::Util::min $h - $H, $Y; 3518 $Y = $child->{force_y} if exists $child->{force_y};
3519
3520 $X = _to_pixel $X, $W, $self->{w};
3521 $Y = _to_pixel $Y, $H, $self->{h};
3522
2385 $child->configure ($X, $Y, $W,$H); 3523 $child->configure ($X, $Y, $W, $H);
2386 } 3524 }
2387} 3525}
2388 3526
2389sub _topleft { 3527sub coord2local {
2390 my ($self, $x, $y) = @_; 3528 my ($self, $x, $y) = @_;
2391 3529
2392 ($x, $y) 3530 ($x, $y)
2393} 3531}
2394 3532
3533sub coord2global {
3534 my ($self, $x, $y) = @_;
3535
3536 ($x, $y)
3537}
3538
2395sub update { 3539sub update {
2396 my ($self) = @_; 3540 my ($self) = @_;
2397 3541
2398 $self->check_size; 3542 $::WANT_REFRESH++;
2399 ::refresh ();
2400} 3543}
2401 3544
2402sub add { 3545sub add {
2403 my ($self, $child) = @_; 3546 my ($self, @children) = @_;
2404 3547
2405 # integerize window positions 3548 $_->{is_toplevel} = 1
2406 $child->{x} = int $child->{x}; 3549 for @children;
2407 $child->{y} = int $child->{y};
2408 3550
2409 $self->SUPER::add ($child); 3551 $self->SUPER::add (@children);
3552}
3553
3554sub remove {
3555 my ($self, @children) = @_;
3556
3557 $self->SUPER::remove (@children);
3558
3559 delete $self->{is_toplevel}
3560 for @children;
3561
3562 while (@children) {
3563 my $w = pop @children;
3564 push @children, $w->children;
3565 $w->set_invisible;
3566 }
2410} 3567}
2411 3568
2412sub on_refresh { 3569sub on_refresh {
2413 my ($self, $id, $cb) = @_; 3570 my ($self, $id, $cb) = @_;
2414 3571
2415 $self->{refresh_hook}{$id} = $cb; 3572 $self->{refresh_hook}{$id} = $cb;
2416} 3573}
2417 3574
3575sub on_post_alloc {
3576 my ($self, $id, $cb) = @_;
3577
3578 $self->{post_alloc_hook}{$id} = $cb;
3579}
3580
2418sub draw { 3581sub draw {
2419 my ($self) = @_; 3582 my ($self) = @_;
2420 3583
2421 while (my $rcb = delete $self->{refresh_hook}) { 3584 while ($self->{refresh_hook}) {
2422 $_->() for values %$rcb; 3585 $_->()
3586 for values %{delete $self->{refresh_hook}};
2423 } 3587 }
3588
3589 if ($self->{realloc}) {
3590 my %queue;
3591 my @queue;
3592 my $widget;
3593
3594 outer:
3595 while () {
3596 if (my $realloc = delete $self->{realloc}) {
3597 for $widget (values %$realloc) {
3598 $widget->{visible} or next; # do not resize invisible widgets
3599
3600 $queue{$widget+0}++ and next; # duplicates are common
3601
3602 push @{ $queue[$widget->{visible}] }, $widget;
3603 }
3604 }
3605
3606 while () {
3607 @queue or last outer;
3608
3609 $widget = pop @{ $queue[-1] || [] }
3610 and last;
3611
3612 pop @queue;
3613 }
3614
3615 delete $queue{$widget+0};
3616
3617 my ($w, $h) = $widget->size_request;
3618
3619 $w = List::Util::max $widget->{min_w}, $w + $widget->{padding_x} * 2;
3620 $h = List::Util::max $widget->{min_h}, $h + $widget->{padding_y} * 2;
3621
3622 $w = $widget->{force_w} if exists $widget->{force_w};
3623 $h = $widget->{force_h} if exists $widget->{force_h};
3624
3625 if ($widget->{req_w} != $w || $widget->{req_h} != $h
3626 || delete $widget->{force_realloc}) {
3627 $widget->{req_w} = $w;
3628 $widget->{req_h} = $h;
3629
3630 $self->{size_alloc}{$widget+0} = $widget;
3631
3632 if (my $parent = $widget->{parent}) {
3633 $self->{realloc}{$parent+0} = $parent
3634 unless $queue{$parent+0};
3635
3636 $parent->{force_size_alloc} = 1;
3637 $self->{size_alloc}{$parent+0} = $parent;
3638 }
3639 }
3640
3641 delete $self->{realloc}{$widget+0};
3642 }
3643 }
3644
3645 while (my $size_alloc = delete $self->{size_alloc}) {
3646 my @queue = sort { $b->{visible} <=> $a->{visible} }
3647 values %$size_alloc;
3648
3649 while () {
3650 my $widget = pop @queue || last;
3651
3652 my ($w, $h) = @$widget{qw(alloc_w alloc_h)};
3653
3654 $w = 0 if $w < 0;
3655 $h = 0 if $h < 0;
3656
3657 $w = int $w + 0.5;
3658 $h = int $h + 0.5;
3659
3660 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) {
3661 $widget->{old_w} = $widget->{w};
3662 $widget->{old_h} = $widget->{h};
3663
3664 $widget->{w} = $w;
3665 $widget->{h} = $h;
3666
3667 $widget->emit (size_allocate => $w, $h);
3668 }
3669 }
3670 }
3671
3672 while ($self->{post_alloc_hook}) {
3673 $_->()
3674 for values %{delete $self->{post_alloc_hook}};
3675 }
3676
2424 3677
2425 glViewport 0, 0, $::WIDTH, $::HEIGHT; 3678 glViewport 0, 0, $::WIDTH, $::HEIGHT;
2426 glClearColor +($::CFG->{fow_intensity}) x 3, 1; 3679 glClearColor +($::CFG->{fow_intensity}) x 3, 1;
2427 glClear GL_COLOR_BUFFER_BIT; 3680 glClear GL_COLOR_BUFFER_BIT;
2428 3681
2429 glMatrixMode GL_PROJECTION; 3682 glMatrixMode GL_PROJECTION;
2430 glLoadIdentity; 3683 glLoadIdentity;
2431 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000 , 10000; 3684 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000;
2432 glMatrixMode GL_MODELVIEW; 3685 glMatrixMode GL_MODELVIEW;
2433 glLoadIdentity; 3686 glLoadIdentity;
2434 3687
3688 {
3689 package CFClient::UI::Base;
3690
3691 ($draw_x, $draw_y, $draw_w, $draw_h) =
3692 (0, 0, $self->{w}, $self->{h});
3693 }
3694
2435 $self->_draw; 3695 $self->_draw;
2436} 3696}
2437 3697
2438############################################################################# 3698#############################################################################
2439 3699
2440package CFClient::UI; 3700package CFClient::UI;
2441 3701
2442$ROOT = new CFClient::UI::Root; 3702$ROOT = new CFClient::UI::Root;
2443$TOOLTIP = new CFClient::UI::Tooltip; 3703$TOOLTIP = new CFClient::UI::Tooltip z => 900;
2444 3704
24451 37051
2446 3706

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines