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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines