ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/UI.pm
Revision: 1.264
Committed: Thu Jun 1 02:58:30 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.263: +59 -59 lines
Log Message:
*** empty log message ***

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.264 delete $self->{texture}
1528     ;#d#
1529 root 1.14 }
1530    
1531 elmex 1.146 sub set_fontsize {
1532     my ($self, $fontsize) = @_;
1533    
1534     $self->{fontsize} = $fontsize;
1535 root 1.152 delete $self->{texture};
1536 root 1.186
1537 root 1.251 $self->realloc;
1538 elmex 1.146 }
1539    
1540 elmex 1.11 sub _draw {
1541 root 1.10 my ($self) = @_;
1542    
1543 root 1.209 $self->SUPER::_draw; # draw background, if applicable
1544    
1545 root 1.59 my $tex = $self->{texture} ||= do {
1546 root 1.194 $self->{layout}->set_foreground (@{$self->{fg}});
1547 root 1.157 $self->{layout}->set_font ($self->{font}) if $self->{font};
1548 root 1.59 $self->{layout}->set_width ($self->{w});
1549 root 1.213 $self->{layout}->set_ellipsise ($self->{ellipsise});
1550     $self->{layout}->set_single_paragraph_mode ($self->{ellipsise});
1551     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
1552 root 1.194
1553     my $tex = new_from_layout CFClient::Texture $self->{layout};
1554    
1555 root 1.258 $self->{ox} = int ($self->{align} < 0 ? $self->{padding_x}
1556     : $self->{align} > 0 ? $self->{w} - $tex->{w} - $self->{padding_x}
1557 root 1.208 : ($self->{w} - $tex->{w}) * 0.5);
1558    
1559 root 1.258 $self->{oy} = int ($self->{valign} < 0 ? $self->{padding_y}
1560     : $self->{valign} > 0 ? $self->{h} - $tex->{h} - $self->{padding_y}
1561 root 1.208 : ($self->{h} - $tex->{h}) * 0.5);
1562 root 1.194
1563     $tex
1564 root 1.59 };
1565 root 1.10
1566     glEnable GL_TEXTURE_2D;
1567 root 1.105 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1568 root 1.10
1569 root 1.194 if ($tex->{format} == GL_ALPHA) {
1570     glColor @{$self->{fg}};
1571     $tex->draw_quad_alpha ($self->{ox}, $self->{oy});
1572     } else {
1573     $tex->draw_quad_alpha_premultiplied ($self->{ox}, $self->{oy});
1574     }
1575 root 1.10
1576 root 1.74 glDisable GL_TEXTURE_2D;
1577 root 1.10 }
1578    
1579 root 1.39 #############################################################################
1580    
1581 root 1.121 package CFClient::UI::EntryBase;
1582 elmex 1.31
1583 root 1.73 our @ISA = CFClient::UI::Label::;
1584 elmex 1.31
1585 root 1.138 use CFClient::OpenGL;
1586 elmex 1.31
1587 root 1.68 sub new {
1588     my $class = shift;
1589    
1590     $class->SUPER::new (
1591 root 1.164 fg => [1, 1, 1],
1592     bg => [0, 0, 0, 0.2],
1593     active_bg => [1, 1, 1, 0.5],
1594     active_fg => [0, 0, 0],
1595     can_hover => 1,
1596     can_focus => 1,
1597     valign => 0,
1598 elmex 1.150 can_events => 1,
1599 root 1.225 #text => ...
1600 root 1.68 @_
1601     )
1602     }
1603    
1604     sub _set_text {
1605     my ($self, $text) = @_;
1606    
1607 root 1.121 delete $self->{cur_h};
1608    
1609     return if $self->{text} eq $text;
1610 elmex 1.100
1611 root 1.134 delete $self->{texture};
1612    
1613 root 1.68 $self->{last_activity} = $::NOW;
1614     $self->{text} = $text;
1615 root 1.72
1616     $text =~ s/./*/g if $self->{hidden};
1617 root 1.121 $self->{layout}->set_text ("$text ");
1618 root 1.72
1619 root 1.231 $self->_emit (changed => $self->{text});
1620 root 1.121 }
1621 root 1.68
1622 root 1.194 sub set_text {
1623     my ($self, $text) = @_;
1624    
1625     $self->{cursor} = length $text;
1626     $self->_set_text ($text);
1627 root 1.251
1628     $self->realloc;
1629 root 1.194 }
1630    
1631 root 1.121 sub get_text {
1632     $_[0]{text}
1633 root 1.68 }
1634    
1635     sub size_request {
1636     my ($self) = @_;
1637    
1638     my ($w, $h) = $self->SUPER::size_request;
1639    
1640     ($w + 1, $h) # add 1 for cursor
1641     }
1642    
1643 elmex 1.31 sub key_down {
1644     my ($self, $ev) = @_;
1645    
1646 root 1.137 my $mod = $ev->{mod};
1647     my $sym = $ev->{sym};
1648     my $uni = $ev->{unicode};
1649 elmex 1.31
1650     my $text = $self->get_text;
1651    
1652 root 1.200 if ($uni == 8) {
1653 root 1.68 substr $text, --$self->{cursor}, 1, "" if $self->{cursor};
1654 root 1.200 } elsif ($uni == 127) {
1655 root 1.68 substr $text, $self->{cursor}, 1, "";
1656 root 1.136 } elsif ($sym == CFClient::SDLK_LEFT) {
1657 root 1.68 --$self->{cursor} if $self->{cursor};
1658 root 1.136 } elsif ($sym == CFClient::SDLK_RIGHT) {
1659 root 1.68 ++$self->{cursor} if $self->{cursor} < length $self->{text};
1660 root 1.136 } elsif ($sym == CFClient::SDLK_HOME) {
1661 root 1.76 $self->{cursor} = 0;
1662 root 1.136 } elsif ($sym == CFClient::SDLK_END) {
1663 root 1.76 $self->{cursor} = length $text;
1664 root 1.200 } elsif ($uni == 27) {
1665 root 1.231 $self->_emit ('escape');
1666 elmex 1.31 } elsif ($uni) {
1667 root 1.68 substr $text, $self->{cursor}++, 0, chr $uni;
1668 elmex 1.31 }
1669 root 1.51
1670 root 1.68 $self->_set_text ($text);
1671 root 1.251
1672     $self->realloc;
1673 root 1.68 }
1674    
1675     sub focus_in {
1676     my ($self) = @_;
1677    
1678     $self->{last_activity} = $::NOW;
1679    
1680     $self->SUPER::focus_in;
1681 elmex 1.31 }
1682    
1683 root 1.51 sub button_down {
1684 root 1.68 my ($self, $ev, $x, $y) = @_;
1685    
1686     $self->SUPER::button_down ($ev, $x, $y);
1687    
1688     my $idx = $self->{layout}->xy_to_index ($x, $y);
1689    
1690     # byte-index to char-index
1691 root 1.76 my $text = $self->{text};
1692 root 1.68 utf8::encode $text;
1693     $self->{cursor} = length substr $text, 0, $idx;
1694 root 1.51
1695 root 1.68 $self->_set_text ($self->{text});
1696     $self->update;
1697 root 1.51 }
1698    
1699 root 1.58 sub mouse_motion {
1700     my ($self, $ev, $x, $y) = @_;
1701 root 1.68 # printf "M %d,%d %d,%d\n", $ev->motion_x, $ev->motion_y, $x, $y;#d#
1702 root 1.58 }
1703    
1704 root 1.51 sub _draw {
1705     my ($self) = @_;
1706    
1707 root 1.68 local $self->{fg} = $self->{fg};
1708    
1709 root 1.51 if ($FOCUS == $self) {
1710 root 1.68 glColor @{$self->{active_bg}};
1711     $self->{fg} = $self->{active_fg};
1712 root 1.51 } else {
1713 root 1.68 glColor @{$self->{bg}};
1714 root 1.51 }
1715    
1716 root 1.76 glEnable GL_BLEND;
1717     glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
1718 root 1.51 glBegin GL_QUADS;
1719 root 1.68 glVertex 0 , 0;
1720     glVertex 0 , $self->{h};
1721     glVertex $self->{w}, $self->{h};
1722     glVertex $self->{w}, 0;
1723 root 1.51 glEnd;
1724 root 1.76 glDisable GL_BLEND;
1725 root 1.51
1726     $self->SUPER::_draw;
1727 root 1.68
1728     #TODO: force update every cursor change :(
1729     if ($FOCUS == $self && (($::NOW - $self->{last_activity}) & 1023) < 600) {
1730 root 1.121
1731     unless (exists $self->{cur_h}) {
1732     my $text = substr $self->{text}, 0, $self->{cursor};
1733     utf8::encode $text;
1734    
1735     @$self{qw(cur_x cur_y cur_h)} = $self->{layout}->cursor_pos (length $text)
1736     }
1737    
1738 root 1.68 glColor @{$self->{fg}};
1739     glBegin GL_LINES;
1740 root 1.122 glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy};
1741     glVertex $self->{cur_x} + $self->{ox}, $self->{cur_y} + $self->{oy} + $self->{cur_h};
1742 root 1.68 glEnd;
1743     }
1744     }
1745    
1746 root 1.121 package CFClient::UI::Entry;
1747 elmex 1.99
1748 root 1.121 our @ISA = CFClient::UI::EntryBase::;
1749 elmex 1.99
1750 root 1.138 use CFClient::OpenGL;
1751 elmex 1.99
1752     sub key_down {
1753     my ($self, $ev) = @_;
1754    
1755 root 1.137 my $sym = $ev->{sym};
1756 elmex 1.99
1757 root 1.136 if ($sym == 13) {
1758 elmex 1.167 unshift @{$self->{history}},
1759     my $txt = $self->get_text;
1760     $self->{history_pointer} = -1;
1761 elmex 1.169 $self->{history_saveback} = '';
1762 root 1.231 $self->_emit (activate => $txt);
1763 elmex 1.99 $self->update;
1764    
1765 elmex 1.167 } elsif ($sym == CFClient::SDLK_UP) {
1766     if ($self->{history_pointer} < 0) {
1767     $self->{history_saveback} = $self->get_text;
1768     }
1769 elmex 1.169 if (@{$self->{history} || []} > 0) {
1770     $self->{history_pointer}++;
1771     if ($self->{history_pointer} >= @{$self->{history} || []}) {
1772     $self->{history_pointer} = @{$self->{history} || []} - 1;
1773     }
1774     $self->set_text ($self->{history}->[$self->{history_pointer}]);
1775 elmex 1.167 }
1776    
1777     } elsif ($sym == CFClient::SDLK_DOWN) {
1778     $self->{history_pointer}--;
1779     $self->{history_pointer} = -1 if $self->{history_pointer} < 0;
1780    
1781     if ($self->{history_pointer} >= 0) {
1782     $self->set_text ($self->{history}->[$self->{history_pointer}]);
1783     } else {
1784     $self->set_text ($self->{history_saveback});
1785     }
1786    
1787 elmex 1.99 } else {
1788     $self->SUPER::key_down ($ev);
1789     }
1790    
1791     }
1792    
1793 root 1.68 #############################################################################
1794    
1795 root 1.79 package CFClient::UI::Button;
1796    
1797     our @ISA = CFClient::UI::Label::;
1798    
1799 root 1.138 use CFClient::OpenGL;
1800 root 1.79
1801 elmex 1.85 my @tex =
1802 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1803 elmex 1.85 qw(b1_button_active.png);
1804    
1805 root 1.79 sub new {
1806     my $class = shift;
1807    
1808     $class->SUPER::new (
1809 root 1.258 padding_x => 4,
1810     padding_y => 4,
1811 root 1.164 fg => [1, 1, 1],
1812     active_fg => [0, 0, 1],
1813     can_hover => 1,
1814     align => 0,
1815     valign => 0,
1816 elmex 1.150 can_events => 1,
1817 root 1.79 @_
1818     )
1819     }
1820    
1821 root 1.231 sub activate { }
1822    
1823 root 1.79 sub button_up {
1824     my ($self, $ev, $x, $y) = @_;
1825    
1826 root 1.231 $self->emit ("activate")
1827     if $x >= 0 && $x < $self->{w}
1828     && $y >= 0 && $y < $self->{h};
1829 root 1.79 }
1830    
1831     sub _draw {
1832     my ($self) = @_;
1833    
1834     local $self->{fg} = $self->{fg};
1835    
1836     if ($GRAB == $self) {
1837     $self->{fg} = $self->{active_fg};
1838     }
1839    
1840 root 1.119 glEnable GL_TEXTURE_2D;
1841 elmex 1.85 glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1842 root 1.119 glColor 0, 0, 0, 1;
1843 elmex 1.85
1844 root 1.195 $tex[0]->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
1845 elmex 1.85
1846     glDisable GL_TEXTURE_2D;
1847 root 1.79
1848     $self->SUPER::_draw;
1849     }
1850    
1851     #############################################################################
1852    
1853 root 1.86 package CFClient::UI::CheckBox;
1854    
1855     our @ISA = CFClient::UI::DrawBG::;
1856    
1857 elmex 1.102 my @tex =
1858 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1859 elmex 1.102 qw(c1_checkbox_bg.png c1_checkbox_active.png);
1860    
1861 root 1.138 use CFClient::OpenGL;
1862 root 1.86
1863     sub new {
1864     my $class = shift;
1865    
1866     $class->SUPER::new (
1867 root 1.258 padding_x => 2,
1868     padding_y => 2,
1869 root 1.86 fg => [1, 1, 1],
1870     active_fg => [1, 1, 0],
1871 root 1.209 bg => [0, 0, 0, 0.2],
1872     active_bg => [1, 1, 1, 0.5],
1873 root 1.86 state => 0,
1874 root 1.97 can_hover => 1,
1875 root 1.86 @_
1876     )
1877     }
1878    
1879 root 1.87 sub size_request {
1880     my ($self) = @_;
1881    
1882 root 1.258 (6) x 2
1883 root 1.87 }
1884    
1885 root 1.86 sub button_down {
1886     my ($self, $ev, $x, $y) = @_;
1887    
1888 root 1.258 if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
1889     && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
1890 root 1.86 $self->{state} = !$self->{state};
1891 root 1.231 $self->_emit (changed => $self->{state});
1892 root 1.86 }
1893     }
1894    
1895     sub _draw {
1896     my ($self) = @_;
1897    
1898 root 1.87 $self->SUPER::_draw;
1899 root 1.86
1900 root 1.258 glTranslate $self->{padding_x} + 0.375, $self->{padding_y} + 0.375, 0;
1901 root 1.86
1902 root 1.258 my ($w, $h) = @$self{qw(w h)};
1903    
1904     my $s = List::Util::min $w - $self->{padding_x} * 2, $h - $self->{padding_y} * 2;
1905 elmex 1.102
1906 root 1.87 glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };
1907 root 1.86
1908 elmex 1.102 my $tex = $self->{state} ? $tex[1] : $tex[0];
1909    
1910 root 1.197 glEnable GL_TEXTURE_2D;
1911 root 1.195 $tex->draw_quad_alpha (0, 0, $s, $s);
1912 elmex 1.102 glDisable GL_TEXTURE_2D;
1913 root 1.86 }
1914    
1915     #############################################################################
1916    
1917 elmex 1.145 package CFClient::UI::Image;
1918    
1919     our @ISA = CFClient::UI::Base::;
1920    
1921     use CFClient::OpenGL;
1922     use Carp qw/confess/;
1923    
1924     our %loaded_images;
1925    
1926     sub new {
1927     my $class = shift;
1928    
1929 elmex 1.150 my $self = $class->SUPER::new (can_events => 0, @_);
1930 elmex 1.145
1931     $self->{image} or confess "Image has 'image' not set. This is a fatal error!";
1932    
1933     $loaded_images{$self->{image}} ||=
1934     new_from_file CFClient::Texture CFClient::find_rcfile $self->{image}, mipmap => 1;
1935    
1936     my $tex = $self->{tex} = $loaded_images{$self->{image}};
1937    
1938 root 1.147 Scalar::Util::weaken $loaded_images{$self->{image}};
1939    
1940 elmex 1.145 $self->{aspect} = $tex->{w} / $tex->{h};
1941    
1942     $self
1943     }
1944    
1945     sub size_request {
1946     my ($self) = @_;
1947    
1948     ($self->{tex}->{w}, $self->{tex}->{h})
1949     }
1950    
1951     sub _draw {
1952     my ($self) = @_;
1953    
1954     my $tex = $self->{tex};
1955    
1956     my ($w, $h) = ($self->{w}, $self->{h});
1957    
1958     if ($self->{rot90}) {
1959     glRotate 90, 0, 0, 1;
1960     glTranslate 0, -$self->{w}, 0;
1961    
1962     ($w, $h) = ($h, $w);
1963     }
1964    
1965     glEnable GL_TEXTURE_2D;
1966     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
1967    
1968 root 1.195 $tex->draw_quad_alpha (0, 0, $w, $h);
1969 elmex 1.145
1970     glDisable GL_TEXTURE_2D;
1971     }
1972    
1973     #############################################################################
1974    
1975 elmex 1.124 package CFClient::UI::VGauge;
1976    
1977     our @ISA = CFClient::UI::Base::;
1978    
1979 root 1.158 use List::Util qw(min max);
1980    
1981 root 1.138 use CFClient::OpenGL;
1982 elmex 1.124
1983     my %tex = (
1984     food => [
1985 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1986 elmex 1.124 qw/g1_food_gauge_empty.png g1_food_gauge_full.png/
1987     ],
1988     grace => [
1989 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1990 root 1.158 qw/g1_grace_gauge_empty.png g1_grace_gauge_full.png g1_grace_gauge_overflow.png/
1991 elmex 1.124 ],
1992     hp => [
1993 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1994 elmex 1.124 qw/g1_hp_gauge_empty.png g1_hp_gauge_full.png/
1995     ],
1996     mana => [
1997 root 1.144 map { new_from_file CFClient::Texture CFClient::find_rcfile $_, mipmap => 1 }
1998 root 1.158 qw/g1_mana_gauge_empty.png g1_mana_gauge_full.png g1_mana_gauge_overflow.png/
1999 elmex 1.124 ],
2000     );
2001    
2002     # eg. VGauge->new (gauge => 'food'), default gauge: food
2003     sub new {
2004     my $class = shift;
2005    
2006 root 1.140 my $self = $class->SUPER::new (
2007 root 1.141 type => 'food',
2008 root 1.140 @_
2009     );
2010    
2011 root 1.141 $self->{aspect} = $tex{$self->{type}}[0]{w} / $tex{$self->{type}}[0]{h};
2012 elmex 1.124
2013     $self
2014     }
2015    
2016     sub size_request {
2017     my ($self) = @_;
2018    
2019 root 1.143 #my $tex = $tex{$self->{type}}[0];
2020     #@$tex{qw(w h)}
2021     (0, 0)
2022 elmex 1.124 }
2023    
2024     sub set_max {
2025     my ($self, $max) = @_;
2026 root 1.127
2027 root 1.173 return if $self->{max_val} == $max;
2028    
2029 elmex 1.124 $self->{max_val} = $max;
2030 root 1.173 $self->update;
2031 elmex 1.124 }
2032    
2033     sub set_value {
2034     my ($self, $val, $max) = @_;
2035    
2036     $self->set_max ($max)
2037     if defined $max;
2038    
2039 root 1.173 return if $self->{val} == $val;
2040    
2041 elmex 1.124 $self->{val} = $val;
2042     $self->update;
2043     }
2044    
2045     sub _draw {
2046     my ($self) = @_;
2047    
2048 root 1.141 my $tex = $tex{$self->{type}};
2049 root 1.158 my ($t1, $t2, $t3) = @$tex;
2050 elmex 1.124
2051     my ($w, $h) = ($self->{w}, $self->{h});
2052    
2053 elmex 1.142 if ($self->{vertical}) {
2054     glRotate 90, 0, 0, 1;
2055     glTranslate 0, -$self->{w}, 0;
2056    
2057     ($w, $h) = ($h, $w);
2058     }
2059    
2060 elmex 1.124 my $ycut = $self->{val} / ($self->{max_val} || 1);
2061    
2062 root 1.158 my $ycut1 = max 0, min 1, $ycut;
2063     my $ycut2 = max 0, min 1, $ycut - 1;
2064    
2065     my $h1 = $self->{h} * (1 - $ycut1);
2066     my $h2 = $self->{h} * (1 - $ycut2);
2067 elmex 1.124
2068     glEnable GL_BLEND;
2069     glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA;
2070     glEnable GL_TEXTURE_2D;
2071     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2072    
2073 root 1.131 glBindTexture GL_TEXTURE_2D, $t1->{name};
2074     glBegin GL_QUADS;
2075 root 1.158 glTexCoord 0 , 0; glVertex 0 , 0;
2076     glTexCoord 0 , $t1->{t} * (1 - $ycut1); glVertex 0 , $h1;
2077     glTexCoord $t1->{s}, $t1->{t} * (1 - $ycut1); glVertex $w, $h1;
2078     glTexCoord $t1->{s}, 0; glVertex $w, 0;
2079 root 1.131 glEnd;
2080 elmex 1.124
2081 root 1.158 my $ycut1 = List::Util::min 1, $ycut;
2082 root 1.131 glBindTexture GL_TEXTURE_2D, $t2->{name};
2083     glBegin GL_QUADS;
2084 root 1.158 glTexCoord 0 , $t2->{t} * (1 - $ycut1); glVertex 0 , $h1;
2085     glTexCoord 0 , $t2->{t} * (1 - $ycut2); glVertex 0 , $h2;
2086     glTexCoord $t2->{s}, $t2->{t} * (1 - $ycut2); glVertex $w, $h2;
2087     glTexCoord $t2->{s}, $t2->{t} * (1 - $ycut1); glVertex $w, $h1;
2088 root 1.131 glEnd;
2089 elmex 1.124
2090 root 1.158 if ($t3) {
2091     glBindTexture GL_TEXTURE_2D, $t3->{name};
2092     glBegin GL_QUADS;
2093     glTexCoord 0 , $t3->{t} * (1 - $ycut2); glVertex 0 , $h2;
2094     glTexCoord 0 , $t3->{t}; glVertex 0 , $self->{h};
2095     glTexCoord $t3->{s}, $t3->{t}; glVertex $w, $self->{h};
2096     glTexCoord $t3->{s}, $t3->{t} * (1 - $ycut2); glVertex $w, $h2;
2097     glEnd;
2098     }
2099    
2100 elmex 1.124 glDisable GL_BLEND;
2101     glDisable GL_TEXTURE_2D;
2102     }
2103    
2104     #############################################################################
2105    
2106 root 1.141 package CFClient::UI::Gauge;
2107    
2108     our @ISA = CFClient::UI::VBox::;
2109    
2110     sub new {
2111 root 1.151 my ($class, %arg) = @_;
2112 root 1.141
2113     my $self = $class->SUPER::new (
2114 root 1.171 tooltip => $arg{type},
2115     can_hover => 1,
2116     can_events => 1,
2117 root 1.151 %arg,
2118 root 1.141 );
2119    
2120 root 1.161 $self->add ($self->{value} = new CFClient::UI::Label valign => +1, align => 0, template => "999");
2121     $self->add ($self->{gauge} = new CFClient::UI::VGauge type => $self->{type}, expand => 1, can_hover => 1);
2122     $self->add ($self->{max} = new CFClient::UI::Label valign => -1, align => 0, template => "999");
2123 root 1.141
2124     $self
2125     }
2126    
2127 elmex 1.146 sub set_fontsize {
2128     my ($self, $fsize) = @_;
2129    
2130     $self->{value}->set_fontsize ($fsize);
2131     $self->{max} ->set_fontsize ($fsize);
2132     }
2133    
2134 root 1.173 sub set_max {
2135     my ($self, $max) = @_;
2136    
2137     $self->{gauge}->set_max ($max);
2138     $self->{max}->set_text ($max);
2139     }
2140    
2141 root 1.141 sub set_value {
2142     my ($self, $val, $max) = @_;
2143    
2144     $self->set_max ($max)
2145     if defined $max;
2146    
2147     $self->{gauge}->set_value ($val, $max);
2148     $self->{value}->set_text ($val);
2149     }
2150    
2151     #############################################################################
2152    
2153 root 1.73 package CFClient::UI::Slider;
2154 root 1.68
2155     use strict;
2156    
2157 root 1.138 use CFClient::OpenGL;
2158 root 1.68
2159 root 1.73 our @ISA = CFClient::UI::DrawBG::;
2160 root 1.68
2161 elmex 1.99 my @tex =
2162     map { new_from_file CFClient::Texture CFClient::find_rcfile $_ }
2163     qw(s1_slider.png s1_slider_bg.png);
2164    
2165 root 1.68 sub new {
2166     my $class = shift;
2167    
2168 root 1.206 # range [value, low, high, page, unit]
2169 root 1.68
2170 root 1.97 # TODO: 0-width page
2171     # TODO: req_w/h are wrong with vertical
2172     # TODO: calculations are off
2173 root 1.76 my $self = $class->SUPER::new (
2174 root 1.68 fg => [1, 1, 1],
2175     active_fg => [0, 0, 0],
2176 root 1.209 bg => [0, 0, 0, 0.2],
2177     active_bg => [1, 1, 1, 0.5],
2178 root 1.227 range => [0, 0, 100, 10, 0],
2179 root 1.257 min_w => $::WIDTH / 80,
2180     min_h => $::WIDTH / 80,
2181 root 1.76 vertical => 0,
2182 root 1.97 can_hover => 1,
2183 root 1.217 inner_pad => 0.02,
2184 root 1.68 @_
2185 root 1.76 );
2186    
2187 root 1.206 $self->set_value ($self->{range}[0]);
2188     $self->update;
2189    
2190 root 1.76 $self
2191     }
2192    
2193 root 1.251 sub changed { }
2194    
2195 root 1.225 sub set_range {
2196     my ($self, $range) = @_;
2197    
2198 root 1.239 ($range, $self->{range}) = ($self->{range}, $range);
2199 root 1.225
2200 root 1.239 $self->update
2201     if "@$range" ne "@{$self->{range}}";
2202 root 1.225 }
2203    
2204 root 1.206 sub set_value {
2205     my ($self, $value) = @_;
2206    
2207     my ($old_value, $lo, $hi, $page, $unit) = @{$self->{range}};
2208    
2209     $hi = $lo + 1 if $hi <= $lo;
2210    
2211 root 1.227 $page = $hi - $lo if $page > $hi - $lo;
2212    
2213     $value = $lo if $value < $lo;
2214     $value = $hi - $page if $value > $hi - $page;
2215 root 1.206
2216     $value = $lo + $unit * int +($value - $lo + $unit * 0.5) / $unit
2217     if $unit;
2218    
2219     @{$self->{range}} = ($value, $lo, $hi, $page, $unit);
2220    
2221     if ($value != $old_value) {
2222 root 1.231 $self->_emit (changed => $value);
2223 root 1.206 $self->update;
2224     }
2225     }
2226    
2227 root 1.76 sub size_request {
2228     my ($self) = @_;
2229    
2230 root 1.257 ($self->{req_w}, $self->{req_h})
2231 root 1.68 }
2232    
2233 root 1.69 sub button_down {
2234     my ($self, $ev, $x, $y) = @_;
2235    
2236     $self->SUPER::button_down ($ev, $x, $y);
2237 root 1.227
2238     $self->{click} = [$self->{range}[0], $self->{vertical} ? $y : $x];
2239    
2240 root 1.69 $self->mouse_motion ($ev, $x, $y);
2241     }
2242    
2243     sub mouse_motion {
2244     my ($self, $ev, $x, $y) = @_;
2245    
2246     if ($GRAB == $self) {
2247 root 1.71 my ($x, $w) = $self->{vertical} ? ($y, $self->{h}) : ($x, $self->{w});
2248    
2249 root 1.206 my (undef, $lo, $hi, $page) = @{$self->{range}};
2250 elmex 1.103
2251 root 1.227 $x = ($x - $self->{click}[1]) / ($w * $self->{scale});
2252 root 1.69
2253 root 1.227 $self->set_value ($self->{click}[0] + $x * ($hi - $page - $lo));
2254 root 1.69 }
2255     }
2256    
2257 root 1.206 sub update {
2258     my ($self) = @_;
2259    
2260     $CFClient::UI::ROOT->on_post_alloc ($self => sub {
2261     $self->set_value ($self->{range}[0]);
2262    
2263     my ($value, $lo, $hi, $page) = @{$self->{range}};
2264 root 1.227 my $range = ($hi - $page - $lo) || 1e-100;
2265 root 1.206
2266 root 1.227 my $knob_w = List::Util::min 1, $page / ($hi - $lo) || 0.1;
2267 root 1.206
2268 root 1.227 $self->{offset} = List::Util::max $self->{inner_pad}, $knob_w * 0.5;
2269     $self->{scale} = 1 - 2 * $self->{offset} || 1e-100;
2270 root 1.206
2271 root 1.227 $value = ($value - $lo) / $range;
2272     $value = $value * $self->{scale} + $self->{offset};
2273 root 1.206
2274 root 1.227 $self->{knob_x} = $value - $knob_w * 0.5;
2275     $self->{knob_w} = $knob_w;
2276 root 1.206 });
2277    
2278     $self->SUPER::update;
2279 elmex 1.103 }
2280    
2281 root 1.68 sub _draw {
2282     my ($self) = @_;
2283    
2284     $self->SUPER::_draw ();
2285    
2286 root 1.206 glScale $self->{w}, $self->{h};
2287 root 1.68
2288     if ($self->{vertical}) {
2289     # draw a vertical slider like a rotated horizontal slider
2290    
2291 root 1.214 glTranslate 1, 0, 0;
2292 root 1.68 glRotate 90, 0, 0, 1;
2293     }
2294    
2295     my $fg = $FOCUS == $self ? $self->{active_fg} : $self->{fg};
2296     my $bg = $FOCUS == $self ? $self->{active_bg} : $self->{bg};
2297    
2298 elmex 1.99 glEnable GL_TEXTURE_2D;
2299     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2300    
2301     # draw background
2302 root 1.206 $tex[1]->draw_quad_alpha (0, 0, 1, 1);
2303 root 1.69
2304 elmex 1.99 # draw handle
2305 root 1.206 $tex[0]->draw_quad_alpha ($self->{knob_x}, 0, $self->{knob_w}, 1);
2306 root 1.69
2307 elmex 1.99 glDisable GL_TEXTURE_2D;
2308 root 1.51 }
2309    
2310 root 1.39 #############################################################################
2311    
2312 root 1.225 package CFClient::UI::ValSlider;
2313    
2314     our @ISA = CFClient::UI::HBox::;
2315    
2316     sub new {
2317     my ($class, %arg) = @_;
2318    
2319     my $range = delete $arg{range};
2320    
2321     my $self = $class->SUPER::new (
2322     slider => (new CFClient::UI::Slider expand => 1, range => $range),
2323     entry => (new CFClient::UI::Label text => "", template => delete $arg{template}),
2324     to_value => sub { shift },
2325     from_value => sub { shift },
2326     %arg,
2327     );
2328    
2329     $self->{slider}->connect (changed => sub {
2330     my ($self, $value) = @_;
2331     $self->{parent}{entry}->set_text ($self->{parent}{to_value}->($value));
2332     $self->{parent}->emit (changed => $value);
2333     });
2334    
2335     # $self->{entry}->connect (changed => sub {
2336     # my ($self, $value) = @_;
2337     # $self->{parent}{slider}->set_value ($self->{parent}{from_value}->($value));
2338     # $self->{parent}->emit (changed => $value);
2339     # });
2340    
2341     $self->add ($self->{slider}, $self->{entry});
2342    
2343     $self->{slider}->emit (changed => $self->{slider}{range}[0]);
2344    
2345     $self
2346     }
2347    
2348     sub set_range { shift->{slider}->set_range (@_) }
2349     sub set_value { shift->{slider}->set_value (@_) }
2350    
2351     #############################################################################
2352    
2353 root 1.97 package CFClient::UI::TextView;
2354    
2355     our @ISA = CFClient::UI::HBox::;
2356    
2357 root 1.138 use CFClient::OpenGL;
2358 root 1.97
2359     sub new {
2360     my $class = shift;
2361    
2362     my $self = $class->SUPER::new (
2363 root 1.164 fontsize => 1,
2364     can_events => 0,
2365     #font => default_font
2366 root 1.105 @_,
2367 root 1.164
2368 root 1.195 layout => (new CFClient::Layout 1),
2369 root 1.164 par => [],
2370     height => 0,
2371     children => [
2372 root 1.97 (new CFClient::UI::Empty expand => 1),
2373     (new CFClient::UI::Slider vertical => 1),
2374     ],
2375     );
2376    
2377 root 1.176 $self->{children}[1]->connect (changed => sub { $self->update });
2378 root 1.107
2379 root 1.97 $self
2380     }
2381    
2382 root 1.107 sub set_fontsize {
2383     my ($self, $fontsize) = @_;
2384    
2385     $self->{fontsize} = $fontsize;
2386     $self->reflow;
2387     }
2388    
2389 root 1.220 sub size_allocate {
2390 root 1.259 my ($self, $w, $h) = @_;
2391 root 1.220
2392 root 1.259 $self->SUPER::size_allocate ($w, $h);
2393 root 1.220
2394     $self->{layout}->set_font ($self->{font}) if $self->{font};
2395     $self->{layout}->set_height ($self->{fontsize} * $::FONTSIZE);
2396     $self->{layout}->set_width ($self->{children}[0]{w});
2397    
2398     $self->reflow;
2399     }
2400    
2401 root 1.228 sub text_size {
2402 root 1.220 my ($self, $text, $indent) = @_;
2403 root 1.105
2404     my $layout = $self->{layout};
2405    
2406 root 1.134 $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2407 root 1.220 $layout->set_width ($self->{children}[0]{w} - $indent);
2408 root 1.195 $layout->set_markup ($text);
2409 root 1.105
2410 root 1.228 $layout->size
2411 root 1.105 }
2412    
2413     sub reflow {
2414     my ($self) = @_;
2415    
2416 root 1.107 $self->{need_reflow}++;
2417     $self->update;
2418 root 1.105 }
2419    
2420 root 1.227 sub set_offset {
2421     my ($self, $offset) = @_;
2422    
2423     # todo: base offset on lines or so, not on pixels
2424     $self->{children}[1]->set_value ($offset);
2425     }
2426    
2427 root 1.226 sub clear {
2428     my ($self) = @_;
2429    
2430     $self->{par} = [];
2431     $self->{height} = 0;
2432 root 1.227 $self->{children}[1]->set_range ([0, 0, 0, 1, 1]);
2433 root 1.226 }
2434    
2435 root 1.97 sub add_paragraph {
2436 root 1.220 my ($self, $color, $text, $indent) = @_;
2437 root 1.97
2438 root 1.220 for my $line (split /\n/, $text) {
2439 root 1.228 my ($w, $h) = $self->text_size ($line);
2440     $self->{height} += $h;
2441     push @{$self->{par}}, [$w + $indent, $h, $color, $indent, $line];
2442 root 1.220 }
2443 root 1.105
2444 root 1.227 $self->{children}[1]->set_range ([$self->{height}, 0, $self->{height}, $self->{h}, 1]);
2445 root 1.97 }
2446    
2447 root 1.105 sub update {
2448 root 1.97 my ($self) = @_;
2449    
2450 root 1.105 $self->SUPER::update;
2451    
2452     return unless $self->{h} > 0;
2453    
2454 root 1.107 delete $self->{texture};
2455    
2456 root 1.198 $ROOT->on_post_alloc ($self, sub {
2457 root 1.228 my ($W, $H) = @{$self->{children}[0]}{qw(w h)};
2458    
2459 root 1.107 if (delete $self->{need_reflow}) {
2460     my $height = 0;
2461    
2462 root 1.228 my $layout = $self->{layout};
2463    
2464     $layout->set_height ($self->{fontsize} * $::FONTSIZE);
2465    
2466     for (@{$self->{par}}) {
2467     if (1 || $_->[0] >= $W) { # TODO: works,but needs reconfigure etc. support
2468     $layout->set_width ($W - $_->[3]);
2469     $layout->set_markup ($_->[4]);
2470     my ($w, $h) = $layout->size;
2471     $_->[0] = $w + $_->[3];
2472     $_->[1] = $h;
2473     }
2474    
2475     $height += $_->[1];
2476     }
2477 root 1.107
2478     $self->{height} = $height;
2479    
2480 root 1.228 $self->{children}[1]->set_range ([$height, 0, $height, $H, 1]);
2481 root 1.107
2482     delete $self->{texture};
2483     }
2484    
2485 root 1.228 $self->{texture} ||= new_from_opengl CFClient::Texture $W, $H, sub {
2486 root 1.215 glClearColor 0.5, 0.5, 0.5, 0;
2487 root 1.107 glClear GL_COLOR_BUFFER_BIT;
2488    
2489     my $top = int $self->{children}[1]{range}[0];
2490 root 1.105
2491 root 1.107 my $y0 = $top;
2492 root 1.228 my $y1 = $top + $H;
2493 root 1.105
2494 root 1.107 my $y = 0;
2495 root 1.97
2496 root 1.107 my $layout = $self->{layout};
2497 root 1.97
2498 root 1.157 $layout->set_font ($self->{font}) if $self->{font};
2499    
2500 root 1.220 glEnable GL_BLEND;
2501 root 1.228 #TODO# not correct in windows where rgba is forced off
2502 root 1.220 glBlendFunc GL_ONE, GL_ONE_MINUS_SRC_ALPHA;
2503    
2504 root 1.107 for my $par (@{$self->{par}}) {
2505 root 1.228 my $h = $par->[1];
2506 root 1.97
2507 root 1.107 if ($y0 < $y + $h && $y < $y1) {
2508 root 1.228 $layout->set_foreground (@{ $par->[2] });
2509     $layout->set_width ($W - $par->[3]);
2510     $layout->set_markup ($par->[4]);
2511 root 1.220
2512     my ($w, $h, $data, $format, $internalformat) = $layout->render;
2513 root 1.105
2514 root 1.228 glRasterPos $par->[3], $y - $y0;
2515 root 1.220 glDrawPixels $w, $h, $format, GL_UNSIGNED_BYTE, $data;
2516 root 1.107 }
2517    
2518     $y += $h;
2519 root 1.105 }
2520    
2521 root 1.220 glDisable GL_BLEND;
2522 root 1.107 };
2523     });
2524 root 1.105 }
2525 root 1.97
2526 root 1.105 sub _draw {
2527     my ($self) = @_;
2528 root 1.97
2529 root 1.176 glEnable GL_TEXTURE_2D;
2530     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2531     glColor 1, 1, 1, 1;
2532 root 1.216 $self->{texture}->draw_quad_alpha (0, 0, $self->{children}[0]{w}, $self->{children}[0]{h});
2533 root 1.176 glDisable GL_TEXTURE_2D;
2534 root 1.97
2535 root 1.106 $self->{children}[1]->draw;
2536    
2537 root 1.97 }
2538    
2539     #############################################################################
2540    
2541 root 1.73 package CFClient::UI::Animator;
2542 root 1.35
2543 root 1.138 use CFClient::OpenGL;
2544 root 1.35
2545 root 1.73 our @ISA = CFClient::UI::Bin::;
2546 root 1.35
2547     sub moveto {
2548     my ($self, $x, $y) = @_;
2549    
2550     $self->{moveto} = [$self->{x}, $self->{y}, $x, $y];
2551 root 1.56 $self->{speed} = 0.001;
2552 root 1.35 $self->{time} = 1;
2553    
2554     ::animation_start $self;
2555     }
2556    
2557     sub animate {
2558     my ($self, $interval) = @_;
2559    
2560     $self->{time} -= $interval * $self->{speed};
2561     if ($self->{time} <= 0) {
2562     $self->{time} = 0;
2563     ::animation_stop $self;
2564     }
2565    
2566     my ($x0, $y0, $x1, $y1) = @{$self->{moveto}};
2567    
2568     $self->{x} = $x0 * $self->{time} + $x1 * (1 - $self->{time});
2569     $self->{y} = $y0 * $self->{time} + $y1 * (1 - $self->{time});
2570     }
2571    
2572     sub _draw {
2573     my ($self) = @_;
2574    
2575     glPushMatrix;
2576 root 1.51 glRotate $self->{time} * 1000, 0, 1, 0;
2577 root 1.38 $self->{children}[0]->draw;
2578 root 1.35 glPopMatrix;
2579     }
2580    
2581 root 1.51 #############################################################################
2582    
2583 root 1.96 package CFClient::UI::Flopper;
2584    
2585     our @ISA = CFClient::UI::Button::;
2586    
2587     sub new {
2588     my $class = shift;
2589    
2590     my $self = $class->SUPER::new (
2591 root 1.243 state => 0,
2592     on_activate => \&toggle_flopper,
2593 root 1.96 @_
2594     );
2595    
2596     $self
2597     }
2598    
2599     sub toggle_flopper {
2600     my ($self) = @_;
2601    
2602 elmex 1.245 $self->{other}->toggle_visibility;
2603 root 1.96 }
2604    
2605     #############################################################################
2606    
2607 root 1.153 package CFClient::UI::Tooltip;
2608    
2609     our @ISA = CFClient::UI::Bin::;
2610    
2611     use CFClient::OpenGL;
2612    
2613     sub new {
2614     my $class = shift;
2615    
2616     $class->SUPER::new (
2617     @_,
2618     can_events => 0,
2619     )
2620     }
2621    
2622 root 1.196 sub set_tooltip_from {
2623     my ($self, $widget) = @_;
2624 root 1.195
2625 root 1.259 my $tooltip = $widget->{tooltip};
2626    
2627     if ($ENV{CFPLUS_DEBUG} & 2) {
2628     $tooltip .= "\n\n" . (ref $widget) . "\n"
2629     . "$widget->{x} $widget->{y} $widget->{w} $widget->{h}\n"
2630     . "req $widget->{req_w} $widget->{req_h}\n"
2631     . "visible $widget->{visible}";
2632     }
2633    
2634 root 1.197 $self->add (new CFClient::UI::Label
2635 root 1.259 markup => $tooltip,
2636 root 1.213 max_w => ($widget->{tooltip_width} || 0.25) * $::WIDTH,
2637     fontsize => 0.8,
2638     fg => [0, 0, 0, 1],
2639     ellipsise => 0,
2640     font => ($widget->{tooltip_font} || $::FONT_PROP),
2641 root 1.197 );
2642 root 1.153 }
2643    
2644     sub size_request {
2645     my ($self) = @_;
2646    
2647     my ($w, $h) = @{$self->child}{qw(req_w req_h)};
2648    
2649 root 1.154 ($w + 4, $h + 4)
2650     }
2651    
2652 root 1.162 sub size_allocate {
2653 root 1.259 my ($self, $w, $h) = @_;
2654 root 1.162
2655 root 1.259 $self->SUPER::size_allocate ($w - 4, $h - 4);
2656 root 1.162 }
2657    
2658 root 1.253 sub visibility_change {
2659     my ($self, $visible) = @_;
2660    
2661     return unless $visible;
2662    
2663     $self->{root}->on_post_alloc ("move_$self" => sub {
2664 root 1.254 my $widget = $self->{owner}
2665     or return;
2666 root 1.253
2667     my ($x, $y) = $widget->coord2global ($widget->{w}, 0);
2668    
2669     ($x, $y) = $widget->coord2global (-$self->{w}, 0)
2670     if $x + $self->{w} > $::WIDTH;
2671    
2672 root 1.256 $self->move_abs ($x, $y);
2673 root 1.253 });
2674     }
2675    
2676 root 1.154 sub _draw {
2677     my ($self) = @_;
2678    
2679     glTranslate 0.375, 0.375;
2680    
2681     my ($w, $h) = @$self{qw(w h)};
2682    
2683     glColor 1, 0.8, 0.4;
2684     glBegin GL_QUADS;
2685     glVertex 0 , 0;
2686     glVertex 0 , $h;
2687     glVertex $w, $h;
2688     glVertex $w, 0;
2689     glEnd;
2690    
2691     glColor 0, 0, 0;
2692     glBegin GL_LINE_LOOP;
2693     glVertex 0 , 0;
2694     glVertex 0 , $h;
2695     glVertex $w, $h;
2696     glVertex $w, 0;
2697     glEnd;
2698    
2699 root 1.197 glTranslate 2 - 0.375, 2 - 0.375;
2700 root 1.252
2701 root 1.154 $self->SUPER::_draw;
2702 root 1.153 }
2703    
2704     #############################################################################
2705    
2706 root 1.162 package CFClient::UI::Face;
2707    
2708     our @ISA = CFClient::UI::Base::;
2709    
2710     use CFClient::OpenGL;
2711    
2712     sub new {
2713     my $class = shift;
2714    
2715 root 1.217 my $self = $class->SUPER::new (
2716 root 1.234 aspect => 1,
2717     can_events => 0,
2718 root 1.162 @_,
2719 root 1.217 );
2720    
2721     if ($self->{anim} && $self->{animspeed}) {
2722     Scalar::Util::weaken (my $widget = $self);
2723    
2724     $self->{timer} = Event->timer (
2725     at => $self->{animspeed} * int $::NOW / $self->{animspeed},
2726     hard => 1,
2727     interval => $self->{animspeed},
2728     cb => sub {
2729     ++$widget->{frame};
2730     $widget->update;
2731     },
2732     );
2733     }
2734    
2735     $self
2736 root 1.162 }
2737    
2738     sub size_request {
2739     (32, 8)
2740     }
2741    
2742 root 1.222 sub update {
2743     my ($self) = @_;
2744    
2745     return unless $self->{visible};
2746    
2747     $self->SUPER::update;
2748     }
2749    
2750 elmex 1.179 sub _draw {
2751 root 1.162 my ($self) = @_;
2752    
2753 root 1.227 return unless $::CONN;
2754 root 1.162
2755 root 1.217 my $face;
2756    
2757     if ($self->{frame}) {
2758     my $anim = $::CONN->{anim}[$self->{anim}];
2759    
2760     $face = $anim->[ $self->{frame} % @$anim ]
2761     if $anim && @$anim;
2762     }
2763    
2764     my $tex = $::CONN->{texture}[$::CONN->{faceid}[$face || $self->{face}]];
2765    
2766 root 1.162 if ($tex) {
2767     glEnable GL_TEXTURE_2D;
2768     glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE;
2769     glColor 1, 1, 1, 1;
2770 root 1.195 $tex->draw_quad_alpha (0, 0, $self->{w}, $self->{h});
2771 root 1.162 glDisable GL_TEXTURE_2D;
2772     }
2773     }
2774    
2775 root 1.217 sub DESTROY {
2776     my ($self) = @_;
2777    
2778     $self->{timer}->cancel
2779     if $self->{timer};
2780    
2781     $self->SUPER::DESTROY;
2782     }
2783    
2784 root 1.162 #############################################################################
2785    
2786 root 1.186 package CFClient::UI::Inventory;
2787    
2788     our @ISA = CFClient::UI::ScrolledWindow::;
2789    
2790     sub new {
2791     my $class = shift;
2792    
2793     my $self = $class->SUPER::new (
2794 root 1.234 scrolled => (new CFClient::UI::Table col_expand => [0, 1, 0]),
2795 root 1.186 @_,
2796     );
2797    
2798     $self
2799     }
2800    
2801     sub set_items {
2802     my ($self, $items) = @_;
2803    
2804     $self->{scrolled}->clear;
2805     return unless $items;
2806    
2807 root 1.201 my @items = sort {
2808     ($a->{type} <=> $b->{type})
2809     or ($a->{name} cmp $b->{name})
2810     } @$items;
2811 root 1.186
2812     $self->{real_items} = \@items;
2813    
2814 root 1.234 my $row = 0;
2815 root 1.186 for my $item (@items) {
2816 root 1.234 CFClient::Item::update_widgets $item;
2817    
2818     $self->{scrolled}->add (0, $row, $item->{face_widget});
2819     $self->{scrolled}->add (1, $row, $item->{desc_widget});
2820     $self->{scrolled}->add (2, $row, $item->{weight_widget});
2821 root 1.186
2822 root 1.234 $row++;
2823 elmex 1.224 }
2824 root 1.186 }
2825    
2826     #############################################################################
2827    
2828 root 1.178 package CFClient::UI::Menu;
2829    
2830     our @ISA = CFClient::UI::FancyFrame::;
2831    
2832     use CFClient::OpenGL;
2833    
2834     sub new {
2835     my $class = shift;
2836    
2837     my $self = $class->SUPER::new (
2838     items => [],
2839     z => 100,
2840     @_,
2841     );
2842    
2843     $self->add ($self->{vbox} = new CFClient::UI::VBox);
2844    
2845     for my $item (@{ $self->{items} }) {
2846     my ($widget, $cb) = @$item;
2847    
2848     # handle various types of items, only text for now
2849     if (!ref $widget) {
2850     $widget = new CFClient::UI::Label
2851     can_hover => 1,
2852     can_events => 1,
2853     text => $widget;
2854     }
2855    
2856     $self->{item}{$widget} = $item;
2857    
2858     $self->{vbox}->add ($widget);
2859     }
2860    
2861     $self
2862     }
2863    
2864     # popup given the event (must be a mouse button down event currently)
2865     sub popup {
2866     my ($self, $ev) = @_;
2867    
2868 root 1.231 $self->_emit ("popdown");
2869 root 1.178
2870     # maybe save $GRAB? must be careful about events...
2871     $GRAB = $self;
2872     $self->{button} = $ev->{button};
2873    
2874     $self->show;
2875 root 1.258 $self->move_abs ($ev->{x} - $self->{w} * 0.5, $ev->{y} - $self->{h} * 0.5);
2876 root 1.178 }
2877    
2878     sub mouse_motion {
2879     my ($self, $ev, $x, $y) = @_;
2880    
2881 root 1.182 # TODO: should use vbox->find_widget or so
2882 root 1.178 $HOVER = $ROOT->find_widget ($ev->{x}, $ev->{y});
2883     $self->{hover} = $self->{item}{$HOVER};
2884     }
2885    
2886     sub button_up {
2887     my ($self, $ev, $x, $y) = @_;
2888    
2889     if ($ev->{button} == $self->{button}) {
2890     undef $GRAB;
2891     $self->hide;
2892    
2893 root 1.231 $self->_emit ("popdown");
2894 root 1.178 $self->{hover}[1]->() if $self->{hover};
2895     }
2896     }
2897    
2898     #############################################################################
2899    
2900 root 1.194 package CFClient::UI::Statusbox;
2901    
2902     our @ISA = CFClient::UI::VBox::;
2903    
2904 root 1.210 sub new {
2905     my $class = shift;
2906    
2907     $class->SUPER::new (
2908     fontsize => 0.8,
2909     @_,
2910     )
2911     }
2912    
2913 root 1.194 sub reorder {
2914     my ($self) = @_;
2915     my $NOW = time;
2916    
2917     while (my ($k, $v) = each %{ $self->{item} }) {
2918     delete $self->{item}{$k} if $v->{timeout} < $NOW;
2919     }
2920    
2921     my @widgets;
2922 root 1.197
2923     my @items = sort {
2924     $a->{pri} <=> $b->{pri}
2925     or $b->{id} <=> $a->{id}
2926     } values %{ $self->{item} };
2927    
2928 root 1.194 my $count = 10 + 1;
2929     for my $item (@items) {
2930     last unless --$count;
2931    
2932     push @widgets, $item->{label} ||= do {
2933     # TODO: doesn't handle markup well (read as: at all)
2934 root 1.197 my $short = $item->{count} > 1
2935     ? "<b>$item->{count} ×</b> $item->{text}"
2936     : $item->{text};
2937    
2938 root 1.194 for ($short) {
2939     s/^\s+//;
2940 root 1.205 s/\s+/ /g;
2941 root 1.194 }
2942    
2943     new CFClient::UI::Label
2944 root 1.196 markup => $short,
2945 root 1.197 tooltip => $item->{tooltip},
2946 root 1.196 tooltip_font => $::FONT_PROP,
2947 root 1.197 tooltip_width => 0.67,
2948 root 1.213 fontsize => $item->{fontsize} || $self->{fontsize},
2949     max_w => $::WIDTH * 0.44,
2950 root 1.205 fg => $item->{fg},
2951 root 1.196 can_events => 1,
2952 root 1.197 can_hover => 1
2953 root 1.194 };
2954     }
2955    
2956     $self->clear;
2957 root 1.197 $self->SUPER::add (reverse @widgets);
2958 root 1.194 }
2959    
2960     sub add {
2961     my ($self, $text, %arg) = @_;
2962    
2963 root 1.198 $text =~ s/^\s+//;
2964     $text =~ s/\s+$//;
2965    
2966 root 1.233 return unless $text;
2967    
2968 root 1.197 my $timeout = time + ((delete $arg{timeout}) || 60);
2969 root 1.194
2970 root 1.197 my $group = exists $arg{group} ? $arg{group} : ++$self->{id};
2971 root 1.194
2972 root 1.197 if (my $item = $self->{item}{$group}) {
2973     if ($item->{text} eq $text) {
2974     $item->{count}++;
2975     } else {
2976     $item->{count} = 1;
2977     $item->{text} = $item->{tooltip} = $text;
2978     }
2979 root 1.198 $item->{id} = ++$self->{id};
2980 root 1.197 $item->{timeout} = $timeout;
2981     delete $item->{label};
2982     } else {
2983     $self->{item}{$group} = {
2984     id => ++$self->{id},
2985     text => $text,
2986     timeout => $timeout,
2987     tooltip => $text,
2988 root 1.205 fg => [0.8, 0.8, 0.8, 0.8],
2989 root 1.197 pri => 0,
2990     count => 1,
2991     %arg,
2992     };
2993     }
2994 root 1.194
2995     $self->reorder;
2996     }
2997    
2998 root 1.213 sub reconfigure {
2999     my ($self) = @_;
3000    
3001     delete $_->{label}
3002     for values %{ $self->{item} || {} };
3003    
3004     $self->reorder;
3005     $self->SUPER::reconfigure;
3006     }
3007    
3008 root 1.194 #############################################################################
3009    
3010 root 1.113 package CFClient::UI::Root;
3011 root 1.51
3012 root 1.73 our @ISA = CFClient::UI::Container::;
3013 root 1.51
3014 root 1.138 use CFClient::OpenGL;
3015 root 1.107
3016 root 1.191 sub new {
3017     my $class = shift;
3018    
3019 root 1.251 my $self = $class->SUPER::new (
3020 root 1.241 visible => 1,
3021 root 1.191 @_,
3022 root 1.251 );
3023    
3024     Scalar::Util::weaken ($self->{root} = $self);
3025    
3026     $self
3027 root 1.191 }
3028    
3029 root 1.51 sub size_request {
3030 root 1.186 my ($self) = @_;
3031    
3032     ($self->{w}, $self->{h})
3033     }
3034    
3035 root 1.256 sub _to_pixel {
3036     my ($coord, $size, $max) = @_;
3037    
3038     $coord =
3039     $coord eq "center" ? ($max - $size) * 0.5
3040     : $coord eq "max" ? $max
3041     : $coord;
3042    
3043     $coord = 0 if $coord < 0;
3044     $coord = $max - $size if $coord > $max - $size;
3045    
3046     int $coord + 0.5
3047     }
3048    
3049 root 1.186 sub size_allocate {
3050 root 1.259 my ($self, $w, $h) = @_;
3051 root 1.186
3052     for my $child ($self->children) {
3053 root 1.149 my ($X, $Y, $W, $H) = @$child{qw(x y req_w req_h)};
3054    
3055 root 1.256 $X = $child->{force_x} if exists $child->{force_x};
3056     $Y = $child->{force_y} if exists $child->{force_y};
3057 root 1.205
3058 root 1.256 $X = _to_pixel $X, $W, $self->{w};
3059     $Y = _to_pixel $Y, $H, $self->{h};
3060 root 1.205
3061 root 1.186 $child->configure ($X, $Y, $W, $H);
3062 root 1.149 }
3063 root 1.191 }
3064    
3065     sub coord2local {
3066     my ($self, $x, $y) = @_;
3067    
3068     ($x, $y)
3069 root 1.51 }
3070    
3071 root 1.191 sub coord2global {
3072 root 1.58 my ($self, $x, $y) = @_;
3073    
3074     ($x, $y)
3075     }
3076    
3077 root 1.51 sub update {
3078     my ($self) = @_;
3079    
3080 root 1.191 $::WANT_REFRESH++;
3081 root 1.51 }
3082    
3083     sub add {
3084 root 1.203 my ($self, @children) = @_;
3085    
3086 root 1.256 $_->{is_toplevel} = 1
3087     for @children;
3088 elmex 1.146
3089 root 1.203 $self->SUPER::add (@children);
3090 root 1.222 }
3091    
3092     sub remove {
3093     my ($self, @children) = @_;
3094    
3095     $self->SUPER::remove (@children);
3096    
3097 root 1.256 delete $self->{is_toplevel}
3098     for @children;
3099    
3100 root 1.222 while (@children) {
3101     my $w = pop @children;
3102     push @children, $w->children;
3103 root 1.231 $w->set_invisible;
3104 root 1.222 }
3105 root 1.51 }
3106    
3107 root 1.107 sub on_refresh {
3108     my ($self, $id, $cb) = @_;
3109    
3110     $self->{refresh_hook}{$id} = $cb;
3111     }
3112    
3113 root 1.198 sub on_post_alloc {
3114     my ($self, $id, $cb) = @_;
3115    
3116     $self->{post_alloc_hook}{$id} = $cb;
3117     }
3118    
3119 root 1.191 sub draw {
3120 root 1.51 my ($self) = @_;
3121    
3122 root 1.198 while ($self->{refresh_hook}) {
3123     $_->()
3124     for values %{delete $self->{refresh_hook}};
3125     }
3126    
3127 root 1.251 if ($self->{realloc}) {
3128 root 1.243 my @queue;
3129 root 1.191
3130 root 1.250 while () {
3131 root 1.251 if ($self->{realloc}) {
3132 root 1.243 #TODO use array-of-depth approach
3133    
3134 root 1.259 use sort 'stable';
3135    
3136 root 1.243 @queue = sort { $a->{visible} <=> $b->{visible} }
3137 root 1.251 @queue, values %{delete $self->{realloc}};
3138 root 1.191 }
3139    
3140 root 1.243 my $widget = pop @queue || last;
3141    
3142 root 1.251 $widget->{visible} or last; # do not resize invisible widgets
3143 root 1.191
3144 root 1.257 my ($w, $h) = $widget->size_request;
3145 root 1.256
3146 root 1.258 $w = List::Util::max $widget->{min_w}, $w + $widget->{padding_x} * 2;
3147     $h = List::Util::max $widget->{min_h}, $h + $widget->{padding_y} * 2;
3148 root 1.257
3149     $w = $widget->{force_w} if exists $widget->{force_w};
3150     $h = $widget->{force_h} if exists $widget->{force_h};
3151 root 1.191
3152 root 1.259 if ($widget->{req_w} != $w || $widget->{req_h} != $h
3153     || delete $widget->{force_realloc}) {
3154     $widget->{req_w} = $w;
3155     $widget->{req_h} = $h;
3156    
3157     $self->{size_alloc}{$widget+0} = $widget;
3158    
3159     if (my $parent = $widget->{parent}) {
3160     $self->{realloc}{$parent+0} = $parent;
3161     #unshift @queue, $parent;
3162     $parent->{force_size_alloc} = 1;
3163     $self->{size_alloc}{$parent+0} = $parent;
3164     }
3165     }
3166 root 1.191
3167 root 1.259 delete $self->{realloc}{$widget+0};
3168 root 1.191 }
3169     }
3170    
3171 root 1.250 while (my $size_alloc = delete $self->{size_alloc}) {
3172 root 1.259 my @queue = sort { $b->{visible} <=> $a->{visible} }
3173 root 1.250 values %$size_alloc;
3174    
3175     while () {
3176 root 1.259 my $widget = pop @queue || last;
3177 root 1.191
3178 root 1.259 my ($w, $h) = @$widget{qw(alloc_w alloc_h)};
3179 root 1.256
3180 root 1.193 $w = 0 if $w < 0;
3181     $h = 0 if $h < 0;
3182    
3183 root 1.256 $w = int $w + 0.5;
3184     $h = int $h + 0.5;
3185    
3186 root 1.259 if ($widget->{w} != $w || $widget->{h} != $h || delete $widget->{force_size_alloc}) {
3187     $widget->{w} = $w;
3188     $widget->{h} = $h;
3189 root 1.252
3190 root 1.259 $widget->emit (size_allocate => $w, $h);
3191     }
3192 root 1.191 }
3193     }
3194    
3195 root 1.198 while ($self->{post_alloc_hook}) {
3196 root 1.191 $_->()
3197 root 1.198 for values %{delete $self->{post_alloc_hook}};
3198 root 1.107 }
3199    
3200 root 1.259
3201 root 1.107 glViewport 0, 0, $::WIDTH, $::HEIGHT;
3202     glClearColor +($::CFG->{fow_intensity}) x 3, 1;
3203     glClear GL_COLOR_BUFFER_BIT;
3204    
3205     glMatrixMode GL_PROJECTION;
3206     glLoadIdentity;
3207 root 1.219 glOrtho 0, $::WIDTH, $::HEIGHT, 0, -10000, 10000;
3208 root 1.107 glMatrixMode GL_MODELVIEW;
3209     glLoadIdentity;
3210    
3211 root 1.51 $self->_draw;
3212     }
3213    
3214     #############################################################################
3215    
3216 root 1.264 package CFClient::UI::SpellList;
3217    
3218     our @ISA = CFClient::UI::FancyFrame::;
3219    
3220     sub new {
3221     my $class = shift;
3222    
3223     my $self = $class->SUPER::new (binding => [], commands => [], @_);
3224    
3225     $self->add (new CFClient::UI::ScrolledWindow
3226     scrolled => $self->{spellbox} = new CFClient::UI::Table);
3227    
3228     $self;
3229     }
3230    
3231     # XXX: Do sorting? Argl...
3232     sub add_spell {
3233     my ($self, $spell) = @_;
3234     $self->{spells}->{$spell->{name}} = $spell;
3235    
3236     $self->{spellbox}->add (0, $self->{tbl_idx}, new CFClient::UI::Face
3237     face => $spell->{face},
3238     can_hover => 1,
3239     can_events => 1,
3240     tooltip => $spell->{message});
3241    
3242     $self->{spellbox}->add (1, $self->{tbl_idx}, new CFClient::UI::Label
3243     text => $spell->{name},
3244     can_hover => 1,
3245     can_events => 1,
3246     tooltip => $spell->{message},
3247     expand => 1);
3248    
3249     $self->{spellbox}->add (2, $self->{tbl_idx}, new CFClient::UI::Label
3250     text => (sprintf "lvl: %2d sp: %2d dmg: %2d",
3251     $spell->{level}, ($spell->{mana} || $spell->{grace}), $spell->{damage}),
3252     expand => 1);
3253    
3254     $self->{spellbox}->add (3, $self->{tbl_idx}++, new CFClient::UI::Button
3255     text => "bind to key",
3256     on_activate => sub { $::BIND_EDITOR->do_quick_binding (["cast $spell->{name}"]) });
3257     }
3258    
3259     sub rebuild_spell_list {
3260     my ($self) = @_;
3261     $self->{tbl_idx} = 0;
3262     $self->add_spell ($_) for values %{$self->{spells}};
3263     }
3264    
3265     sub remove_spell {
3266     my ($self, $spell) = @_;
3267     delete $self->{spells}->{$spell->{name}};
3268     $self->rebuild_spell_list;
3269     }
3270    
3271     #############################################################################
3272    
3273 elmex 1.260 package CFClient::UI::BindEditor;
3274    
3275     our @ISA = CFClient::UI::FancyFrame::;
3276    
3277     sub new {
3278     my $class = shift;
3279    
3280     my $self = $class->SUPER::new (binding => [], commands => [], @_);
3281    
3282     $self->add (my $vb = new CFClient::UI::VBox);
3283    
3284    
3285     $vb->add ($self->{rec_btn} = new CFClient::UI::Button
3286     text => "start recording",
3287     tooltip => "Start/Stops recording of actions."
3288     ."All subsequent actions after the recording started will be captured."
3289     ."The actions are displayed after the record was stopped."
3290     ."To bind the action you have to click on the 'Bind' button",
3291     on_activate => sub {
3292     unless ($self->{recording}) {
3293     $self->start;
3294     } else {
3295     $self->stop;
3296     }
3297     });
3298    
3299 elmex 1.261 $vb->add (new CFClient::UI::Label text => "Actions:");
3300 elmex 1.260 $vb->add ($self->{cmdbox} = new CFClient::UI::VBox);
3301    
3302 elmex 1.261 $vb->add (new CFClient::UI::Label text => "Bound to: ");
3303     $vb->add (my $hb = new CFClient::UI::HBox);
3304     $hb->add ($self->{keylbl} = new CFClient::UI::Label expand => 1);
3305     $hb->add (new CFClient::UI::Button
3306     text => "bind",
3307     tooltip => "This opens a query where you have to press the key combination to bind the recorded actions",
3308     on_activate => sub {
3309     $self->ask_for_bind;
3310     });
3311    
3312 elmex 1.260 $vb->add (my $hb = new CFClient::UI::HBox);
3313     $hb->add (new CFClient::UI::Button
3314     text => "ok",
3315 elmex 1.261 expand => 1,
3316 elmex 1.260 tooltip => "This closes the binding editor and saves the binding",
3317     on_activate => sub {
3318     $self->hide;
3319     $self->commit;
3320     });
3321    
3322     $hb->add (new CFClient::UI::Button
3323     text => "cancel",
3324 elmex 1.261 expand => 1,
3325 elmex 1.260 tooltip => "This closes the binding editor without saving",
3326     on_activate => sub {
3327     $self->hide;
3328     $self->{binding_cancel}->()
3329     if $self->{binding_cancel};
3330     });
3331    
3332     $self->update_binding_widgets;
3333    
3334     $self
3335     }
3336    
3337     sub commit {
3338     my ($self) = @_;
3339     my ($mod, $sym, $cmds) = $self->get_binding;
3340     if ($sym != 0 && @$cmds > 0) {
3341     $::STATUSBOX->add ("Bound actions to '".CFClient::Binder::keycombo_to_name ($mod, $sym)
3342     ."'. Don't forget 'Save Config'!");
3343     $self->{binding_change}->($mod, $sym, $cmds)
3344     if $self->{binding_change};
3345     } else {
3346 elmex 1.261 $::STATUSBOX->add ("No action bound, no key or action specified!");
3347 elmex 1.260 $self->{binding_cancel}->()
3348     if $self->{binding_cancel};
3349     }
3350     }
3351    
3352     sub start {
3353     my ($self) = @_;
3354    
3355     $self->{rec_btn}->set_text ("stop recording");
3356     $self->{recording} = 1;
3357     $self->clear_command_list;
3358     $::CONN->start_record if $::CONN;
3359     }
3360    
3361     sub stop {
3362     my ($self) = @_;
3363    
3364     $self->{rec_btn}->set_text ("start recording");
3365     $self->{recording} = 0;
3366    
3367     my $rec;
3368     $rec = $::CONN->stop_record if $::CONN;
3369     return unless ref $rec eq 'ARRAY';
3370     $self->set_command_list ($rec);
3371     }
3372    
3373 elmex 1.262 # if $commit is true, the binding will be set after the user entered a key combo
3374 elmex 1.260 sub ask_for_bind {
3375     my ($self, $commit) = @_;
3376    
3377     CFClient::Binder::open_binding_dialog (sub {
3378     my ($mod, $sym) = @_;
3379     $self->{binding} = [$mod, $sym]; # XXX: how to stop that memleak?
3380     $self->update_binding_widgets;
3381     $self->commit if $commit;
3382     });
3383     }
3384    
3385 elmex 1.262 # $mod and $sym are the modifiers and key symbol
3386     # $cmds is a array ref of strings (the commands)
3387     # $cb is the callback that is executed on OK
3388     # $ccb is the callback that is executed on CANCEL and
3389     # when the binding was unsuccessful on OK
3390 elmex 1.260 sub set_binding {
3391     my ($self, $mod, $sym, $cmds, $cb, $ccb) = @_;
3392    
3393     $self->clear_command_list;
3394     $self->{recording} = 0;
3395     $self->{rec_btn}->set_text ("start recording");
3396    
3397     $self->{binding} = [$mod, $sym];
3398     $self->{commands} = $cmds;
3399    
3400     $self->{binding_change} = $cb;
3401     $self->{binding_cancel} = $ccb;
3402    
3403     $self->update_binding_widgets;
3404     }
3405    
3406 elmex 1.262 # this is a shortcut method that asks for a binding
3407     # and then just binds it.
3408     sub do_quick_binding {
3409     my ($self, $cmds) = @_;
3410     $self->set_binding (undef, undef, $cmds, sub {
3411     $::CFG->{bindings}->{$_[0]}->{$_[1]} = $_[2];
3412     });
3413     $self->ask_for_bind (1);
3414     }
3415    
3416 elmex 1.260 sub update_binding_widgets {
3417     my ($self) = @_;
3418     my ($mod, $sym, $cmds) = $self->get_binding;
3419     $self->{keylbl}->set_text (CFClient::Binder::keycombo_to_name ($mod, $sym));
3420     $self->set_command_list ($cmds);
3421     }
3422    
3423     sub get_binding {
3424     my ($self) = @_;
3425     return (
3426     $self->{binding}->[0],
3427     $self->{binding}->[1],
3428     [ grep { defined $_ } @{$self->{commands}} ]
3429     );
3430     }
3431    
3432     sub clear_command_list {
3433     my ($self) = @_;
3434     $self->{cmdbox}->clear ();
3435     }
3436    
3437     sub set_command_list {
3438     my ($self, $cmds) = @_;
3439    
3440     $self->{cmdbox}->clear ();
3441     $self->{commands} = $cmds;
3442    
3443     my $idx = 0;
3444    
3445     for (@$cmds) {
3446     $self->{cmdbox}->add (my $hb = new CFClient::UI::HBox);
3447    
3448     my $i = $idx;
3449 elmex 1.263 $hb->add (new CFClient::UI::Label text => $_);
3450 elmex 1.260 $hb->add (new CFClient::UI::Button
3451     text => "delete",
3452     tooltip => "Deletes the action from the record",
3453     on_activate => sub {
3454     $self->{cmdbox}->remove ($hb);
3455     $cmds->[$i] = undef;
3456     });
3457    
3458    
3459     $idx++
3460     }
3461     }
3462    
3463 elmex 1.262 #############################################################################
3464    
3465 root 1.73 package CFClient::UI;
3466 root 1.51
3467 root 1.113 $ROOT = new CFClient::UI::Root;
3468 root 1.213 $TOOLTIP = new CFClient::UI::Tooltip z => 900;
3469 root 1.51
3470     1
3471 root 1.5