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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines