ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.262
Committed: Wed May 31 13:44:26 2006 UTC (17 years, 11 months ago) by elmex
Branch: MAIN
Changes since 1.261: +67 -0 lines
Log Message:
added first version of a spell widget

File Contents

# User Rev Content
1 root 1.73 package CFClient::UI;
2 root 1.8
3 root 1.194 use utf8;
4 elmex 1.1 use strict;
5 root 1.18
6 root 1.74 use Scalar::Util ();
7     use List::Util ();
8 root 1.18
9 root 1.60 use CFClient;
10 root 1.240 use CFClient::Texture;
11 root 1.41
12 root 1.51 our ($FOCUS, $HOVER, $GRAB); # various widgets
13    
14 elmex 1.242 our $LAYOUT;
15 root 1.113 our $ROOT;
16 root 1.151 our $TOOLTIP;
17 root 1.51 our $BUTTON_STATE;
18    
19 root 1.202 our %WIDGET; # all widgets, weak-referenced
20    
21 elmex 1.242 sub get_layout {
22 root 1.256 my $layout;
23    
24 elmex 1.242 for (grep { $_->{name} } values %WIDGET) {
25 root 1.256 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 root 1.258
32     $win->{show} = $_->{visible} && $_->{is_toplevel};
33 elmex 1.242 }
34    
35 root 1.256 $layout
36 elmex 1.242 }
37    
38     sub set_layout {
39     my ($layout) = @_;
40 root 1.258
41 elmex 1.242 $LAYOUT = $layout;
42     }
43    
44 root 1.151 sub check_tooltip {
45     if (!$GRAB) {
46     for (my $widget = $HOVER; $widget; $widget = $widget->{parent}) {
47 root 1.160 if (length $widget->{tooltip}) {
48 root 1.151
49     if ($TOOLTIP->{owner} != $widget) {
50 root 1.253 $TOOLTIP->hide;
51    
52 root 1.151 $TOOLTIP->{owner} = $widget;
53 root 1.155
54     my $tip = $widget->{tooltip};
55    
56     $tip = $tip->($widget) if CODE:: eq ref $tip;
57    
58 root 1.196 $TOOLTIP->set_tooltip_from ($widget);
59 root 1.252 $TOOLTIP->show;
60 root 1.151 }
61    
62     return;
63     }
64     }
65     }
66    
67     $TOOLTIP->hide;
68     delete $TOOLTIP->{owner};
69     }
70    
71 elmex 1.1 # class methods for events
72 root 1.51 sub feed_sdl_key_down_event {
73 root 1.231 $FOCUS->emit (key_down => $_[0])
74 root 1.163 if $FOCUS;
75 root 1.51 }
76    
77     sub feed_sdl_key_up_event {
78 root 1.231 $FOCUS->emit (key_up => $_[0])
79 root 1.163 if $FOCUS;
80 root 1.51 }
81    
82     sub feed_sdl_button_down_event {
83     my ($ev) = @_;
84 root 1.137 my ($x, $y) = ($ev->{x}, $ev->{y});
85 root 1.51
86     if (!$BUTTON_STATE) {
87 root 1.113 my $widget = $ROOT->find_widget ($x, $y);
88 root 1.51
89     $GRAB = $widget;
90     $GRAB->update if $GRAB;
91 root 1.151
92     check_tooltip;
93 root 1.51 }
94    
95 root 1.137 $BUTTON_STATE |= 1 << ($ev->{button} - 1);
96 root 1.51
97 root 1.231 $GRAB->emit (button_down => $ev, $GRAB->coord2local ($x, $y))
98     if $GRAB;
99 root 1.51 }
100    
101     sub feed_sdl_button_up_event {
102     my ($ev) = @_;
103 root 1.137 my ($x, $y) = ($ev->{x}, $ev->{y});
104 root 1.51
105 root 1.113 my $widget = $GRAB || $ROOT->find_widget ($x, $y);
106 root 1.51
107 root 1.137 $BUTTON_STATE &= ~(1 << ($ev->{button} - 1));
108 root 1.51
109 root 1.231 $GRAB->emit (button_up => $ev, $GRAB->coord2local ($x, $y))
110     if $GRAB;
111 root 1.58
112 root 1.51 if (!$BUTTON_STATE) {
113     my $grab = $GRAB; undef $GRAB;
114     $grab->update if $grab;
115     $GRAB->update if $GRAB;
116 root 1.151
117     check_tooltip;
118 root 1.51 }
119     }
120    
121     sub feed_sdl_motion_event {
122     my ($ev) = @_;
123 root 1.137 my ($x, $y) = ($ev->{x}, $ev->{y});
124 root 1.51
125 root 1.113 my $widget = $GRAB || $ROOT->find_widget ($x, $y);
126 root 1.51
127     if ($widget != $HOVER) {
128     my $hover = $HOVER; $HOVER = $widget;
129    
130 root 1.105 $hover->update if $hover && $hover->{can_hover};
131     $HOVER->update if $HOVER && $HOVER->{can_hover};
132 root 1.151
133     check_tooltip;
134 root 1.51 }
135    
136 root 1.231 $HOVER->emit (mouse_motion => $ev, $HOVER->coord2local ($x, $y))
137     if $HOVER;
138 root 1.51 }
139 elmex 1.1
140 root 1.112 # convert position array to integers
141     sub harmonize {
142     my ($vals) = @_;
143    
144     my $rem = 0;
145    
146 root 1.116 for (@$vals) {
147 root 1.112 my $i = int $_ + $rem;
148     $rem += $_ - $i;
149     $_ = $i;
150     }
151     }
152    
153 root 1.220 sub 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    
161 root 1.230 sub 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 root 1.202 # call when resolution changes etc.
170     sub rescale_widgets {
171     my ($sx, $sy) = @_;
172    
173 root 1.230 for my $widget (values %WIDGET) {
174     if ($widget->{is_toplevel}) {
175 root 1.256 $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 root 1.202 }
189 root 1.230 }
190 root 1.202
191 root 1.230 reconfigure_widgets;
192 root 1.202 }
193    
194 root 1.73 #############################################################################
195    
196     package CFClient::UI::Base;
197    
198     use strict;
199    
200 root 1.138 use CFClient::OpenGL;
201 root 1.73
202 elmex 1.1 sub new {
203     my $class = shift;
204 root 1.10
205 root 1.79 my $self = bless {
206 root 1.256 x => "center",
207     y => "center",
208 root 1.164 z => 0,
209 root 1.256 w => undef,
210     h => undef,
211 elmex 1.150 can_events => 1,
212 root 1.65 @_
213 root 1.79 }, $class;
214    
215 root 1.258 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 root 1.256 if (my $layout = $CFClient::UI::LAYOUT->{$self->{name}}) {
224 root 1.259 $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 root 1.256
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 root 1.258 $self->show if $layout->{show};
233 root 1.79 }
234    
235     $self
236 elmex 1.1 }
237    
238 root 1.159 sub destroy {
239     my ($self) = @_;
240    
241     $self->hide;
242     %$self = ();
243     }
244    
245 root 1.134 sub show {
246     my ($self) = @_;
247 root 1.248
248 root 1.134 return if $self->{parent};
249    
250     $CFClient::UI::ROOT->add ($self);
251     }
252    
253 root 1.246 sub 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 root 1.248 $self->emit (visibility_change => 1);
262 root 1.251
263 root 1.252 $self->realloc if !exists $self->{req_w};
264 root 1.251
265     $_->set_visible for $self->children;
266 root 1.246 }
267    
268 root 1.231 sub set_invisible {
269 root 1.134 my ($self) = @_;
270    
271 root 1.244 return unless $self->{visible};
272    
273 root 1.251 $_->set_invisible for $self->children;
274 root 1.231
275 root 1.241 delete $self->{root};
276 root 1.231 delete $self->{visible};
277    
278 root 1.163 undef $GRAB if $GRAB == $self;
279     undef $HOVER if $HOVER == $self;
280 root 1.134
281 root 1.232 CFClient::UI::check_tooltip
282 root 1.251 if $TOOLTIP->{owner} == $self;
283 root 1.232
284 root 1.231 $self->focus_out;
285 root 1.244
286     $self->emit (visibility_change => 0);
287 root 1.231 }
288    
289 root 1.250 sub set_visibility {
290     my ($self, $visible) = @_;
291    
292     return if $self->{visible} == $visible;
293    
294     $visible ? $self->hide
295     : $self->show;
296     }
297    
298     sub toggle_visibility {
299     my ($self) = @_;
300    
301     $self->{visible}
302     ? $self->hide
303     : $self->show;
304     }
305    
306 root 1.231 sub hide {
307     my ($self) = @_;
308    
309     $self->set_invisible;
310    
311 root 1.163 $self->{parent}->remove ($self)
312     if $self->{parent};
313 root 1.134 }
314    
315 root 1.256 sub move_abs {
316 root 1.18 my ($self, $x, $y, $z) = @_;
317 root 1.128
318 root 1.256 $self->{x} = List::Util::max 0, int $x;
319     $self->{y} = List::Util::max 0, int $y;
320 root 1.18 $self->{z} = $z if defined $z;
321 root 1.128
322     $self->update;
323 root 1.18 }
324    
325 root 1.158 sub set_size {
326     my ($self, $w, $h) = @_;
327    
328 root 1.256 $self->{force_w} = $w;
329     $self->{force_h} = $h;
330 root 1.158
331 root 1.251 $self->realloc;
332 elmex 1.20 }
333    
334 root 1.14 sub size_request {
335 elmex 1.36 require Carp;
336 root 1.147 Carp::confess "size_request is abstract";
337 elmex 1.36 }
338    
339 root 1.128 sub configure {
340 root 1.75 my ($self, $x, $y, $w, $h) = @_;
341    
342 root 1.141 if ($self->{aspect}) {
343 root 1.256 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 root 1.141
348     # use alignment to adjust x, y
349 root 1.75
350 root 1.256 $x += int 0.5 * ($ow - $w);
351     $y += int 0.5 * ($oh - $h);
352 root 1.140 }
353    
354 root 1.256 if ($self->{x} ne $x || $self->{y} ne $y) {
355 root 1.141 $self->{x} = $x;
356     $self->{y} = $y;
357     $self->update;
358     }
359 root 1.40
360 root 1.259 if ($self->{alloc_w} != $w || $self->{alloc_h} != $h) {
361 root 1.251 return unless $self->{visible};
362    
363 root 1.259 $self->{alloc_w} = $w;
364     $self->{alloc_h} = $h;
365    
366     $self->{root}{size_alloc}{$self+0} = $self;
367 root 1.141 }
368 root 1.75 }
369    
370     sub size_allocate {
371 root 1.128 # nothing to be done
372 root 1.14 }
373    
374 root 1.202 sub children {
375     }
376    
377 root 1.157 sub set_max_size {
378     my ($self, $w, $h) = @_;
379    
380     delete $self->{max_w}; $self->{max_w} = $w if $w;
381     delete $self->{max_h}; $self->{max_h} = $h if $h;
382     }
383    
384 root 1.209 sub set_tooltip {
385     my ($self, $tooltip) = @_;
386    
387 root 1.234 $tooltip =~ s/^\s+//;
388     $tooltip =~ s/\s+$//;
389    
390     return if $self->{tooltip} eq $tooltip;
391    
392 root 1.209 $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    
400 root 1.82 # translate global coordinates to local coordinate system
401 root 1.113 sub coord2local {
402 root 1.58 my ($self, $x, $y) = @_;
403    
404 root 1.191 $self->{parent}->coord2local ($x - $self->{x}, $y - $self->{y})
405 root 1.113 }
406    
407     # translate local coordinates to global coordinate system
408     sub coord2global {
409     my ($self, $x, $y) = @_;
410    
411 root 1.191 $self->{parent}->coord2global ($x + $self->{x}, $y + $self->{y})
412 root 1.58 }
413    
414 elmex 1.1 sub focus_in {
415 root 1.51 my ($self) = @_;
416    
417 root 1.68 return if $FOCUS == $self;
418 root 1.97 return unless $self->{can_focus};
419 root 1.68
420 root 1.51 my $focus = $FOCUS; $FOCUS = $self;
421 elmex 1.120
422 root 1.231 $self->_emit (focus_in => $focus);
423 elmex 1.120
424 root 1.51 $focus->update if $focus;
425     $FOCUS->update;
426 elmex 1.1 }
427 root 1.4
428 elmex 1.1 sub focus_out {
429 root 1.51 my ($self) = @_;
430 root 1.4
431 root 1.51 return unless $FOCUS == $self;
432 root 1.4
433 root 1.51 my $focus = $FOCUS; undef $FOCUS;
434 elmex 1.120
435 root 1.231 $self->_emit (focus_out => $focus);
436 elmex 1.120
437 root 1.51 $focus->update if $focus; #?
438 root 1.231
439     $::MAPWIDGET->focus_in #d# focus mapwidget if no other widget has focus
440     unless $FOCUS;
441 elmex 1.1 }
442 root 1.4
443 root 1.51 sub mouse_motion { }
444 root 1.231 sub button_up { }
445     sub key_down { }
446     sub key_up { }
447 root 1.51
448 root 1.68 sub button_down {
449     my ($self, $ev, $x, $y) = @_;
450    
451     $self->focus_in;
452     }
453    
454 root 1.51 sub w { $_[0]{w} = $_[1] if @_ > 1; $_[0]{w} }
455     sub h { $_[0]{h} = $_[1] if @_ > 1; $_[0]{h} }
456     sub x { $_[0]{x} = $_[1] if @_ > 1; $_[0]{x} }
457     sub y { $_[0]{y} = $_[1] if @_ > 1; $_[0]{y} }
458     sub z { $_[0]{z} = $_[1] if @_ > 1; $_[0]{z} }
459 elmex 1.11
460 root 1.251 sub 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    
472     sub set_parent {
473     my ($self, $parent) = @_;
474    
475     Scalar::Util::weaken ($self->{parent} = $parent);
476     $self->set_visible if $parent->{visible};
477     }
478    
479     sub connect {
480     my ($self, $signal, $cb) = @_;
481    
482     push @{ $self->{signal_cb}{$signal} }, $cb;
483     }
484    
485     sub _emit {
486     my ($self, $signal, @args) = @_;
487    
488     List::Util::sum map $_->($self, @args), @{$self->{signal_cb}{$signal} || []}
489     }
490    
491     sub emit {
492     my ($self, $signal, @args) = @_;
493    
494     $self->_emit ($signal, @args)
495     || $self->$signal (@args);
496     }
497    
498     sub visibility_change {
499     #my ($self, $visible) = @_;
500     }
501    
502     sub realloc {
503     my ($self) = @_;
504    
505 root 1.252 if ($self->{visible}) {
506 root 1.259 return if $self->{root}{realloc}{$self+0};
507 root 1.251
508 root 1.259 $self->{root}{realloc}{$self+0} = $self;
509 root 1.252 $self->{root}->update;
510     } else {
511     delete $self->{req_w};
512 root 1.259 delete $self->{req_h};
513 root 1.252 }
514 root 1.251 }
515    
516     sub update {
517     my ($self) = @_;
518    
519     $self->{parent}->update
520     if $self->{parent};
521     }
522    
523 root 1.256 sub reconfigure {
524     my ($self) = @_;
525    
526     $self->realloc;
527     $self->update;
528     }
529    
530 elmex 1.1 sub draw {
531 elmex 1.11 my ($self) = @_;
532    
533 root 1.68 return unless $self->{h} && $self->{w};
534    
535 elmex 1.11 glPushMatrix;
536 root 1.12 glTranslate $self->{x}, $self->{y}, 0;
537 elmex 1.11 $self->_draw;
538 root 1.72 glPopMatrix;
539    
540 root 1.97 if ($self == $HOVER && $self->{can_hover}) {
541 root 1.73 my ($x, $y) = @$self{qw(x y)};
542 root 1.72
543 root 1.122 glColor 1, 0.8, 0.5, 0.2;
544 root 1.50 glEnable GL_BLEND;
545 root 1.74 glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
546 root 1.48 glBegin GL_QUADS;
547 root 1.72 glVertex $x , $y;
548     glVertex $x + $self->{w}, $y;
549     glVertex $x + $self->{w}, $y + $self->{h};
550     glVertex $x , $y + $self->{h};
551 root 1.47 glEnd;
552 root 1.50 glDisable GL_BLEND;
553 root 1.47 }
554 root 1.162
555 root 1.259 if ($ENV{CFPLUS_DEBUG} & 1) {
556 root 1.162 glPushMatrix;
557     glColor 1, 1, 0, 1;
558     glTranslate $self->{x} + 0.375, $self->{y} + 0.375;
559     glBegin GL_LINE_LOOP;
560 root 1.234 glVertex 0 , 0;
561     glVertex $self->{w} - 1, 0;
562     glVertex $self->{w} - 1, $self->{h} - 1;
563     glVertex 0 , $self->{h} - 1;
564 root 1.162 glEnd;
565     glPopMatrix;
566 root 1.205 #CFClient::UI::Label->new (w => $self->{w}, h => $self->{h}, text => $self, fontsize => 0)->_draw;
567 root 1.162 }
568 elmex 1.11 }
569    
570     sub _draw {
571 root 1.38 my ($self) = @_;
572    
573     warn "no draw defined for $self\n";
574 elmex 1.1 }
575 root 1.4
576 root 1.18 sub DESTROY {
577     my ($self) = @_;
578    
579 root 1.202 delete $WIDGET{$self+0};
580 elmex 1.32 #$self->deactivate;
581 root 1.18 }
582    
583 root 1.39 #############################################################################
584    
585 root 1.73 package CFClient::UI::DrawBG;
586 root 1.68
587 root 1.73 our @ISA = CFClient::UI::Base::;
588 root 1.68
589     use strict;
590 root 1.138 use CFClient::OpenGL;
591 root 1.68
592     sub new {
593     my $class = shift;
594    
595     # range [value, low, high, page]
596    
597     $class->SUPER::new (
598 root 1.209 #bg => [0, 0, 0, 0.2],
599     #active_bg => [1, 1, 1, 0.5],
600 root 1.68 @_
601     )
602     }
603    
604     sub _draw {
605     my ($self) = @_;
606    
607 root 1.209 my $color = $FOCUS == $self && $self->{active_bg}
608     ? $self->{active_bg}
609     : $self->{bg};
610    
611     if ($color && (@$color < 4 || $color->[3])) {
612     my ($w, $h) = @$self{qw(w h)};
613 root 1.68
614 root 1.209 glEnable GL_BLEND;
615     glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
616     glColor @$color;
617 root 1.76
618 root 1.209 glBegin GL_QUADS;
619     glVertex 0 , 0;
620     glVertex 0 , $h;
621     glVertex $w, $h;
622     glVertex $w, 0;
623     glEnd;
624 root 1.76
625 root 1.209 glDisable GL_BLEND;
626     }
627 root 1.68 }
628    
629     #############################################################################
630    
631 root 1.73 package CFClient::UI::Empty;
632 root 1.66
633 root 1.73 our @ISA = CFClient::UI::Base::;
634 root 1.66
635 elmex 1.150 sub new {
636     my ($class, %arg) = @_;
637     $class->SUPER::new (can_events => 0, %arg);
638     }
639    
640 root 1.66 sub size_request {
641 root 1.256 my ($self) = @_;
642    
643     ($self->{w} + 0, $self->{h} + 0)
644 root 1.66 }
645    
646 root 1.67 sub draw { }
647 root 1.66
648     #############################################################################
649    
650 root 1.73 package CFClient::UI::Container;
651 elmex 1.15
652 root 1.73 our @ISA = CFClient::UI::Base::;
653 elmex 1.15
654 root 1.38 sub new {
655 root 1.64 my ($class, %arg) = @_;
656    
657     my $children = delete $arg{children} || [];
658 root 1.38
659 root 1.166 my $self = $class->SUPER::new (
660     children => [],
661     can_events => 0,
662     %arg,
663     );
664 root 1.64 $self->add ($_) for @$children;
665 root 1.38
666     $self
667     }
668    
669     sub add {
670 root 1.188 my ($self, @widgets) = @_;
671 root 1.113
672 root 1.188 $_->set_parent ($self)
673     for @widgets;
674 root 1.38
675 root 1.113 use sort 'stable';
676 root 1.38
677 root 1.66 $self->{children} = [
678 root 1.38 sort { $a->{z} <=> $b->{z} }
679 root 1.188 @{$self->{children}}, @widgets
680 root 1.66 ];
681 root 1.38
682 root 1.251 $self->realloc;
683 root 1.38 }
684 root 1.35
685 root 1.172 sub children {
686     @{ $_[0]{children} }
687     }
688    
689 elmex 1.32 sub remove {
690 root 1.134 my ($self, $child) = @_;
691    
692     delete $child->{parent};
693 root 1.163 $child->hide;
694 root 1.38
695 root 1.134 $self->{children} = [ grep $_ != $child, @{ $self->{children} } ];
696 root 1.38
697 root 1.251 $self->realloc;
698 root 1.38 }
699    
700 root 1.162 sub clear {
701     my ($self) = @_;
702    
703 root 1.163 my $children = delete $self->{children};
704     $self->{children} = [];
705 root 1.162
706 root 1.163 for (@$children) {
707     delete $_->{parent};
708     $_->hide;
709     }
710 root 1.182
711 root 1.251 $self->realloc;
712 root 1.162 }
713    
714 root 1.38 sub find_widget {
715     my ($self, $x, $y) = @_;
716    
717 root 1.45 $x -= $self->{x};
718     $y -= $self->{y};
719    
720 root 1.38 my $res;
721    
722 root 1.46 for (reverse @{ $self->{children} }) {
723 root 1.45 $res = $_->find_widget ($x, $y)
724 root 1.38 and return $res;
725     }
726    
727 root 1.46 $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
728 elmex 1.32 }
729 elmex 1.15
730 root 1.35 sub _draw {
731     my ($self) = @_;
732    
733 root 1.38 $_->draw for @{$self->{children}};
734 root 1.35 }
735 elmex 1.15
736 root 1.39 #############################################################################
737    
738 root 1.73 package CFClient::UI::Bin;
739 elmex 1.32
740 root 1.73 our @ISA = CFClient::UI::Container::;
741 elmex 1.32
742 root 1.66 sub new {
743     my ($class, %arg) = @_;
744    
745 root 1.73 my $child = (delete $arg{child}) || new CFClient::UI::Empty::;
746 root 1.66
747     $class->SUPER::new (children => [$child], %arg)
748     }
749    
750     sub add {
751 root 1.134 my ($self, $child) = @_;
752 root 1.66
753     $self->{children} = [];
754    
755 root 1.134 $self->SUPER::add ($child);
756 root 1.66 }
757    
758     sub remove {
759     my ($self, $widget) = @_;
760    
761     $self->SUPER::remove ($widget);
762    
763 root 1.73 $self->{children} = [new CFClient::UI::Empty]
764 root 1.66 unless @{$self->{children}};
765     }
766    
767 root 1.39 sub child { $_[0]->{children}[0] }
768 elmex 1.32
769 root 1.38 sub size_request {
770 root 1.68 $_[0]{children}[0]->size_request
771 root 1.38 }
772 elmex 1.32
773 root 1.38 sub size_allocate {
774 root 1.259 my ($self, $w, $h) = @_;
775 root 1.42
776 root 1.128 $self->{children}[0]->configure (0, 0, $w, $h);
777 root 1.38 }
778 elmex 1.32
779 root 1.39 #############################################################################
780    
781 root 1.73 package CFClient::UI::Window;
782 elmex 1.20
783 root 1.73 our @ISA = CFClient::UI::Bin::;
784 elmex 1.20
785 root 1.138 use CFClient::OpenGL;
786 elmex 1.20
787 root 1.42 sub new {
788 root 1.64 my ($class, %arg) = @_;
789 elmex 1.32
790 root 1.64 my $self = $class->SUPER::new (%arg);
791 elmex 1.32 }
792    
793     sub update {
794     my ($self) = @_;
795 root 1.42
796 root 1.198 $ROOT->on_post_alloc ($self => sub { $self->render_child });
797 root 1.42 $self->SUPER::update;
798 elmex 1.20 }
799    
800 root 1.174 sub size_allocate {
801 root 1.259 my ($self, $w, $h) = @_;
802 root 1.174
803 root 1.259 $self->SUPER::size_allocate ($w, $h);
804     $self->update;
805 root 1.174 }
806    
807 root 1.182 sub _render {
808     $_[0]{children}[0]->draw;
809     }
810    
811 root 1.174 sub render_child {
812 elmex 1.20 my ($self) = @_;
813    
814 root 1.105 $self->{texture} = new_from_opengl CFClient::Texture $self->{w}, $self->{h}, sub {
815 root 1.216 glClearColor 0, 0, 0, 0;
816 root 1.105 glClear GL_COLOR_BUFFER_BIT;
817 root 1.182
818     $self->_render;
819 root 1.105 };
820 elmex 1.36 }
821    
822 elmex 1.20 sub _draw {
823     my ($self) = @_;
824    
825 elmex 1.36 my ($w, $h) = ($self->w, $self->h);
826 root 1.29
827 elmex 1.20 my $tex = $self->{texture}
828     or return;
829    
830     glEnable GL_TEXTURE_2D;
831 root 1.35 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
832 root 1.215 glColor 1, 1, 1, 1;
833 elmex 1.20
834 root 1.194 $tex->draw_quad_alpha_premultiplied (0, 0, $w, $h);
835 elmex 1.20
836     glDisable GL_TEXTURE_2D;
837     }
838    
839 root 1.39 #############################################################################
840    
841 root 1.125 package CFClient::UI::ViewPort;
842    
843     our @ISA = CFClient::UI::Window::;
844    
845 root 1.234 sub new {
846     my $class = shift;
847    
848     $class->SUPER::new (
849     scroll_x => 0,
850     scroll_y => 1,
851     @_,
852     )
853     }
854    
855 root 1.125 sub size_request {
856     my ($self) = @_;
857    
858 root 1.259 my ($w, $h) = @{$self->child}{qw(req_w req_h)};
859 root 1.247
860     $w = 10 if $self->{scroll_x};
861     $h = 10 if $self->{scroll_y};
862 root 1.125
863 root 1.247 ($w, $h)
864 root 1.125 }
865    
866 root 1.182 sub size_allocate {
867 root 1.259 my ($self, $w, $h) = @_;
868    
869     my $child = $self->child;
870 root 1.182
871 root 1.259 $w = $child->{req_w} if $self->{scroll_x} && $child->{req_w};
872     $h = $child->{req_h} if $self->{scroll_y} && $child->{req_h};
873 root 1.234
874     $self->child->configure (0, 0, $w, $h);
875 root 1.182 $self->update;
876     }
877    
878     sub 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 root 1.191 # hmm, this does not work for topleft of $self... but we should not ask for that
888     sub coord2local {
889     my ($self, $x, $y) = @_;
890    
891     $self->SUPER::coord2local ($x + $self->{view_x}, $y + $self->{view_y})
892     }
893    
894     sub coord2global {
895 root 1.184 my ($self, $x, $y) = @_;
896    
897 root 1.191 $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 root 1.184 }
902    
903     sub 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    
915 root 1.182 sub _render {
916 root 1.125 my ($self) = @_;
917    
918 root 1.182 CFClient::OpenGL::glTranslate -$self->{view_x}, -$self->{view_y};
919    
920     $self->SUPER::_render;
921     }
922    
923     #############################################################################
924    
925     package CFClient::UI::ScrolledWindow;
926    
927     our @ISA = CFClient::UI::HBox::;
928    
929     sub new {
930     my $class = shift;
931    
932     my $self;
933    
934     my $slider = new CFClient::UI::Slider
935 root 1.243 vertical => 1,
936     range => [0, 0, 1, 0.01], # HACK fix
937     on_changed => sub {
938 root 1.229 $self->{vp}->set_offset (0, $_[1]);
939 root 1.182 },
940     ;
941    
942     $self = $class->SUPER::new (
943 root 1.217 vp => (new CFClient::UI::ViewPort expand => 1),
944 root 1.182 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 root 1.125 }
954    
955 root 1.239 sub 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    
965 root 1.229 sub size_allocate {
966 root 1.259 my ($self, $w, $h) = @_;
967 root 1.229
968 root 1.259 $self->SUPER::size_allocate ($w, $h);
969 root 1.229
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 root 1.199 #TODO# update range on size_allocate depending on child
975 root 1.182 # update viewport offset on scroll
976 root 1.125
977     #############################################################################
978    
979 root 1.73 package CFClient::UI::Frame;
980 elmex 1.15
981 root 1.73 our @ISA = CFClient::UI::Bin::;
982 elmex 1.15
983 root 1.138 use CFClient::OpenGL;
984 elmex 1.15
985 root 1.199 sub new {
986     my $class = shift;
987    
988     $class->SUPER::new (
989     bg => undef,
990     @_,
991     )
992     }
993    
994     sub _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 root 1.39 #############################################################################
1018    
1019 root 1.73 package CFClient::UI::FancyFrame;
1020 elmex 1.31
1021 root 1.73 our @ISA = CFClient::UI::Bin::;
1022 elmex 1.31
1023 root 1.138 use CFClient::OpenGL;
1024 elmex 1.31
1025 root 1.255 my $bg =
1026     new_from_file CFClient::Texture CFClient::find_rcfile "d1_bg.png",
1027     mipmap => 1, wrap => 1;
1028    
1029     my @border =
1030 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1031 root 1.255 qw(d1_border_top.png d1_border_right.png d1_border_left.png d1_border_bottom.png);
1032 elmex 1.34
1033 root 1.97 sub new {
1034     my $class = shift;
1035    
1036 root 1.141 my $self = $class->SUPER::new (
1037 root 1.230 bg => [1, 1, 1, 1],
1038     border_bg => [1, 1, 1, 1],
1039     border => 0.6,
1040     can_events => 1,
1041 root 1.258 min_w => 16,
1042     min_h => 16,
1043 root 1.97 @_
1044 root 1.141 );
1045    
1046     $self->{title} &&= new CFClient::UI::Label
1047     align => 0,
1048     valign => 1,
1049     text => $self->{title},
1050 root 1.176 fontsize => $self->{border};
1051 root 1.141
1052     $self
1053 root 1.97 }
1054    
1055 root 1.134 sub border {
1056     int $_[0]{border} * $::FONTSIZE
1057     }
1058    
1059 elmex 1.34 sub size_request {
1060     my ($self) = @_;
1061 root 1.39
1062 root 1.78 my ($w, $h) = $self->SUPER::size_request;
1063 elmex 1.34
1064 root 1.97 (
1065 root 1.134 $w + $self->border * 2,
1066     $h + $self->border * 2,
1067 root 1.97 )
1068 elmex 1.36 }
1069    
1070     sub size_allocate {
1071 root 1.259 my ($self, $w, $h) = @_;
1072 root 1.40
1073 root 1.134 $h -= List::Util::max 0, $self->border * 2;
1074     $w -= List::Util::max 0, $self->border * 2;
1075 elmex 1.36
1076 root 1.208 $self->{title}->configure ($self->border, int $self->border - $::FONTSIZE * 2, $w, int $::FONTSIZE * 2)
1077 root 1.141 if $self->{title};
1078    
1079 root 1.134 $self->child->configure ($self->border, $self->border, $w, $h);
1080 elmex 1.34 }
1081    
1082 root 1.77 sub button_down {
1083     my ($self, $ev, $x, $y) = @_;
1084    
1085 root 1.176 my ($w, $h) = @$self{qw(w h)};
1086 root 1.134 my $border = $self->border;
1087    
1088 root 1.176 my $lr = ($x >= 0 && $x < $border) || ($x > $w - $border && $x < $w);
1089     my $td = ($y >= 0 && $y < $border) || ($y > $h - $border && $y < $h);
1090 root 1.77
1091 root 1.176 if ($lr & $td) {
1092     my ($wx, $wy) = ($self->{x}, $self->{y});
1093 root 1.139 my ($ox, $oy) = ($ev->{x}, $ev->{y});
1094 root 1.77 my ($bw, $bh) = ($self->{w}, $self->{h});
1095    
1096 root 1.176 my $mx = $x < $border;
1097     my $my = $y < $border;
1098    
1099 root 1.77 $self->{motion} = sub {
1100     my ($ev, $x, $y) = @_;
1101    
1102 root 1.176 my $dx = $ev->{x} - $ox;
1103     my $dy = $ev->{y} - $oy;
1104 root 1.77
1105 root 1.256 $self->{force_w} = $bw + $dx * ($mx ? -1 : 1);
1106     $self->{force_h} = $bh + $dy * ($my ? -1 : 1);
1107    
1108 root 1.251 $self->realloc;
1109 root 1.256 $self->move_abs ($wx + $dx * $mx, $wy + $dy * $my);
1110 root 1.77 };
1111    
1112 root 1.176 } elsif ($lr ^ $td) {
1113 root 1.139 my ($ox, $oy) = ($ev->{x}, $ev->{y});
1114 root 1.77 my ($bx, $by) = ($self->{x}, $self->{y});
1115    
1116     $self->{motion} = sub {
1117     my ($ev, $x, $y) = @_;
1118    
1119 root 1.139 ($x, $y) = ($ev->{x}, $ev->{y});
1120 root 1.77
1121 root 1.256 $self->move_abs ($bx + $x - $ox, $by + $y - $oy);
1122 root 1.77 };
1123     }
1124     }
1125    
1126     sub button_up {
1127     my ($self, $ev, $x, $y) = @_;
1128    
1129     delete $self->{motion};
1130     }
1131    
1132     sub mouse_motion {
1133     my ($self, $ev, $x, $y) = @_;
1134    
1135     $self->{motion}->($ev, $x, $y) if $self->{motion};
1136     }
1137    
1138 elmex 1.34 sub _draw {
1139     my ($self) = @_;
1140    
1141 root 1.97 my ($w, $h ) = ($self->{w}, $self->{h});
1142 root 1.43 my ($cw, $ch) = ($self->child->{w}, $self->child->{h});
1143 elmex 1.34
1144     glEnable GL_TEXTURE_2D;
1145 root 1.97 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE;
1146 elmex 1.34
1147 root 1.134 my $border = $self->border;
1148    
1149 root 1.97 glColor @{ $self->{border_bg} };
1150 root 1.255 $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 elmex 1.34
1155 root 1.177 if (@{$self->{bg}} < 4 || $self->{bg}[3]) {
1156 root 1.255 glColor @{ $self->{bg} };
1157 root 1.76
1158 root 1.177 # TODO: repeat texture not scale
1159 root 1.255 # solve this better(?)
1160     $bg->{s} = $cw / $bg->{w};
1161     $bg->{t} = $ch / $bg->{h};
1162 root 1.197 $bg->draw_quad_alpha ($border, $border, $cw, $ch);
1163     }
1164 elmex 1.34
1165 root 1.197 glDisable GL_TEXTURE_2D;
1166 elmex 1.36
1167 root 1.141 $self->{title}->draw if $self->{title};
1168 root 1.177
1169 root 1.39 $self->child->draw;
1170 elmex 1.34 }
1171 elmex 1.31
1172 root 1.39 #############################################################################
1173    
1174 root 1.73 package CFClient::UI::Table;
1175 elmex 1.15
1176 root 1.73 our @ISA = CFClient::UI::Base::;
1177 elmex 1.15
1178 root 1.75 use List::Util qw(max sum);
1179    
1180 root 1.138 use CFClient::OpenGL;
1181 elmex 1.15
1182 root 1.78 sub new {
1183     my $class = shift;
1184    
1185     $class->SUPER::new (
1186     col_expand => [],
1187 root 1.234 @_,
1188 root 1.78 )
1189     }
1190    
1191 root 1.236 sub children {
1192     grep $_, map @$_, grep $_, @{ $_[0]{children} }
1193     }
1194    
1195 elmex 1.15 sub add {
1196 root 1.113 my ($self, $x, $y, $child) = @_;
1197 elmex 1.32
1198 root 1.113 $child->set_parent ($self);
1199     $self->{children}[$y][$x] = $child;
1200 root 1.75
1201 root 1.251 $self->realloc;
1202 root 1.172 }
1203    
1204 root 1.236 # TODO: move to container class maybe? send children a signal on removal?
1205 root 1.115 sub clear {
1206     my ($self) = @_;
1207    
1208 root 1.172 my @children = $self->children;
1209     delete $self->{children};
1210 root 1.163
1211 root 1.172 for (@children) {
1212 root 1.163 delete $_->{parent};
1213     $_->hide;
1214     }
1215    
1216 root 1.251 $self->realloc;
1217 root 1.115 }
1218    
1219 root 1.75 sub get_wh {
1220     my ($self) = @_;
1221    
1222     my (@w, @h);
1223 elmex 1.15
1224 root 1.75 for my $y (0 .. $#{$self->{children}}) {
1225     my $row = $self->{children}[$y]
1226     or next;
1227 elmex 1.15
1228 root 1.75 for my $x (0 .. $#$row) {
1229     my $widget = $row->[$x]
1230     or next;
1231 root 1.149 my ($w, $h) = @$widget{qw(req_w req_h)};
1232 elmex 1.15
1233 root 1.75 $w[$x] = max $w[$x], $w;
1234     $h[$y] = max $h[$y], $h;
1235 elmex 1.17 }
1236 elmex 1.15 }
1237 root 1.75
1238     (\@w, \@h)
1239 elmex 1.15 }
1240    
1241     sub size_request {
1242     my ($self) = @_;
1243    
1244 root 1.75 my ($ws, $hs) = $self->get_wh;
1245 elmex 1.15
1246 root 1.75 (
1247 root 1.78 (sum @$ws),
1248     (sum @$hs),
1249 root 1.75 )
1250     }
1251    
1252     sub size_allocate {
1253 root 1.259 my ($self, $w, $h) = @_;
1254 root 1.75
1255     my ($ws, $hs) = $self->get_wh;
1256    
1257 root 1.238 my $req_w = (sum @$ws) || 1;
1258     my $req_h = (sum @$hs) || 1;
1259 root 1.78
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;
1264 elmex 1.15
1265 root 1.75 # linearly scale sizes
1266 root 1.78 $ws->[$_] += $col_expand[$_] / $col_expand * ($w - $req_w) for 0 .. $#$ws;
1267     $hs->[$_] *= 1 * $h / $req_h for 0 .. $#$hs;
1268 elmex 1.15
1269 root 1.112 CFClient::UI::harmonize $ws;
1270     CFClient::UI::harmonize $hs;
1271 root 1.106
1272 root 1.75 my $y;
1273 elmex 1.15
1274 root 1.75 for my $r (0 .. $#{$self->{children}}) {
1275     my $row = $self->{children}[$r]
1276     or next;
1277 elmex 1.15
1278     my $x = 0;
1279 root 1.75 my $row_h = $hs->[$r];
1280    
1281     for my $c (0 .. $#$row) {
1282     my $col_w = $ws->[$c];
1283 elmex 1.15
1284 root 1.83 if (my $widget = $row->[$c]) {
1285 root 1.128 $widget->configure ($x, $y, $col_w, $row_h);
1286 root 1.83 }
1287 elmex 1.15
1288 root 1.75 $x += $col_w;
1289 elmex 1.15 }
1290    
1291 root 1.75 $y += $row_h;
1292     }
1293    
1294     }
1295    
1296 root 1.76 sub find_widget {
1297     my ($self, $x, $y) = @_;
1298    
1299     $x -= $self->{x};
1300     $y -= $self->{y};
1301    
1302     my $res;
1303    
1304     for (grep $_, map @$_, grep $_, @{ $self->{children} }) {
1305     $res = $_->find_widget ($x, $y)
1306     and return $res;
1307     }
1308    
1309     $self->SUPER::find_widget ($x + $self->{x}, $y + $self->{y})
1310     }
1311    
1312 root 1.75 sub _draw {
1313     my ($self) = @_;
1314    
1315     for (grep $_, @{$self->{children}}) {
1316     $_->draw for grep $_, @$_;
1317 elmex 1.15 }
1318     }
1319    
1320 root 1.39 #############################################################################
1321    
1322 root 1.246 package CFClient::UI::Box;
1323 root 1.76
1324     our @ISA = CFClient::UI::Container::;
1325    
1326     sub size_request {
1327     my ($self) = @_;
1328    
1329 root 1.246 $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 root 1.76 }
1339    
1340     sub size_allocate {
1341 root 1.259 my ($self, $w, $h) = @_;
1342 root 1.76
1343 root 1.246 my $space = $self->{vertical} ? $h : $w;
1344 root 1.76 my $children = $self->{children};
1345    
1346 root 1.247 my @req;
1347 root 1.76
1348 root 1.247 if ($self->{homogeneous}) {
1349     @req = ($space / (@$children || 1)) x @$children;
1350 root 1.76 } else {
1351 root 1.247 @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 root 1.76 }
1366    
1367 root 1.246 CFClient::UI::harmonize \@req;
1368 root 1.112
1369 root 1.246 my $pos = 0;
1370 root 1.76 for (0 .. $#$children) {
1371 root 1.246 my $alloc = $req[$_];
1372     $children->[$_]->configure ($self->{vertical} ? (0, $pos, $w, $alloc) : ($pos, 0, $alloc, $h));
1373 root 1.76
1374 root 1.246 $pos += $alloc;
1375 root 1.76 }
1376 root 1.125
1377     1
1378 root 1.76 }
1379    
1380     #############################################################################
1381    
1382 root 1.246 package CFClient::UI::HBox;
1383 elmex 1.15
1384 root 1.246 our @ISA = CFClient::UI::Box::;
1385 root 1.76
1386 root 1.246 sub new {
1387     my $class = shift;
1388 elmex 1.15
1389 root 1.246 $class->SUPER::new (
1390     vertical => 0,
1391     @_,
1392 root 1.43 )
1393     }
1394    
1395 root 1.246 #############################################################################
1396 root 1.68
1397 root 1.246 package CFClient::UI::VBox;
1398 root 1.193
1399 root 1.246 our @ISA = CFClient::UI::Box::;
1400 root 1.68
1401 root 1.246 sub new {
1402     my $class = shift;
1403 root 1.68
1404 root 1.246 $class->SUPER::new (
1405     vertical => 1,
1406     @_,
1407     )
1408 elmex 1.36 }
1409    
1410 root 1.39 #############################################################################
1411    
1412 root 1.73 package CFClient::UI::Label;
1413 root 1.10
1414 root 1.209 our @ISA = CFClient::UI::DrawBG::;
1415 root 1.12
1416 root 1.138 use CFClient::OpenGL;
1417 root 1.10
1418     sub new {
1419 root 1.64 my ($class, %arg) = @_;
1420 root 1.51
1421 root 1.59 my $self = $class->SUPER::new (
1422 root 1.164 fg => [1, 1, 1],
1423 root 1.209 #bg => none
1424     #active_bg => none
1425 root 1.164 #font => default_font
1426 root 1.194 #text => initial text
1427     #markup => initial narkup
1428 root 1.213 #max_w => maximum pixel width
1429     ellipsise => 3, # end
1430 root 1.194 layout => (new CFClient::Layout),
1431 root 1.164 fontsize => 1,
1432     align => -1,
1433     valign => -1,
1434 root 1.258 padding_x => 2,
1435     padding_y => 2,
1436 elmex 1.150 can_events => 0,
1437 root 1.64 %arg
1438 root 1.59 );
1439 root 1.10
1440 root 1.141 if (exists $self->{template}) {
1441     my $layout = new CFClient::Layout;
1442     $layout->set_text (delete $self->{template});
1443     $self->{template} = $layout;
1444     }
1445 root 1.121
1446 root 1.194 if (exists $self->{markup}) {
1447     $self->set_markup (delete $self->{markup});
1448     } else {
1449     $self->set_text (delete $self->{text});
1450     }
1451 root 1.10
1452     $self
1453     }
1454    
1455 root 1.209 sub escape($) {
1456     local $_ = $_[0];
1457 root 1.68
1458     s/&/&amp;/g;
1459     s/>/&gt;/g;
1460     s/</&lt;/g;
1461    
1462 root 1.209 $_
1463 root 1.68 }
1464    
1465 root 1.173 sub update {
1466     my ($self) = @_;
1467    
1468     delete $self->{texture};
1469     $self->SUPER::update;
1470     }
1471    
1472 elmex 1.15 sub set_text {
1473     my ($self, $text) = @_;
1474 root 1.28
1475 root 1.173 return if $self->{text} eq "T$text";
1476     $self->{text} = "T$text";
1477    
1478 root 1.194 $self->{layout} = new CFClient::Layout if $self->{layout}->is_rgba;
1479 root 1.121 $self->{layout}->set_text ($text);
1480 root 1.28
1481 root 1.251 $self->realloc;
1482 root 1.252 $self->update;
1483 elmex 1.15 }
1484    
1485 root 1.121 sub set_markup {
1486     my ($self, $markup) = @_;
1487    
1488 root 1.173 return if $self->{text} eq "M$markup";
1489     $self->{text} = "M$markup";
1490    
1491 root 1.194 my $rgba = $markup =~ /span.*(?:foreground|background)/;
1492    
1493     $self->{layout} = new CFClient::Layout $rgba if $self->{layout}->is_rgba != $rgba;
1494 root 1.121 $self->{layout}->set_markup ($markup);
1495 root 1.28
1496 root 1.251 $self->realloc;
1497 root 1.252 $self->update;
1498 elmex 1.15 }
1499    
1500 root 1.14 sub size_request {
1501     my ($self) = @_;
1502    
1503 root 1.157 $self->{layout}->set_font ($self->{font}) if $self->{font};
1504     $self->{layout}->set_width ($self->{max_w} || -1);
1505 root 1.213 $self->{layout}->set_ellipsise ($self->{ellipsise});
1506     $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1507 root 1.134 $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1508 root 1.121
1509 root 1.76 my ($w, $h) = $self->{layout}->size;
1510    
1511 root 1.141 if (exists $self->{template}) {
1512 root 1.157 $self->{template}->set_font ($self->{font}) if $self->{font};
1513 root 1.141 $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 root 1.258 ($w, $h)
1522 root 1.59 }
1523 root 1.51
1524 root 1.59 sub size_allocate {
1525 root 1.259 my ($self, $w, $h) = @_;
1526 root 1.68
1527 root 1.259 delete $self->{texture};
1528 root 1.14 }
1529    
1530 elmex 1.146 sub set_fontsize {
1531     my ($self, $fontsize) = @_;
1532    
1533     $self->{fontsize} = $fontsize;
1534 root 1.152 delete $self->{texture};
1535 root 1.186
1536 root 1.251 $self->realloc;
1537 elmex 1.146 }
1538    
1539 elmex 1.11 sub _draw {
1540 root 1.10 my ($self) = @_;
1541    
1542 root 1.209 $self->SUPER::_draw; # draw background, if applicable
1543    
1544 root 1.59 my $tex = $self->{texture} ||= do {
1545 root 1.194 $self->{layout}->set_foreground (@{$self->{fg}});
1546 root 1.157 $self->{layout}->set_font ($self->{font}) if $self->{font};
1547 root 1.59 $self->{layout}->set_width ($self->{w});
1548 root 1.213 $self->{layout}->set_ellipsise ($self->{ellipsise});
1549     $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1550     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1551 root 1.194
1552     my $tex = new_from_layout CFClient::Texture $self->{layout};
1553    
1554 root 1.258 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x}
1555     : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding_x}
1556 root 1.208 : ($self->{w} - $tex->{w}) * 0.5);
1557    
1558 root 1.258 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y}
1559     : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding_y}
1560 root 1.208 : ($self->{h} - $tex->{h}) * 0.5);
1561 root 1.194
1562     $tex
1563 root 1.59 };
1564 root 1.10
1565     glEnable GL_TEXTURE_2D;
1566 root 1.105 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1567 root 1.10
1568 root 1.194 if ($tex->{format} == GL_ALPHA) {
1569     glColor @{$self->{fg}};
1570     $tex->draw_quad_alpha ($self->{ox}, $self->{oy});
1571     } else {
1572     $tex->draw_quad_alpha_premultiplied ($self->{ox}, $self->{oy});
1573     }
1574 root 1.10
1575 root 1.74 glDisable GL_TEXTURE_2D;
1576 root 1.10 }
1577    
1578 root 1.39 #############################################################################
1579    
1580 root 1.121 package CFClient::UI::EntryBase;
1581 elmex 1.31
1582 root 1.73 our @ISA = CFClient::UI::Label::;
1583 elmex 1.31
1584 root 1.138 use CFClient::OpenGL;
1585 elmex 1.31
1586 root 1.68 sub new {
1587     my $class = shift;
1588    
1589     $class->SUPER::new (
1590 root 1.164 fg => [1, 1, 1],
1591     bg => [0, 0, 0, 0.2],
1592     active_bg => [1, 1, 1, 0.5],
1593     active_fg => [0, 0, 0],
1594     can_hover => 1,
1595     can_focus => 1,
1596     valign => 0,
1597 elmex 1.150 can_events => 1,
1598 root 1.225 #text => ...
1599 root 1.68 @_
1600     )
1601     }
1602    
1603     sub _set_text {
1604     my ($self, $text) = @_;
1605    
1606 root 1.121 delete $self->{cur_h};
1607    
1608     return if $self->{text} eq $text;
1609 elmex 1.100
1610 root 1.134 delete $self->{texture};
1611    
1612 root 1.68 $self->{last_activity} = $::NOW;
1613     $self->{text} = $text;
1614 root 1.72
1615     $text =~ s/./*/g if $self->{hidden};
1616 root 1.121 $self->{layout}->set_text ("$text ");
1617 root 1.72
1618 root 1.231 $self->_emit (changed => $self->{text});
1619 root 1.121 }
1620 root 1.68
1621 root 1.194 sub set_text {
1622     my ($self, $text) = @_;
1623    
1624     $self->{cursor} = length $text;
1625     $self->_set_text ($text);
1626 root 1.251
1627     $self->realloc;
1628 root 1.194 }
1629    
1630 root 1.121 sub get_text {
1631     $_[0]{text}
1632 root 1.68 }
1633    
1634     sub size_request {
1635     my ($self) = @_;
1636    
1637     my ($w, $h) = $self->SUPER::size_request;
1638    
1639     ($w + 1, $h) # add 1 for cursor
1640     }
1641    
1642 elmex 1.31 sub key_down {
1643     my ($self, $ev) = @_;
1644    
1645 root 1.137 my $mod = $ev->{mod};
1646     my $sym = $ev->{sym};
1647     my $uni = $ev->{unicode};
1648 elmex 1.31
1649     my $text = $self->get_text;
1650    
1651 root 1.200 if ($uni == 8) {
1652 root 1.68 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1653 root 1.200 } elsif ($uni == 127) {
1654 root 1.68 substr $text, $self->{cursor}, 1, "";
1655 root 1.136 } elsif ($sym == CFClient::SDLK_LEFT) {
1656 root 1.68 --$self->{cursor} if $self->{cursor};
1657 root 1.136 } elsif ($sym == CFClient::SDLK_RIGHT) {
1658 root 1.68 ++$self->{cursor} if $self->{cursor} < length $self->{text};
1659 root 1.136 } elsif ($sym == CFClient::SDLK_HOME) {
1660 root 1.76 $self->{cursor} = 0;
1661 root 1.136 } elsif ($sym == CFClient::SDLK_END) {
1662 root 1.76 $self->{cursor} = length $text;
1663 root 1.200 } elsif ($uni == 27) {
1664 root 1.231 $self->_emit ('escape');
1665 elmex 1.31 } elsif ($uni) {
1666 root 1.68 substr $text, $self->{cursor}++, 0, chr $uni;
1667 elmex 1.31 }
1668 root 1.51
1669 root 1.68 $self->_set_text ($text);
1670 root 1.251
1671     $self->realloc;
1672 root 1.68 }
1673    
1674     sub focus_in {
1675     my ($self) = @_;
1676    
1677     $self->{last_activity} = $::NOW;
1678    
1679     $self->SUPER::focus_in;
1680 elmex 1.31 }
1681    
1682 root 1.51 sub button_down {
1683 root 1.68 my ($self, $ev, $x, $y) = @_;
1684    
1685     $self->SUPER::button_down ($ev, $x, $y);
1686    
1687     my $idx = $self->{layout}->xy_to_index ($x, $y);
1688    
1689     # byte-index to char-index
1690 root 1.76 my $text = $self->{text};
1691 root 1.68 utf8::encode $text;
1692     $self->{cursor} = length substr $text, 0, $idx;
1693 root 1.51
1694 root 1.68 $self->_set_text ($self->{text});
1695     $self->update;
1696 root 1.51 }
1697    
1698 root 1.58 sub mouse_motion {
1699     my ($self, $ev, $x, $y) = @_;
1700 root 1.68 # printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d#
1701 root 1.58 }
1702    
1703 root 1.51 sub _draw {
1704     my ($self) = @_;
1705    
1706 root 1.68 local $self->{fg} = $self->{fg};
1707    
1708 root 1.51 if ($FOCUS == $self) {
1709 root 1.68 glColor @{$self->{active_bg}};
1710     $self->{fg} = $self->{active_fg};
1711 root 1.51 } else {
1712 root 1.68 glColor @{$self->{bg}};
1713 root 1.51 }
1714    
1715 root 1.76 glEnable GL_BLEND;
1716     glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1717 root 1.51 glBegin GL_QUADS;
1718 root 1.68 glVertex 0 , 0;
1719     glVertex 0 , $self->{h};
1720     glVertex $self->{w}, $self->{h};
1721     glVertex $self->{w}, 0;
1722 root 1.51 glEnd;
1723 root 1.76 glDisable GL_BLEND;
1724 root 1.51
1725     $self->SUPER::_draw;
1726 root 1.68
1727     #TODO: force update every cursor change :(
1728     if ($FOCUS == $self && (($::NOW - $self->{last_activity}) & 1023) < 600) {
1729 root 1.121
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    
1737 root 1.68 glColor @{$self->{fg}};
1738     glBegin GL_LINES;
1739 root 1.122 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};
1741 root 1.68 glEnd;
1742     }
1743     }
1744    
1745 root 1.121 package CFClient::UI::Entry;
1746 elmex 1.99
1747 root 1.121 our @ISA = CFClient::UI::EntryBase::;
1748 elmex 1.99
1749 root 1.138 use CFClient::OpenGL;
1750 elmex 1.99
1751     sub key_down {
1752     my ($self, $ev) = @_;
1753    
1754 root 1.137 my $sym = $ev->{sym};
1755 elmex 1.99
1756 root 1.136 if ($sym == 13) {
1757 elmex 1.167 unshift @{$self->{history}},
1758     my $txt = $self->get_text;
1759     $self->{history_pointer} = -1;
1760 elmex 1.169 $self->{history_saveback} = '';
1761 root 1.231 $self->_emit (activate => $txt);
1762 elmex 1.99 $self->update;
1763    
1764 elmex 1.167 } elsif ($sym == CFClient::SDLK_UP) {
1765     if ($self->{history_pointer} < 0) {
1766     $self->{history_saveback} = $self->get_text;
1767     }
1768 elmex 1.169 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 elmex 1.167 }
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 elmex 1.99 } else {
1787     $self->SUPER::key_down ($ev);
1788     }
1789    
1790     }
1791    
1792 root 1.68 #############################################################################
1793    
1794 root 1.79 package CFClient::UI::Button;
1795    
1796     our @ISA = CFClient::UI::Label::;
1797    
1798 root 1.138 use CFClient::OpenGL;
1799 root 1.79
1800 elmex 1.85 my @tex =
1801 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1802 elmex 1.85 qw(b1_button_active.png);
1803    
1804 root 1.79 sub new {
1805     my $class = shift;
1806    
1807     $class->SUPER::new (
1808 root 1.258 padding_x => 4,
1809     padding_y => 4,
1810 root 1.164 fg => [1, 1, 1],
1811     active_fg => [0, 0, 1],
1812     can_hover => 1,
1813     align => 0,
1814     valign => 0,
1815 elmex 1.150 can_events => 1,
1816 root 1.79 @_
1817     )
1818     }
1819    
1820 root 1.231 sub activate { }
1821    
1822 root 1.79 sub button_up {
1823     my ($self, $ev, $x, $y) = @_;
1824    
1825 root 1.231 $self->emit ("activate")
1826     if $x >= 0 && $x < $self->{w}
1827     && $y >= 0 && $y < $self->{h};
1828 root 1.79 }
1829    
1830     sub _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 root 1.119 glEnable GL_TEXTURE_2D;
1840 elmex 1.85 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1841 root 1.119 glColor 0, 0, 0, 1;
1842 elmex 1.85
1843 root 1.195 $tex[0]->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
1844 elmex 1.85
1845     glDisable GL_TEXTURE_2D;
1846 root 1.79
1847     $self->SUPER::_draw;
1848     }
1849    
1850     #############################################################################
1851    
1852 root 1.86 package CFClient::UI::CheckBox;
1853    
1854     our @ISA = CFClient::UI::DrawBG::;
1855    
1856 elmex 1.102 my @tex =
1857 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1858 elmex 1.102 qw(c1_checkbox_bg.png c1_checkbox_active.png);
1859    
1860 root 1.138 use CFClient::OpenGL;
1861 root 1.86
1862     sub new {
1863     my $class = shift;
1864    
1865     $class->SUPER::new (
1866 root 1.258 padding_x => 2,
1867     padding_y => 2,
1868 root 1.86 fg => [1, 1, 1],
1869     active_fg => [1, 1, 0],
1870 root 1.209 bg => [0, 0, 0, 0.2],
1871     active_bg => [1, 1, 1, 0.5],
1872 root 1.86 state => 0,
1873 root 1.97 can_hover => 1,
1874 root 1.86 @_
1875     )
1876     }
1877    
1878 root 1.87 sub size_request {
1879     my ($self) = @_;
1880    
1881 root 1.258 (6) x 2
1882 root 1.87 }
1883    
1884 root 1.86 sub button_down {
1885     my ($self, $ev, $x, $y) = @_;
1886    
1887 root 1.258 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
1888     && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
1889 root 1.86 $self->{state} = !$self->{state};
1890 root 1.231 $self->_emit (changed => $self->{state});
1891 root 1.86 }
1892     }
1893    
1894     sub _draw {
1895     my ($self) = @_;
1896    
1897 root 1.87 $self->SUPER::_draw;
1898 root 1.86
1899 root 1.258 glTranslate $self->{padding_x} + 0.375, $self->{padding_y} + 0.375, 0;
1900 root 1.86
1901 root 1.258 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 elmex 1.102
1905 root 1.87 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };
1906 root 1.86
1907 elmex 1.102 my $tex = $self->{state} ? $tex[1] : $tex[0];
1908    
1909 root 1.197 glEnable GL_TEXTURE_2D;
1910 root 1.195 $tex->draw_quad_alpha (0, 0, $s, $s);
1911 elmex 1.102 glDisable GL_TEXTURE_2D;
1912 root 1.86 }
1913    
1914     #############################################################################
1915    
1916 elmex 1.145 package CFClient::UI::Image;
1917    
1918     our @ISA = CFClient::UI::Base::;
1919    
1920     use CFClient::OpenGL;
1921     use Carp qw/confess/;
1922    
1923     our %loaded_images;
1924    
1925     sub new {
1926     my $class = shift;
1927    
1928 elmex 1.150 my $self = $class->SUPER::new (can_events => 0, @_);
1929 elmex 1.145
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 root 1.147 Scalar::Util::weaken $loaded_images{$self->{image}};
1938    
1939 elmex 1.145 $self->{aspect} = $tex->{w} / $tex->{h};
1940    
1941     $self
1942     }
1943    
1944     sub size_request {
1945     my ($self) = @_;
1946    
1947     ($self->{tex}->{w}, $self->{tex}->{h})
1948     }
1949    
1950     sub _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 root 1.195 $tex->draw_quad_alpha (0, 0, $w, $h);
1968 elmex 1.145
1969     glDisable GL_TEXTURE_2D;
1970     }
1971    
1972     #############################################################################
1973    
1974 elmex 1.124 package CFClient::UI::VGauge;
1975    
1976     our @ISA = CFClient::UI::Base::;
1977    
1978 root 1.158 use List::Util qw(min max);
1979    
1980 root 1.138 use CFClient::OpenGL;
1981 elmex 1.124
1982     my %tex = (
1983     food => [
1984 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1985 elmex 1.124 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/
1986     ],
1987     grace => [
1988 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1989 root 1.158 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/
1990 elmex 1.124 ],
1991     hp => [
1992 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1993 elmex 1.124 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/
1994     ],
1995     mana => [
1996 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1997 root 1.158 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/
1998 elmex 1.124 ],
1999     );
2000    
2001     # eg. VGauge->new (gauge => 'food'), default gauge: food
2002     sub new {
2003     my $class = shift;
2004    
2005 root 1.140 my $self = $class->SUPER::new (
2006 root 1.141 type => 'food',
2007 root 1.140 @_
2008     );
2009    
2010 root 1.141 $self->{aspect} = $tex{$self->{type}}[0]{w} / $tex{$self->{type}}[0]{h};
2011 elmex 1.124
2012     $self
2013     }
2014    
2015     sub size_request {
2016     my ($self) = @_;
2017    
2018 root 1.143 #my $tex = $tex{$self->{type}}[0];
2019     #@$tex{qw(w h)}
2020     (0, 0)
2021 elmex 1.124 }
2022    
2023     sub set_max {
2024     my ($self, $max) = @_;
2025 root 1.127
2026 root 1.173 return if $self->{max_val} == $max;
2027    
2028 elmex 1.124 $self->{max_val} = $max;
2029 root 1.173 $self->update;
2030 elmex 1.124 }
2031    
2032     sub set_value {
2033     my ($self, $val, $max) = @_;
2034    
2035     $self->set_max ($max)
2036     if defined $max;
2037    
2038 root 1.173 return if $self->{val} == $val;
2039    
2040 elmex 1.124 $self->{val} = $val;
2041     $self->update;
2042     }
2043    
2044     sub _draw {
2045     my ($self) = @_;
2046    
2047 root 1.141 my $tex = $tex{$self->{type}};
2048 root 1.158 my ($t1, $t2, $t3) = @$tex;
2049 elmex 1.124
2050     my ($w, $h) = ($self->{w}, $self->{h});
2051    
2052 elmex 1.142 if ($self->{vertical}) {
2053     glRotate 90, 0, 0, 1;
2054     glTranslate 0, -$self->{w}, 0;
2055    
2056     ($w, $h) = ($h, $w);
2057     }
2058    
2059 elmex 1.124 my $ycut = $self->{val} / ($self->{max_val} || 1);
2060    
2061 root 1.158 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 elmex 1.124
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 root 1.131 glBindTexture GL_TEXTURE_2D, $t1->{name};
2073     glBegin GL_QUADS;
2074 root 1.158 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 root 1.131 glEnd;
2079 elmex 1.124
2080 root 1.158 my $ycut1 = List::Util::min 1, $ycut;
2081 root 1.131 glBindTexture GL_TEXTURE_2D, $t2->{name};
2082     glBegin GL_QUADS;
2083 root 1.158 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 root 1.131 glEnd;
2088 elmex 1.124
2089 root 1.158 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 elmex 1.124 glDisable GL_BLEND;
2100     glDisable GL_TEXTURE_2D;
2101     }
2102    
2103     #############################################################################
2104    
2105 root 1.141 package CFClient::UI::Gauge;
2106    
2107     our @ISA = CFClient::UI::VBox::;
2108    
2109     sub new {
2110 root 1.151 my ($class, %arg) = @_;
2111 root 1.141
2112     my $self = $class->SUPER::new (
2113 root 1.171 tooltip => $arg{type},
2114     can_hover => 1,
2115     can_events => 1,
2116 root 1.151 %arg,
2117 root 1.141 );
2118    
2119 root 1.161 $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 root 1.141
2123     $self
2124     }
2125    
2126 elmex 1.146 sub set_fontsize {
2127     my ($self, $fsize) = @_;
2128    
2129     $self->{value}->set_fontsize ($fsize);
2130     $self->{max} ->set_fontsize ($fsize);
2131     }
2132    
2133 root 1.173 sub set_max {
2134     my ($self, $max) = @_;
2135    
2136     $self->{gauge}->set_max ($max);
2137     $self->{max}->set_text ($max);
2138     }
2139    
2140 root 1.141 sub 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    
2152 root 1.73 package CFClient::UI::Slider;
2153 root 1.68
2154     use strict;
2155    
2156 root 1.138 use CFClient::OpenGL;
2157 root 1.68
2158 root 1.73 our @ISA = CFClient::UI::DrawBG::;
2159 root 1.68
2160 elmex 1.99 my @tex =
2161     map { new_from_file CFClient::Texture CFClient::find_rcfile $_ }
2162     qw(s1_slider.png s1_slider_bg.png);
2163    
2164 root 1.68 sub new {
2165     my $class = shift;
2166    
2167 root 1.206 # range [value, low, high, page, unit]
2168 root 1.68
2169 root 1.97 # TODO: 0-width page
2170     # TODO: req_w/h are wrong with vertical
2171     # TODO: calculations are off
2172 root 1.76 my $self = $class->SUPER::new (
2173 root 1.68 fg => [1, 1, 1],
2174     active_fg => [0, 0, 0],
2175 root 1.209 bg => [0, 0, 0, 0.2],
2176     active_bg => [1, 1, 1, 0.5],
2177 root 1.227 range => [0, 0, 100, 10, 0],
2178 root 1.257 min_w => $::WIDTH / 80,
2179     min_h => $::WIDTH / 80,
2180 root 1.76 vertical => 0,
2181 root 1.97 can_hover => 1,
2182 root 1.217 inner_pad => 0.02,
2183 root 1.68 @_
2184 root 1.76 );
2185    
2186 root 1.206 $self->set_value ($self->{range}[0]);
2187     $self->update;
2188    
2189 root 1.76 $self
2190     }
2191    
2192 root 1.251 sub changed { }
2193    
2194 root 1.225 sub set_range {
2195     my ($self, $range) = @_;
2196    
2197 root 1.239 ($range, $self->{range}) = ($self->{range}, $range);
2198 root 1.225
2199 root 1.239 $self->update
2200     if "@$range" ne "@{$self->{range}}";
2201 root 1.225 }
2202    
2203 root 1.206 sub 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 root 1.227 $page = $hi - $lo if $page > $hi - $lo;
2211    
2212     $value = $lo if $value < $lo;
2213     $value = $hi - $page if $value > $hi - $page;
2214 root 1.206
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 root 1.231 $self->_emit (changed => $value);
2222 root 1.206 $self->update;
2223     }
2224     }
2225    
2226 root 1.76 sub size_request {
2227     my ($self) = @_;
2228    
2229 root 1.257 ($self->{req_w}, $self->{req_h})
2230 root 1.68 }
2231    
2232 root 1.69 sub button_down {
2233     my ($self, $ev, $x, $y) = @_;
2234    
2235     $self->SUPER::button_down ($ev, $x, $y);
2236 root 1.227
2237     $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x];
2238    
2239 root 1.69 $self->mouse_motion ($ev, $x, $y);
2240     }
2241    
2242     sub mouse_motion {
2243     my ($self, $ev, $x, $y) = @_;
2244    
2245     if ($GRAB == $self) {
2246 root 1.71 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w});
2247    
2248 root 1.206 my (undef, $lo, $hi, $page) = @{$self->{range}};
2249 elmex 1.103
2250 root 1.227 $x = ($x - $self->{click}[1]) / ($w * $self->{scale});
2251 root 1.69
2252 root 1.227 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo));
2253 root 1.69 }
2254     }
2255    
2256 root 1.206 sub update {
2257     my ($self) = @_;
2258    
2259     $CFClient::UI::ROOT->on_post_alloc ($self => sub {
2260     $self->set_value ($self->{range}[0]);
2261    
2262     my ($value, $lo, $hi, $page) = @{$self->{range}};
2263 root 1.227 my $range = ($hi - $page - $lo) || 1e-100;
2264 root 1.206
2265 root 1.227 my $knob_w = List::Util::min 1, $page / ($hi - $lo) || 0.1;
2266 root 1.206
2267 root 1.227 $self->{offset} = List::Util::max $self->{inner_pad}, $knob_w * 0.5;
2268     $self->{scale} = 1 - 2 * $self->{offset} || 1e-100;
2269 root 1.206
2270 root 1.227 $value = ($value - $lo) / $range;
2271     $value = $value * $self->{scale} + $self->{offset};
2272 root 1.206
2273 root 1.227 $self->{knob_x} = $value - $knob_w * 0.5;
2274     $self->{knob_w} = $knob_w;
2275 root 1.206 });
2276    
2277     $self->SUPER::update;
2278 elmex 1.103 }
2279    
2280 root 1.68 sub _draw {
2281     my ($self) = @_;
2282    
2283     $self->SUPER::_draw ();
2284    
2285 root 1.206 glScale $self->{w}, $self->{h};
2286 root 1.68
2287     if ($self->{vertical}) {
2288     # draw a vertical slider like a rotated horizontal slider
2289    
2290 root 1.214 glTranslate 1, 0, 0;
2291 root 1.68 glRotate 90, 0, 0, 1;
2292     }
2293    
2294     my $fg = $FOCUS == $self ? $self->{active_fg} : $self->{fg};
2295     my $bg = $FOCUS == $self ? $self->{active_bg} : $self->{bg};
2296    
2297 elmex 1.99 glEnable GL_TEXTURE_2D;
2298     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2299    
2300     # draw background
2301 root 1.206 $tex[1]->draw_quad_alpha (0, 0, 1, 1);
2302 root 1.69
2303 elmex 1.99 # draw handle
2304 root 1.206 $tex[0]->draw_quad_alpha ($self->{knob_x}, 0, $self->{knob_w}, 1);
2305 root 1.69
2306 elmex 1.99 glDisable GL_TEXTURE_2D;
2307 root 1.51 }
2308    
2309 root 1.39 #############################################################################
2310    
2311 root 1.225 package CFClient::UI::ValSlider;
2312    
2313     our @ISA = CFClient::UI::HBox::;
2314    
2315     sub 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    
2347     sub set_range { shift->{slider}->set_range (@_) }
2348     sub set_value { shift->{slider}->set_value (@_) }
2349    
2350     #############################################################################
2351    
2352 root 1.97 package CFClient::UI::TextView;
2353    
2354     our @ISA = CFClient::UI::HBox::;
2355    
2356 root 1.138 use CFClient::OpenGL;
2357 root 1.97
2358     sub new {
2359     my $class = shift;
2360    
2361     my $self = $class->SUPER::new (
2362 root 1.164 fontsize => 1,
2363     can_events => 0,
2364     #font => default_font
2365 root 1.105 @_,
2366 root 1.164
2367 root 1.195 layout => (new CFClient::Layout 1),
2368 root 1.164 par => [],
2369     height => 0,
2370     children => [
2371 root 1.97 (new CFClient::UI::Empty expand => 1),
2372     (new CFClient::UI::Slider vertical => 1),
2373     ],
2374     );
2375    
2376 root 1.176 $self->{children}[1]->connect (changed => sub { $self->update });
2377 root 1.107
2378 root 1.97 $self
2379     }
2380    
2381 root 1.107 sub set_fontsize {
2382     my ($self, $fontsize) = @_;
2383    
2384     $self->{fontsize} = $fontsize;
2385     $self->reflow;
2386     }
2387    
2388 root 1.220 sub size_allocate {
2389 root 1.259 my ($self, $w, $h) = @_;
2390 root 1.220
2391 root 1.259 $self->SUPER::size_allocate ($w, $h);
2392 root 1.220
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    
2400 root 1.228 sub text_size {
2401 root 1.220 my ($self, $text, $indent) = @_;
2402 root 1.105
2403     my $layout = $self->{layout};
2404    
2405 root 1.134 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2406 root 1.220 $layout->set_width ($self->{children}[0]{w} - $indent);
2407 root 1.195 $layout->set_markup ($text);
2408 root 1.105
2409 root 1.228 $layout->size
2410 root 1.105 }
2411    
2412     sub reflow {
2413     my ($self) = @_;
2414    
2415 root 1.107 $self->{need_reflow}++;
2416     $self->update;
2417 root 1.105 }
2418    
2419 root 1.227 sub 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    
2426 root 1.226 sub clear {
2427     my ($self) = @_;
2428    
2429     $self->{par} = [];
2430     $self->{height} = 0;
2431 root 1.227 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]);
2432 root 1.226 }
2433    
2434 root 1.97 sub add_paragraph {
2435 root 1.220 my ($self, $color, $text, $indent) = @_;
2436 root 1.97
2437 root 1.220 for my $line (split /\n/, $text) {
2438 root 1.228 my ($w, $h) = $self->text_size ($line);
2439     $self->{height} += $h;
2440     push @{$self->{par}}, [$w + $indent, $h, $color, $indent, $line];
2441 root 1.220 }
2442 root 1.105
2443 root 1.227 $self->{children}[1]->set_range ([$self->{height}, 0, $self->{height}, $self->{h}, 1]);
2444 root 1.97 }
2445    
2446 root 1.105 sub update {
2447 root 1.97 my ($self) = @_;
2448    
2449 root 1.105 $self->SUPER::update;
2450    
2451     return unless $self->{h} > 0;
2452    
2453 root 1.107 delete $self->{texture};
2454    
2455 root 1.198 $ROOT->on_post_alloc ($self, sub {
2456 root 1.228 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
2457    
2458 root 1.107 if (delete $self->{need_reflow}) {
2459     my $height = 0;
2460    
2461 root 1.228 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 root 1.107
2477     $self->{height} = $height;
2478    
2479 root 1.228 $self->{children}[1]->set_range ([$height, 0, $height, $H, 1]);
2480 root 1.107
2481     delete $self->{texture};
2482     }
2483    
2484 root 1.228 $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub {
2485 root 1.215 glClearColor 0.5, 0.5, 0.5, 0;
2486 root 1.107 glClear GL_COLOR_BUFFER_BIT;
2487    
2488     my $top = int $self->{children}[1]{range}[0];
2489 root 1.105
2490 root 1.107 my $y0 = $top;
2491 root 1.228 my $y1 = $top + $H;
2492 root 1.105
2493 root 1.107 my $y = 0;
2494 root 1.97
2495 root 1.107 my $layout = $self->{layout};
2496 root 1.97
2497 root 1.157 $layout->set_font ($self->{font}) if $self->{font};
2498    
2499 root 1.220 glEnable GL_BLEND;
2500 root 1.228 #TODO# not correct in windows where rgba is forced off
2501 root 1.220 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2502    
2503 root 1.107 for my $par (@{$self->{par}}) {
2504 root 1.228 my $h = $par->[1];
2505 root 1.97
2506 root 1.107 if ($y0 < $y + $h && $y < $y1) {
2507 root 1.228 $layout->set_foreground (@{ $par->[2] });
2508     $layout->set_width ($W - $par->[3]);
2509     $layout->set_markup ($par->[4]);
2510 root 1.220
2511     my ($w, $h, $data, $format, $internalformat) = $layout->render;
2512 root 1.105
2513 root 1.228 glRasterPos $par->[3], $y - $y0;
2514 root 1.220 glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data;
2515 root 1.107 }
2516    
2517     $y += $h;
2518 root 1.105 }
2519    
2520 root 1.220 glDisable GL_BLEND;
2521 root 1.107 };
2522     });
2523 root 1.105 }
2524 root 1.97
2525 root 1.105 sub _draw {
2526     my ($self) = @_;
2527 root 1.97
2528 root 1.176 glEnable GL_TEXTURE_2D;
2529     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2530     glColor 1, 1, 1, 1;
2531 root 1.216 $self->{texture}->draw_quad_alpha (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h});
2532 root 1.176 glDisable GL_TEXTURE_2D;
2533 root 1.97
2534 root 1.106 $self->{children}[1]->draw;
2535    
2536 root 1.97 }
2537    
2538     #############################################################################
2539    
2540 root 1.73 package CFClient::UI::Animator;
2541 root 1.35
2542 root 1.138 use CFClient::OpenGL;
2543 root 1.35
2544 root 1.73 our @ISA = CFClient::UI::Bin::;
2545 root 1.35
2546     sub moveto {
2547     my ($self, $x, $y) = @_;
2548    
2549     $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
2550 root 1.56 $self->{speed} = 0.001;
2551 root 1.35 $self->{time} = 1;
2552    
2553     ::animation_start $self;
2554     }
2555    
2556     sub animate {
2557     my ($self, $interval) = @_;
2558    
2559     $self->{time} -= $interval * $self->{speed};
2560     if ($self->{time} <= 0) {
2561     $self->{time} = 0;
2562     ::animation_stop $self;
2563     }
2564    
2565     my ($x0, $y0, $x1, $y1) = @{$self->{moveto}};
2566    
2567     $self->{x} = $x0 * $self->{time} + $x1 * (1 - $self->{time});
2568     $self->{y} = $y0 * $self->{time} + $y1 * (1 - $self->{time});
2569     }
2570    
2571     sub _draw {
2572     my ($self) = @_;
2573    
2574     glPushMatrix;
2575 root 1.51 glRotate $self->{time} * 1000, 0, 1, 0;
2576 root 1.38 $self->{children}[0]->draw;
2577 root 1.35 glPopMatrix;
2578     }
2579    
2580 root 1.51 #############################################################################
2581    
2582 root 1.96 package CFClient::UI::Flopper;
2583    
2584     our @ISA = CFClient::UI::Button::;
2585    
2586     sub new {
2587     my $class = shift;
2588    
2589     my $self = $class->SUPER::new (
2590 root 1.243 state => 0,
2591     on_activate => \&toggle_flopper,
2592 root 1.96 @_
2593     );
2594    
2595     $self
2596     }
2597    
2598     sub toggle_flopper {
2599     my ($self) = @_;
2600    
2601 elmex 1.245 $self->{other}->toggle_visibility;
2602 root 1.96 }
2603    
2604     #############################################################################
2605    
2606 root 1.153 package CFClient::UI::Tooltip;
2607    
2608     our @ISA = CFClient::UI::Bin::;
2609    
2610     use CFClient::OpenGL;
2611    
2612     sub new {
2613     my $class = shift;
2614    
2615     $class->SUPER::new (
2616     @_,
2617     can_events => 0,
2618     )
2619     }
2620    
2621 root 1.196 sub set_tooltip_from {
2622     my ($self, $widget) = @_;
2623 root 1.195
2624 root 1.259 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 root 1.197 $self->add (new CFClient::UI::Label
2634 root 1.259 markup => $tooltip,
2635 root 1.213 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 root 1.197 );
2641 root 1.153 }
2642    
2643     sub size_request {
2644     my ($self) = @_;
2645    
2646     my ($w, $h) = @{$self->child}{qw(req_w req_h)};
2647    
2648 root 1.154 ($w + 4, $h + 4)
2649     }
2650    
2651 root 1.162 sub size_allocate {
2652 root 1.259 my ($self, $w, $h) = @_;
2653 root 1.162
2654 root 1.259 $self->SUPER::size_allocate ($w - 4, $h - 4);
2655 root 1.162 }
2656    
2657 root 1.253 sub visibility_change {
2658     my ($self, $visible) = @_;
2659    
2660     return unless $visible;
2661    
2662     $self->{root}->on_post_alloc ("move_$self" => sub {
2663 root 1.254 my $widget = $self->{owner}
2664     or return;
2665 root 1.253
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 root 1.256 $self->move_abs ($x, $y);
2672 root 1.253 });
2673     }
2674    
2675 root 1.154 sub _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 root 1.197 glTranslate 2 - 0.375, 2 - 0.375;
2699 root 1.252
2700 root 1.154 $self->SUPER::_draw;
2701 root 1.153 }
2702    
2703     #############################################################################
2704    
2705 root 1.162 package CFClient::UI::Face;
2706    
2707     our @ISA = CFClient::UI::Base::;
2708    
2709     use CFClient::OpenGL;
2710    
2711     sub new {
2712     my $class = shift;
2713    
2714 root 1.217 my $self = $class->SUPER::new (
2715 root 1.234 aspect => 1,
2716     can_events => 0,
2717 root 1.162 @_,
2718 root 1.217 );
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 root 1.162 }
2736    
2737     sub size_request {
2738     (32, 8)
2739     }
2740    
2741 root 1.222 sub update {
2742     my ($self) = @_;
2743    
2744     return unless $self->{visible};
2745    
2746     $self->SUPER::update;
2747     }
2748    
2749 elmex 1.179 sub _draw {
2750 root 1.162 my ($self) = @_;
2751    
2752 root 1.227 return unless $::CONN;
2753 root 1.162
2754 root 1.217 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 root 1.162 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 root 1.195 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2770 root 1.162 glDisable GL_TEXTURE_2D;
2771     }
2772     }
2773    
2774 root 1.217 sub DESTROY {
2775     my ($self) = @_;
2776    
2777     $self->{timer}->cancel
2778     if $self->{timer};
2779    
2780     $self->SUPER::DESTROY;
2781     }
2782    
2783 root 1.162 #############################################################################
2784    
2785 root 1.186 package CFClient::UI::Inventory;
2786    
2787     our @ISA = CFClient::UI::ScrolledWindow::;
2788    
2789     sub new {
2790     my $class = shift;
2791    
2792     my $self = $class->SUPER::new (
2793 root 1.234 scrolled => (new CFClient::UI::Table col_expand => [0, 1, 0]),
2794 root 1.186 @_,
2795     );
2796    
2797     $self
2798     }
2799    
2800     sub set_items {
2801     my ($self, $items) = @_;
2802    
2803     $self->{scrolled}->clear;
2804     return unless $items;
2805    
2806 root 1.201 my @items = sort {
2807     ($a->{type} <=> $b->{type})
2808     or ($a->{name} cmp $b->{name})
2809     } @$items;
2810 root 1.186
2811     $self->{real_items} = \@items;
2812    
2813 root 1.234 my $row = 0;
2814 root 1.186 for my $item (@items) {
2815 root 1.234 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 root 1.186
2821 root 1.234 $row++;
2822 elmex 1.224 }
2823 root 1.186 }
2824    
2825     #############################################################################
2826    
2827 root 1.178 package CFClient::UI::Menu;
2828    
2829     our @ISA = CFClient::UI::FancyFrame::;
2830    
2831     use CFClient::OpenGL;
2832    
2833     sub 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)
2864     sub popup {
2865     my ($self, $ev) = @_;
2866    
2867 root 1.231 $self->_emit ("popdown");
2868 root 1.178
2869     # maybe save $GRAB? must be careful about events...
2870     $GRAB = $self;
2871     $self->{button} = $ev->{button};
2872    
2873     $self->show;
2874 root 1.258 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5);
2875 root 1.178 }
2876    
2877     sub mouse_motion {
2878     my ($self, $ev, $x, $y) = @_;
2879    
2880 root 1.182 # TODO: should use vbox->find_widget or so
2881 root 1.178 $HOVER = $ROOT->find_widget ($ev->{x}, $ev->{y});
2882     $self->{hover} = $self->{item}{$HOVER};
2883     }
2884    
2885     sub button_up {
2886     my ($self, $ev, $x, $y) = @_;
2887    
2888     if ($ev->{button} == $self->{button}) {
2889     undef $GRAB;
2890     $self->hide;
2891    
2892 root 1.231 $self->_emit ("popdown");
2893 root 1.178 $self->{hover}[1]->() if $self->{hover};
2894     }
2895     }
2896    
2897     #############################################################################
2898    
2899 root 1.194 package CFClient::UI::Statusbox;
2900    
2901     our @ISA = CFClient::UI::VBox::;
2902    
2903 root 1.210 sub new {
2904     my $class = shift;
2905    
2906     $class->SUPER::new (
2907     fontsize => 0.8,
2908     @_,
2909     )
2910     }
2911    
2912 root 1.194 sub 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 root 1.197
2922     my @items = sort {
2923     $a->{pri} <=> $b->{pri}
2924     or $b->{id} <=> $a->{id}
2925     } values %{ $self->{item} };
2926    
2927 root 1.194 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 root 1.197 my $short = $item->{count} > 1
2934     ? "<b>$item->{count} ×</b> $item->{text}"
2935     : $item->{text};
2936    
2937 root 1.194 for ($short) {
2938     s/^\s+//;
2939 root 1.205 s/\s+/ /g;
2940 root 1.194 }
2941    
2942     new CFClient::UI::Label
2943 root 1.196 markup => $short,
2944 root 1.197 tooltip => $item->{tooltip},
2945 root 1.196 tooltip_font => $::FONT_PROP,
2946 root 1.197 tooltip_width => 0.67,
2947 root 1.213 fontsize => $item->{fontsize} || $self->{fontsize},
2948     max_w => $::WIDTH * 0.44,
2949 root 1.205 fg => $item->{fg},
2950 root 1.196 can_events => 1,
2951 root 1.197 can_hover => 1
2952 root 1.194 };
2953     }
2954    
2955     $self->clear;
2956 root 1.197 $self->SUPER::add (reverse @widgets);
2957 root 1.194 }
2958    
2959     sub add {
2960     my ($self, $text, %arg) = @_;
2961    
2962 root 1.198 $text =~ s/^\s+//;
2963     $text =~ s/\s+$//;
2964    
2965 root 1.233 return unless $text;
2966    
2967 root 1.197 my $timeout = time + ((delete $arg{timeout}) || 60);
2968 root 1.194
2969 root 1.197 my $group = exists $arg{group} ? $arg{group} : ++$self->{id};
2970 root 1.194
2971 root 1.197 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 root 1.198 $item->{id} = ++$self->{id};
2979 root 1.197 $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 root 1.205 fg => [0.8, 0.8, 0.8, 0.8],
2988 root 1.197 pri => 0,
2989     count => 1,
2990     %arg,
2991     };
2992     }
2993 root 1.194
2994     $self->reorder;
2995     }
2996    
2997 root 1.213 sub reconfigure {
2998     my ($self) = @_;
2999    
3000     delete $_->{label}
3001     for values %{ $self->{item} || {} };
3002    
3003     $self->reorder;
3004     $self->SUPER::reconfigure;
3005     }
3006    
3007 root 1.194 #############################################################################
3008    
3009 root 1.113 package CFClient::UI::Root;
3010 root 1.51
3011 root 1.73 our @ISA = CFClient::UI::Container::;
3012 root 1.51
3013 root 1.138 use CFClient::OpenGL;
3014 root 1.107
3015 root 1.191 sub new {
3016     my $class = shift;
3017    
3018 root 1.251 my $self = $class->SUPER::new (
3019 root 1.241 visible => 1,
3020 root 1.191 @_,
3021 root 1.251 );
3022    
3023     Scalar::Util::weaken ($self->{root} = $self);
3024    
3025     $self
3026 root 1.191 }
3027    
3028 root 1.51 sub size_request {
3029 root 1.186 my ($self) = @_;
3030    
3031     ($self->{w}, $self->{h})
3032     }
3033    
3034 root 1.256 sub _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
3046     }
3047    
3048 root 1.186 sub size_allocate {
3049 root 1.259 my ($self, $w, $h) = @_;
3050 root 1.186
3051     for my $child ($self->children) {
3052 root 1.149 my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)};
3053    
3054 root 1.256 $X = $child->{force_x} if exists $child->{force_x};
3055     $Y = $child->{force_y} if exists $child->{force_y};
3056 root 1.205
3057 root 1.256 $X = _to_pixel $X, $W, $self->{w};
3058     $Y = _to_pixel $Y, $H, $self->{h};
3059 root 1.205
3060 root 1.186 $child->configure ($X, $Y, $W, $H);
3061 root 1.149 }
3062 root 1.191 }
3063    
3064     sub coord2local {
3065     my ($self, $x, $y) = @_;
3066    
3067     ($x, $y)
3068 root 1.51 }
3069    
3070 root 1.191 sub coord2global {
3071 root 1.58 my ($self, $x, $y) = @_;
3072    
3073     ($x, $y)
3074     }
3075    
3076 root 1.51 sub update {
3077     my ($self) = @_;
3078    
3079 root 1.191 $::WANT_REFRESH++;
3080 root 1.51 }
3081    
3082     sub add {
3083 root 1.203 my ($self, @children) = @_;
3084    
3085 root 1.256 $_->{is_toplevel} = 1
3086     for @children;
3087 elmex 1.146
3088 root 1.203 $self->SUPER::add (@children);
3089 root 1.222 }
3090    
3091     sub remove {
3092     my ($self, @children) = @_;
3093    
3094     $self->SUPER::remove (@children);
3095    
3096 root 1.256 delete $self->{is_toplevel}
3097     for @children;
3098    
3099 root 1.222 while (@children) {
3100     my $w = pop @children;
3101     push @children, $w->children;
3102 root 1.231 $w->set_invisible;
3103 root 1.222 }
3104 root 1.51 }
3105    
3106 root 1.107 sub on_refresh {
3107     my ($self, $id, $cb) = @_;
3108    
3109     $self->{refresh_hook}{$id} = $cb;
3110     }
3111    
3112 root 1.198 sub on_post_alloc {
3113     my ($self, $id, $cb) = @_;
3114    
3115     $self->{post_alloc_hook}{$id} = $cb;
3116     }
3117    
3118 root 1.191 sub draw {
3119 root 1.51 my ($self) = @_;
3120    
3121 root 1.198 while ($self->{refresh_hook}) {
3122     $_->()
3123     for values %{delete $self->{refresh_hook}};
3124     }
3125    
3126 root 1.251 if ($self->{realloc}) {
3127 root 1.243 my @queue;
3128 root 1.191
3129 root 1.250 while () {
3130 root 1.251 if ($self->{realloc}) {
3131 root 1.243 #TODO use array-of-depth approach
3132    
3133 root 1.259 use sort 'stable';
3134    
3135 root 1.243 @queue = sort { $a->{visible} <=> $b->{visible} }
3136 root 1.251 @queue, values %{delete $self->{realloc}};
3137 root 1.191 }
3138    
3139 root 1.243 my $widget = pop @queue || last;
3140    
3141 root 1.251 $widget->{visible} or last; # do not resize invisible widgets
3142 root 1.191
3143 root 1.257 my ($w, $h) = $widget->size_request;
3144 root 1.256
3145 root 1.258 $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 root 1.257
3148     $w = $widget->{force_w} if exists $widget->{force_w};
3149     $h = $widget->{force_h} if exists $widget->{force_h};
3150 root 1.191
3151 root 1.259 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 root 1.191
3166 root 1.259 delete $self->{realloc}{$widget+0};
3167 root 1.191 }
3168     }
3169    
3170 root 1.250 while (my $size_alloc = delete $self->{size_alloc}) {
3171 root 1.259 my @queue = sort { $b->{visible} <=> $a->{visible} }
3172 root 1.250 values %$size_alloc;
3173    
3174     while () {
3175 root 1.259 my $widget = pop @queue || last;
3176 root 1.191
3177 root 1.259 my ($w, $h) = @$widget{qw(alloc_w alloc_h)};
3178 root 1.256
3179 root 1.193 $w = 0 if $w < 0;
3180     $h = 0 if $h < 0;
3181    
3182 root 1.256 $w = int $w + 0.5;
3183     $h = int $h + 0.5;
3184    
3185 root 1.259 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) {
3186     $widget->{w} = $w;
3187     $widget->{h} = $h;
3188 root 1.252
3189 root 1.259 $widget->emit (size_allocate => $w, $h);
3190     }
3191 root 1.191 }
3192     }
3193    
3194 root 1.198 while ($self->{post_alloc_hook}) {
3195 root 1.191 $_->()
3196 root 1.198 for values %{delete $self->{post_alloc_hook}};
3197 root 1.107 }
3198    
3199 root 1.259
3200 root 1.107 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 root 1.219 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000;
3207 root 1.107 glMatrixMode GL_MODELVIEW;
3208     glLoadIdentity;
3209    
3210 root 1.51 $self->_draw;
3211     }
3212    
3213     #############################################################################
3214    
3215 elmex 1.260 package CFClient::UI::BindEditor;
3216    
3217     our @ISA = CFClient::UI::FancyFrame::;
3218    
3219     sub 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 elmex 1.261 $vb->add (new CFClient::UI::Label text => "Actions:");
3242 elmex 1.260 $vb->add ($self->{cmdbox} = new CFClient::UI::VBox);
3243    
3244 elmex 1.261 $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 elmex 1.260 $vb->add (my $hb = new CFClient::UI::HBox);
3255     $hb->add (new CFClient::UI::Button
3256     text => "ok",
3257 elmex 1.261 expand => 1,
3258 elmex 1.260 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 elmex 1.261 expand => 1,
3267 elmex 1.260 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    
3279     sub 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 elmex 1.261 $::STATUSBOX->add ("No action bound, no key or action specified!");
3289 elmex 1.260 $self->{binding_cancel}->()
3290     if $self->{binding_cancel};
3291     }
3292     }
3293    
3294     sub 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    
3303     sub 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    
3315 elmex 1.262 # if $commit is true, the binding will be set after the user entered a key combo
3316 elmex 1.260 sub ask_for_bind {
3317     my ($self, $commit) = @_;
3318    
3319     CFClient::Binder::open_binding_dialog (sub {
3320     my ($mod, $sym) = @_;
3321     $self->{binding} = [$mod, $sym]; # XXX: how to stop that memleak?
3322     $self->update_binding_widgets;
3323     $self->commit if $commit;
3324     });
3325     }
3326    
3327 elmex 1.262 # $mod and $sym are the modifiers and key symbol
3328     # $cmds is a array ref of strings (the commands)
3329     # $cb is the callback that is executed on OK
3330     # $ccb is the callback that is executed on CANCEL and
3331     # when the binding was unsuccessful on OK
3332 elmex 1.260 sub set_binding {
3333     my ($self, $mod, $sym, $cmds, $cb, $ccb) = @_;
3334    
3335     $self->clear_command_list;
3336     $self->{recording} = 0;
3337     $self->{rec_btn}->set_text ("start recording");
3338    
3339     $self->{binding} = [$mod, $sym];
3340     $self->{commands} = $cmds;
3341    
3342     $self->{binding_change} = $cb;
3343     $self->{binding_cancel} = $ccb;
3344    
3345     $self->update_binding_widgets;
3346     }
3347    
3348 elmex 1.262 # this is a shortcut method that asks for a binding
3349     # and then just binds it.
3350     sub do_quick_binding {
3351     my ($self, $cmds) = @_;
3352     $self->set_binding (undef, undef, $cmds, sub {
3353     $::CFG->{bindings}->{$_[0]}->{$_[1]} = $_[2];
3354     });
3355     $self->ask_for_bind (1);
3356     }
3357    
3358 elmex 1.260 sub update_binding_widgets {
3359     my ($self) = @_;
3360     my ($mod, $sym, $cmds) = $self->get_binding;
3361     $self->{keylbl}->set_text (CFClient::Binder::keycombo_to_name ($mod, $sym));
3362     $self->set_command_list ($cmds);
3363     }
3364    
3365     sub get_binding {
3366     my ($self) = @_;
3367     return (
3368     $self->{binding}->[0],
3369     $self->{binding}->[1],
3370     [ grep { defined $_ } @{$self->{commands}} ]
3371     );
3372     }
3373    
3374     sub clear_command_list {
3375     my ($self) = @_;
3376     $self->{cmdbox}->clear ();
3377     }
3378    
3379     sub set_command_list {
3380     my ($self, $cmds) = @_;
3381    
3382     $self->{cmdbox}->clear ();
3383     $self->{commands} = $cmds;
3384    
3385     my $idx = 0;
3386    
3387     for (@$cmds) {
3388     $self->{cmdbox}->add (my $hb = new CFClient::UI::HBox);
3389    
3390     my $i = $idx;
3391     $hb->add (new CFClient::UI::Button
3392     text => "delete",
3393     tooltip => "Deletes the action from the record",
3394     on_activate => sub {
3395     $self->{cmdbox}->remove ($hb);
3396     $cmds->[$i] = undef;
3397     });
3398    
3399     $hb->add (new CFClient::UI::Label text => $_);
3400    
3401     $idx++
3402     }
3403     }
3404    
3405    
3406     #############################################################################
3407    
3408 elmex 1.262 package CFClient::UI::SpellList;
3409    
3410     our @ISA = CFClient::UI::FancyFrame::;
3411    
3412     sub new {
3413     my $class = shift;
3414    
3415     my $self = $class->SUPER::new (binding => [], commands => [], @_);
3416    
3417     $self->add (new CFClient::UI::ScrolledWindow
3418     scrolled => $self->{spellbox} = new CFClient::UI::Table);
3419    
3420     $self;
3421     }
3422    
3423     # XXX: Do sorting? Argl...
3424     sub add_spell {
3425     my ($self, $spell) = @_;
3426     $self->{spells}->{$spell->{name}} = $spell;
3427    
3428     $self->{spellbox}->add (0, $self->{tbl_idx}, new CFClient::UI::Face
3429     face => $spell->{face},
3430     can_hover => 1,
3431     can_events => 1,
3432     tooltip => $spell->{message});
3433    
3434     $self->{spellbox}->add (1, $self->{tbl_idx}, new CFClient::UI::Label
3435     text => $spell->{name},
3436     can_hover => 1,
3437     can_events => 1,
3438     tooltip => $spell->{message},
3439     expand => 1);
3440     $self->{spellbox}->add (2, $self->{tbl_idx}++, new CFClient::UI::Button
3441     text => "bind to key",
3442     on_activate => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) });
3443     }
3444    
3445     sub rebuild_spell_list {
3446     my ($self) = @_;
3447     $self->{tbl_idx} = 0;
3448     $self->add_spell ($_) for values %{$self->{spells}};
3449     }
3450    
3451     sub remove_spell {
3452     my ($self, $spell) = @_;
3453     delete $self->{spells}->{$spell->{name}};
3454     $self->rebuild_spell_list;
3455     }
3456    
3457     #############################################################################
3458    
3459 root 1.73 package CFClient::UI;
3460 root 1.51
3461 root 1.113 $ROOT = new CFClient::UI::Root;
3462 root 1.213 $TOOLTIP = new CFClient::UI::Tooltip z => 900;
3463 root 1.51
3464     1
3465 root 1.5