1# contains: 2# SpinButton 3# AltSpinButton 4# SpinEdit 5# Gauge 6# Slider 7# CircularSlider 8 9package Prima::Sliders; 10 11use strict; 12use warnings; 13use Prima::Const; 14use Prima::Classes; 15use Prima::IntUtils; 16 17package Prima::AbstractSpinButton; 18use vars qw(@ISA); 19@ISA = qw(Prima::Widget Prima::MouseScroller); 20 21{ 22my %RNT = ( 23 %{Prima::Widget-> notification_types()}, 24 Increment => nt::Default, 25 TrackEnd => nt::Default, 26); 27sub notification_types { return \%RNT; } 28} 29 30sub profile_default 31{ 32 return { 33 %{$_[ 0]-> SUPER::profile_default}, 34 ownerBackColor => 1, 35 color => cl::Black, 36 selectable => 0, 37 tabStop => 0, 38 widgetClass => wc::Button, 39 } 40} 41 42sub init 43{ 44 my $self = shift; 45 my %profile = $self-> SUPER::init( @_); 46 $self-> { pressState} = 0; 47 return %profile; 48} 49 50sub on_mouseclick 51{ 52 my $self = shift; 53 $self-> clear_event; 54 return unless pop; 55 $self-> clear_event unless $self-> notify( "MouseDown", @_); 56} 57 58sub state {($#_)?$_[0]-> set_state ($_[1]):return $_[0]-> {pressState}} 59 60#sub on_trackend {} 61#sub on_increment { 62# my ( $self, $increment) = @_; 63#} 64 65 66package Prima::SpinButton; 67use vars qw(@ISA); 68@ISA = qw(Prima::AbstractSpinButton); 69 70sub profile_default 71{ 72 return { 73 %{$_[ 0]-> SUPER::profile_default}, 74 width => 17 * $::application-> uiScaling, 75 height => 24 * $::application-> uiScaling, 76 } 77} 78 79sub on_mousedown 80{ 81 my ( $self, $btn, $mod, $x, $y) = @_; 82 return if $self-> {mouseTransaction}; 83 return if $btn != mb::Left; 84 my $h = $self-> height; 85 if ( $y >= $h * 0.6) { 86 $self-> { mouseTransaction} = 1; 87 } elsif ( $y < $h * 0.4) { 88 $self-> { mouseTransaction} = 2; 89 } else { 90 $self-> { mouseTransaction} = 3; 91 } 92 delete $self->{prelight}; 93 $self-> { lastMouseOver} = 1; 94 $self-> { startMouseY } = $y; 95 $self-> state( $self-> { mouseTransaction}); 96 $self-> capture(1); 97 $self-> clear_event; 98 $self-> {increment} = 0; 99 if ( $self-> { mouseTransaction} != 3) { 100 $self-> notify( 'Increment', $self-> { mouseTransaction} == 1 ? 1 : -1); 101 $self-> scroll_timer_start; 102 $self-> scroll_timer_semaphore(0); 103 } else { 104 $self-> {pointerSave} = $self-> pointer; 105 $self-> pointer( cr::SizeWE); 106 } 107} 108 109sub on_mouseup 110{ 111 my ( $self, $btn, $mod, $x, $y) = @_; 112 return if $btn != mb::Left; 113 return unless $self-> {mouseTransaction}; 114 my $mt = $self-> {mouseTransaction}; 115 my $inc = $mt != 2 ? 1 : -1; 116 117 $self-> {mouseTransaction} = undef; 118 $self-> {spaceTransaction} = undef; 119 $self-> {lastMouseOver} = undef; 120 $self-> capture(0); 121 $self-> scroll_timer_stop; 122 $self-> state( 0); 123 $self-> pointer( $self-> {pointerSave}), $self-> {pointerSave} = undef 124 if $mt == 3; 125 $self-> {increment} = 0; 126 $self-> notify( 'TrackEnd'); 127} 128 129sub on_mousemove 130{ 131 my ( $self, $mod, $x, $y) = @_; 132 unless ( $self-> {mouseTransaction}) { 133 my $h = $self-> height; 134 my $prelight; 135 if ( $self-> enabled ) { 136 if ( $y >= $h * 0.6) { 137 $prelight = 'lower'; 138 $self-> pointer(cr::Default); 139 } elsif ($y < $h * 0.4 ) { 140 $prelight = 'upper'; 141 $self-> pointer(cr::Default); 142 } else { 143 $prelight = 'middle'; 144 $self-> pointer(cr::SizeWE); 145 } 146 if (( $prelight // '') ne ($self->{prelight} // '')) { 147 $self->{prelight} = $prelight; 148 $self-> repaint; 149 } 150 } 151 return; 152 } 153 my @size = $self-> size; 154 my $mouseOver = $x > 0 && $y > 0 && $x < $size[0] && $y < $size[1]; 155 $self-> state( $self-> {pressState} ? 0 : $self-> {mouseTransaction}) 156 if $self-> { lastMouseOver} != $mouseOver && $self-> {pressState} != 3; 157 $self-> { lastMouseOver} = $mouseOver; 158 159 if ( $self-> {pressState} == 3) { 160 my $d = ( $self-> {startMouseY} - $y) / 3; # 2 is mouse sensitivity 161 $self-> notify( 'Increment', int($self-> {increment}) - int($d)) 162 if int( $self-> {increment}) != int( $d); 163 $self-> {increment} = $d; 164 } elsif ( $self-> {pressState} > 0) { 165 $self-> scroll_timer_start unless $self-> scroll_timer_active; 166 return unless $self-> scroll_timer_semaphore; 167 $self-> scroll_timer_semaphore(0); 168 $self-> notify( 'Increment', $self-> {mouseTransaction} == 1 ? 1 : -1); 169 } else { 170 $self-> scroll_timer_stop; 171 } 172} 173 174sub on_mouseleave 175{ 176 my $self = shift; 177 $self-> repaint if defined( delete $self->{prelight} ); 178} 179 180sub on_paint 181{ 182 my ( $self, $canvas) = @_; 183 my @clr; 184 my ($prelightPart, $prelightColor) = (''); 185 if ( $self-> enabled) { 186 @clr = ($self-> color, $self-> backColor); 187 if ($self->{prelight}) { 188 $prelightColor = $self-> prelight_color($clr[1], 1.5); 189 $prelightPart = $self->{prelight}; 190 } 191 } else { 192 @clr = ( $self-> disabledColor, $self-> disabledBackColor); 193 } 194 my @c3d = ( $self-> light3DColor, $self-> dark3DColor); 195 my @size = $canvas-> size; 196 my $p = $self-> {pressState}; 197 198 $canvas-> rect3d( 0, 0, $size[0] - 1, $size[1] * 0.4 - 1, 2, 199 (($p != 2) ? @c3d : reverse @c3d), ($prelightPart eq 'upper') ? $prelightColor : $clr[1]); 200 $canvas-> rect3d( 0, $size[1] * 0.4, $size[0] - 1, $size[1] * 0.6 - 1, 2, 201 (($p != 3) ? @c3d : reverse @c3d), ($prelightPart eq 'middle') ? $prelightColor : $clr[1]); 202 $canvas-> rect3d( 0, $size[1] * 0.6, $size[0] - 1, $size[1] - 1, 2, 203 (($p != 1) ? @c3d : reverse @c3d), ($prelightPart eq 'lower') ? $prelightColor : $clr[1]); 204 205 $canvas-> color( $clr[0]); 206 my $p1 = ( $p == 1) ? 1 : 0; 207 $canvas-> fillpoly( [ 208 $size[0] * 0.3 + $p1, $size[1] * 0.73 - $p1, 209 $size[0] * 0.5 + $p1, $size[1] * 0.87 - $p1, 210 $size[0] * 0.7 + $p1, $size[1] * 0.73 - $p1 211 ]); 212 $p1 = ( $p == 2) ? 1 : 0; 213 $canvas-> fillpoly( [ 214 $size[0] * 0.3 + $p1, $size[1] * 0.27 - $p1, 215 $size[0] * 0.5 + $p1, $size[1] * 0.13 - $p1, 216 $size[0] * 0.7 + $p1, $size[1] * 0.27 - $p1 217 ]); 218} 219 220sub set_state 221{ 222 my ( $self, $s) = @_; 223 $s = 0 if $s > 3; 224 return if $s == $self-> {pressState}; 225 $self-> {pressState} = $s; 226 $self-> repaint; 227} 228 229package Prima::AltSpinButton; 230use vars qw(@ISA); 231@ISA = qw(Prima::AbstractSpinButton); 232 233sub profile_default 234{ 235 return { 236 %{$_[ 0]-> SUPER::profile_default}, 237 width => 18 * $::application-> uiScaling, 238 height => 18 * $::application-> uiScaling, 239 } 240} 241 242sub profile_check_in 243{ 244 my ( $self, $p, $default) = @_; 245 $p-> {height} = $p-> {width} if !exists( $p-> {height}) && exists( $p-> {width}); 246 $p-> {width} = $p-> {height} if exists( $p-> {height}) && !exists( $p-> {width}); 247 $self-> SUPER::profile_check_in( $p, $default); 248} 249 250sub on_mousedown 251{ 252 my ( $self, $btn, $mod, $x, $y) = @_; 253 return if $self-> {mouseTransaction}; 254 return if $btn != mb::Left; 255 $self-> { mouseTransaction} = 256 (( $x * $self-> height / ( $self-> width || 1)) > $y) ? 257 2 : 1; 258 $self-> { lastMouseOver} = 1; 259 delete $self->{prelight}; 260 $self-> state( $self-> { mouseTransaction}); 261 $self-> capture(1); 262 $self-> clear_event; 263 $self-> notify( 'Increment', $self-> { mouseTransaction} == 1 ? 1 : -1); 264 $self-> scroll_timer_start; 265 $self-> scroll_timer_semaphore(0); 266} 267 268sub on_mouseup 269{ 270 my ( $self, $btn, $mod, $x, $y) = @_; 271 return if $btn != mb::Left; 272 return unless $self-> {mouseTransaction}; 273 $self-> {mouseTransaction} = undef; 274 $self-> {spaceTransaction} = undef; 275 $self-> {lastMouseOver} = undef; 276 $self-> capture(0); 277 $self-> scroll_timer_stop; 278 $self-> state( 0); 279 $self-> notify( 'TrackEnd'); 280} 281 282sub on_mousemove 283{ 284 my ( $self, $mod, $x, $y) = @_; 285 unless ($self-> {mouseTransaction}) { 286 if ( $self-> enabled ) { 287 my $prelight = (( $x * $self-> height / ( $self-> width || 1)) > $y) ? 2 : 1; 288 if (( $self->{prelight} // 0 ) != $prelight) { 289 $self->{prelight} = $prelight; 290 $self->repaint; 291 } 292 } 293 return; 294 } 295 my @size = $self-> size; 296 my $mouseOver = $x > 0 && $y > 0 && $x < $size[0] && $y < $size[1]; 297 $self-> state( $self-> {pressState} ? 0 : $self-> {mouseTransaction}) 298 if $self-> { lastMouseOver} != $mouseOver; 299 $self-> { lastMouseOver} = $mouseOver; 300 if ( $self-> {pressState}) { 301 $self-> scroll_timer_start unless $self-> scroll_timer_active; 302 return unless $self-> scroll_timer_semaphore; 303 $self-> scroll_timer_semaphore(0); 304 $self-> notify( 'Increment', $self-> {mouseTransaction} == 1 ? 1 : -1); 305 } else { 306 $self-> scroll_timer_stop; 307 } 308} 309 310sub on_mouseleave 311{ 312 my $self = shift; 313 $self-> repaint if defined( delete $self->{prelight} ); 314} 315 316sub fix_triangle 317{ 318 my @spot = map { int($_ + .5) } @_; 319 my $dx = $spot[4] - $spot[0]; 320 my $dy = $spot[3] - $spot[1]; 321 if ($dx % 2) { 322 $spot[2] = $spot[0] + ($dx - 1) / 2; 323 $spot[4]--; 324 $dx--; 325 } 326 if ( $dx == 2 ) { 327 $spot[4]++; 328 $spot[0]--; 329 $dx += 2; 330 } 331 $spot[3] -= ($dy > 0) ? 1 : -1 if abs($dy) > $dx / 2; 332 return \@spot; 333} 334 335 336sub on_paint 337{ 338 my ( $self, $canvas) = @_; 339 my @clr = ( $self-> color, $self-> backColor); 340 @clr = ( $self-> hiliteColor, $self-> hiliteBackColor) if $self-> { default}; 341 @clr = ( $self-> disabledColor, $self-> disabledBackColor) if !$self-> enabled; 342 my ($prelightPart, $prelightColor) = (0); 343 if ($self->{prelight}) { 344 $prelightColor = $self-> prelight_color($clr[1], 1.5); 345 $prelightPart = $self->{prelight}; 346 } 347 my @c3d = ( $self-> light3DColor, $self-> dark3DColor); 348 my @size = $canvas-> size; 349 $canvas-> color( $clr[ 1]); 350 $canvas-> bar( 0, 0, $size[0]-1, $size[1]-1); 351 my $p = $self-> {pressState}; 352 353 if ( $prelightPart == 1 && $size[1] > 4 && $size[0] > 4 ) { 354 $canvas->color( $prelightColor ); 355 $canvas->fillpoly([ 356 2, 2, 357 2, $size[1] - 3, 358 $size[0] - 3, $size[1] - 3, 359 ]); 360 } 361 $canvas-> color( $p == 1 ? 0x404040 : $c3d[1]); 362 $canvas-> polyline( [0, 0, 0, $size[1] - 1, $size[0] - 2, $size[1] - 1]); 363 $canvas-> color( $p == 1 ? $c3d[1] : $c3d[0]); 364 $canvas-> polyline( [1, 1, 1, $size[1] - 2, $size[0] - 3, $size[1] - 2]); 365 366 if ( $prelightPart == 2 && $size[1] > 4 && $size[0] > 4 ) { 367 $canvas->color( $prelightColor ); 368 $canvas->fillpoly([ 369 2, 2, 370 $size[0] - 3, $size[1] - 3, 371 $size[0] - 3, 2, 372 ]); 373 } 374 $canvas-> color( $p == 2 ? $c3d[0] : $c3d[1]); 375 $canvas-> polyline([2, 1, $size[0] - 2, 1, $size[0] - 2, $size[1] - 2]); 376 $canvas-> color( $p == 2 ? $c3d[1] : 0x404040); 377 $canvas-> polyline([1, 0, $size[0] - 1, 0, $size[0] - 1, $size[1] - 1]); 378 379 $canvas-> color( $p == 1 ? $c3d[ 0] : $c3d[ 1]); 380 $canvas-> line( -1, 0, $size[0] - 2, $size[1] - 1); 381 $canvas-> color( 0x404040); 382 $canvas-> line( 0, 0, $size[0] - 1, $size[1] - 1); 383 $canvas-> color( $p == 2 ? $c3d[ 1] : $c3d[ 0]); 384 $canvas-> line( 1, 0, $size[0], $size[1] - 1); 385 386 $canvas-> color( $clr[0]); 387 my $p1 = ( $p == 1) ? 1 : 0; 388 $canvas-> fillpoly( fix_triangle( 389 $size[0] * 0.2 + $p1, $size[1] * 0.65 - $p1, 390 $size[0] * 0.3 + $p1, $size[1] * 0.77 - $p1, 391 $size[0] * 0.4 + $p1, $size[1] * 0.65 - $p1 392 )); 393 $p1 = ( $p == 2) ? 1 : 0; 394 $canvas-> fillpoly( fix_triangle( 395 $size[0] * 0.59 + $p1, $size[1] * 0.35 - $p1, 396 $size[0] * 0.69 + $p1, $size[1] * 0.23 - $p1, 397 $size[0] * 0.79 + $p1, $size[1] * 0.35 - $p1 398 )); 399} 400 401sub set_state 402{ 403 my ( $self, $s) = @_; 404 $s = 0 if $s > 2; 405 return if $s == $self-> {pressState}; 406 $self-> {pressState} = $s; 407 $self-> repaint; 408} 409 410package Prima::SpinEdit; 411use vars qw(@ISA %editProps %spinDynas); 412use Prima::InputLine; 413@ISA = qw(Prima::Widget); 414 415 416%editProps = ( 417 alignment => 1, autoScroll => 1, text => 1, 418 charOffset => 1, maxLen => 1, insertMode => 1, firstChar => 1, 419 selection => 1, selStart => 1, selEnd => 1, writeOnly => 1, 420 copy => 1, cut => 1, 'delete' => 1, paste => 1, 421 wordDelimiters => 1, readOnly => 1, passwordChar=> 1, focus => 1, 422 select_all => 1, 423); 424 425%spinDynas = ( onIncrement => 1, onTrackEnd => 1,); 426 427for ( keys %editProps) { 428 eval <<GENPROC; 429 sub $_ { return shift-> {edit}-> $_(\@_); } 430 sub Prima::SpinEdit::DummyEdit::$_ { } 431GENPROC 432} 433 434sub profile_default 435{ 436 my $font = $_[ 0]-> get_default_font; 437 my $fh = $font-> {height} + 2; 438 return { 439 %{Prima::InputLine-> profile_default}, 440 %{$_[ 0]-> SUPER::profile_default}, 441 autoEnableChildren => 1, 442 ownerBackColor => 1, 443 selectable => 0, 444 scaleChildren => 0, 445 min => 0, 446 max => 100, 447 step => 1, 448 pageStep => 10, 449 value => 0, 450 circulate => 0, 451 height => $fh < 20 ? 20 : $fh, 452 editClass => 'Prima::InputLine', 453 spinClass => 'Prima::AltSpinButton', 454 editProfile => {}, 455 spinProfile => {}, 456 editDelegations=> [qw(KeyDown Change MouseWheel Enter Leave DragEnd)], 457 spinDelegations=> [qw(Increment)], 458 } 459} 460 461sub init 462{ 463 my $self = shift; 464 my %profile = @_; 465 my $visible = $profile{visible}; 466 $profile{visible} = 0; 467 for (qw( min max step circulate pageStep)) {$self-> {$_} = 1;}; 468 $self-> {edit} = bless [], q\Prima::SpinEdit::DummyEdit\; 469 %profile = $self-> SUPER::init(%profile); 470 my ( $w, $h) = ( $self-> size); 471 $self-> {spin} = $self-> insert( $profile{spinClass} => 472 ownerBackColor => 1, 473 name => 'Spin', 474 bottom => 1, 475 right => $w - 1, 476 height => $h - 1 * 2, 477 growMode => gm::Right, 478 delegations => $profile{spinDelegations}, 479 (map { $_ => $profile{$_}} grep { exists $profile{$_} ? 1 : 0} keys %spinDynas), 480 %{$profile{spinProfile}}, 481 ); 482 $self-> {edit} = $self-> insert( $profile{editClass} => 483 name => 'InputLine', 484 origin => [ 1, 1], 485 size => [ $w - $self-> {spin}-> width - 1 * 2, $h - 1 * 2], 486 growMode => gm::GrowHiX|gm::GrowHiY, 487 selectable => 1, 488 tabStop => 1, 489 borderWidth => 0, 490 current => 1, 491 delegations => $profile{editDelegations}, 492 (map { $_ => $profile{$_}} keys %editProps), 493 %{$profile{editProfile}}, 494 text => $profile{value}, 495 ); 496 for (qw( min max step value circulate pageStep)) {$self-> $_($profile{$_});}; 497 $self-> visible( $visible); 498 return %profile; 499} 500 501sub on_paint 502{ 503 my ( $self, $canvas) = @_; 504 my @s = $canvas-> size; 505 $canvas-> rect3d( 0, 0, $s[0]-1, $s[1]-1, 1, $self-> dark3DColor, $self-> light3DColor); 506} 507 508sub InputLine_MouseWheel 509{ 510 my ( $self, $edit, $mod, $x, $y, $z) = @_; 511 $z = (abs($z) > 120) ? int($z/120) : (($z > 0) ? 1 : -1); 512 $z *= $self-> {pageStep} if $mod & km::Ctrl; 513 my $value = $self-> value; 514 $self-> value( $value + $z * $self-> {step}); 515 $self-> value( $z > 0 ? $self-> min : $self-> max) 516 if $self-> {circulate} && ( $self-> value == $value); 517 $edit-> clear_event; 518} 519 520sub InputLine_DragEnd 521{ 522 my ( $self, $edit, $clipboard, $action, $mod, $x, $y, $ref ) = @_; 523 return unless $clipboard; 524 my $text = $clipboard->text; 525 return unless defined $text; 526 $text =~ s/^\s+//; 527 $text =~ s/\s+$//; 528 return if $text =~ /^-?\d+(\.\d+)?$/ and $text >= $self->min and $text <= $self->max; 529 $edit->clear_event; 530 $edit->on_dragend(undef, $action, $mod, $x, $y, $ref); 531 $ref->{allow} = 0; 532} 533 534sub Spin_Increment 535{ 536 my ( $self, $spin, $increment) = @_; 537 my $value = $self-> value; 538 $self-> value( $value + $increment * $self-> {step}); 539 $self-> value( $increment > 0 ? $self-> min : $self-> max) 540 if $self-> {circulate} && ( $self-> value == $value); 541} 542 543sub InputLine_KeyDown 544{ 545 my ( $self, $edit, $code, $key, $mod) = @_; 546 $edit-> clear_event, return if 547 $key == kb::NoKey && !($mod & (km::Alt | km::Ctrl)) && 548 chr($code) !~ /^[.\d+-]$/; 549 if ( $key == kb::Up || $key == kb::Down || $key == kb::PgDn || $key == kb::PgUp) { 550 my ($s,$pgs) = ( $self-> step, $self-> pageStep); 551 my $z = ( $key == kb::Up) ? $s : (( $key == kb::Down) ? -$s : 552 (( $key == kb::PgUp) ? $pgs : -$pgs)); 553 if (( $mod & km::Ctrl) && ( $key == kb::PgDn || $key == kb::PgUp)) { 554 $self-> value( $key == kb::PgDn ? $self-> min : $self-> max); 555 } else { 556 my $value = $self-> value; 557 $self-> value( $value + $z); 558 $self-> value( $z > 0 ? $self-> min : $self-> max) 559 if $self-> {circulate} && ( $self-> value == $value); 560 } 561 $edit-> clear_event; 562 return; 563 } 564 if ($key == kb::Enter) { 565 my $value = $edit-> text; 566 $self-> value( $value); 567 $edit-> clear_event if $value ne $self-> value; 568 return; 569 } 570} 571 572sub InputLine_Change 573{ 574 my ( $self, $edit) = @_; 575 $self-> notify(q(Change)); 576} 577 578sub InputLine_Enter 579{ 580 my ( $self, $edit) = @_; 581 $self-> notify(q(Enter)); 582} 583 584sub InputLine_Leave 585{ 586 my ( $self, $edit) = @_; 587 $self-> notify(q(Leave)); 588} 589 590sub set_bounds 591{ 592 my ( $self, $min, $max) = @_; 593 $max = $min if $max < $min; 594 ( $self-> { min}, $self-> { max}) = ( $min, $max); 595 my $oldValue = $self-> value; 596 $self-> value( $max) if $max < $self-> value; 597 $self-> value( $min) if $min > $self-> value; 598} 599 600sub set_step 601{ 602 my ( $self, $step) = @_; 603 $step = 0 if $step < 0; 604 $self-> {step} = $step; 605} 606 607sub circulate 608{ 609 return $_[0]-> {circulate} unless $#_; 610 $_[0]-> {circulate} = $_[1]; 611} 612 613sub pageStep 614{ 615 return $_[0]-> {pageStep} unless $#_; 616 $_[0]-> {pageStep} = $_[1]; 617} 618 619 620sub min {($#_)?$_[0]-> set_bounds($_[1], $_[0]-> {'max'}) : return $_[0]-> {min};} 621sub max {($#_)?$_[0]-> set_bounds($_[0]-> {'min'}, $_[1]) : return $_[0]-> {max};} 622sub step {($#_)?$_[0]-> set_step ($_[1]):return $_[0]-> {step}} 623sub value 624{ 625 if ($#_) { 626 my ( $self, $value) = @_; 627 if ( $value =~ m/^\s*([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?\s*$/) { 628 $value = $self-> {min} if $value < $self-> {min}; 629 $value = $self-> {max} if $value > $self-> {max}; 630 } else { 631 $value = $self-> {min}; 632 } 633 return if $value eq $self-> {edit}-> text; 634 $self-> {edit}-> text( $value); 635 } else { 636 my $self = $_[0]; 637 my $value = $self-> {edit}-> text; 638 if ( $value =~ m/^\s*([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?\s*$/) { 639 $value = $self-> {min} if $value < $self-> {min}; 640 $value = $self-> {max} if $value > $self-> {max}; 641 } else { 642 $value = $self-> {min}; 643 } 644 return $value; 645 } 646} 647 648 649# gauge reliefs 650package 651 gr; 652use constant Sink => -1; 653use constant Border => 0; 654use constant Raise => 1; 655 656 657package Prima::Gauge; 658use vars qw(@ISA); 659@ISA = qw(Prima::Widget); 660 661{ 662my %RNT = ( 663 %{Prima::Widget-> notification_types()}, 664 Stringify => nt::Action, 665); 666 667sub notification_types { return \%RNT; } 668} 669 670sub profile_default 671{ 672 return { 673 %{$_[ 0]-> SUPER::profile_default}, 674 indent => 1, 675 relief => gr::Sink, 676 ownerBackColor => 1, 677 hiliteBackColor=> cl::Blue, 678 hiliteColor => cl::White, 679 min => 0, 680 max => 100, 681 value => 0, 682 threshold => 0, 683 vertical => 0, 684 # additional properties for indeterminate mode 685 indeterminate => '1', 686 sliderLength => 30, 687 } 688} 689 690sub init 691{ 692 my $self = shift; 693 my %profile = $self-> SUPER::init(@_); 694 for (qw( relief value indent min max threshold vertical)) 695 {$self-> {$_} = 0} 696 $self-> {string} = ''; 697 for (qw( vertical threshold min max relief indent value)) 698 {$self-> $_($profile{$_}); } 699 700 # additional properties for indeterminate mode 701 $self->{direction} = 1; 702 for (qw( indeterminate sliderLength)) 703 {$self-> $_($profile{$_}); } 704 # If indeterminate is true, the start value must be > sliderLength 705 $self->value($self->{sliderLength}) if ($self->indeterminate); 706 707 return %profile; 708} 709 710sub setup 711{ 712 $_[0]-> SUPER::setup; 713 $_[0]-> value($_[0]-> {value}); 714} 715 716sub on_paint 717{ 718 my ($self,$canvas) = @_; 719 my ($x, $y) = $canvas-> size; 720 721 $canvas->clear(); 722 my $i = $self-> indent; 723 my ($clComplete,$clBack,$clFore,$clHilite) = ($self-> hiliteBackColor, $self-> backColor, $self-> color, $self-> hiliteColor); 724 my $v = $self-> {vertical}; 725 my $complete = $v ? $y : $x; 726 my $range = ($self-> {max} - $self-> {min}) || 1; 727 $complete = int(($complete - $i*2) * $self-> {value} / $range + 0.5); 728 my ( $l3, $d3) = ( $self-> light3DColor, $self-> dark3DColor); 729 $canvas-> color( $clComplete); 730 731 # INDETERMINATE STYLE HACK 732 my $left_bound = 733 $self->indeterminate ? 734 $complete - ($self->{sliderLength} * ($v ? $y : $x) / $range + 0.5) : 735 $i; 736 $canvas-> bar ( $v ? 737 ($i, $left_bound, $x-$i-1, $i+$complete) : 738 ($left_bound, $i, $i + $complete, $y-$i-1)); 739 740 $canvas-> color( $clBack); 741 $canvas-> bar ( $v ? ($i, $i+$complete+1, $x-$i-1, $y-$i-1) : ( $i+$complete+1, $i, $x-$i-1, $y-$i-1)); 742 743 # draw the border 744 my $relief = $self-> relief; 745 $canvas-> color(( $relief == gr::Sink) ? $d3 : (( $relief == gr::Border) ? cl::Black : $l3)); 746 for ( my $j = 0; $j < $i; $j++) 747 { 748 $canvas-> line( $j, $j, $j, $y - $j - 1); 749 $canvas-> line( $j, $y - $j - 1, $x - $j - 1, $y - $j - 1); 750 } 751 $canvas-> color(( $relief == gr::Sink) ? $l3 : (( $relief == gr::Border) ? cl::Black : $d3)); 752 for ( my $j = 0; $j < $i; $j++) 753 { 754 $canvas-> line( $j + 1, $j, $x - $j - 1, $j); 755 $canvas-> line( $x - $j - 1, $j, $x - $j - 1, $y - $j - 1); 756 } 757 758 759 # draw the text, if neccessary 760 my $s = $self-> {string}; 761 if ( $s ne '') 762 { 763 my ($fw, $fh) = ( $canvas-> get_text_width( $s), $canvas-> font-> height); 764 my $xBeg = int(( $x - $fw) / 2 + 0.5); 765 my $xEnd = $xBeg + $fw; 766 my $yBeg = int(( $y - $fh) / 2 + 0.5); 767 my $yEnd = $yBeg + $fh; 768 my ( $zBeg, $zEnd) = $v ? ( $yBeg, $yEnd) : ( $xBeg, $xEnd); 769 if ( $zBeg > $i + $complete) { 770 $canvas-> color( $clFore); 771 $canvas-> text_shape_out( $s, $xBeg, $yBeg); 772 } elsif ( $zEnd < $i + $complete + 1) { 773 $canvas-> color( $clHilite); 774 $canvas-> text_shape_out( $s, $xBeg, $yBeg); 775 } else { 776 $canvas-> clipRect( $v ? 777 ( 0, 0, $x, $i + $complete) : 778 ( 0, 0, $i + $complete, $y) 779 ); 780 $canvas-> color( $clHilite); 781 $canvas-> text_shape_out( $s, $xBeg, $yBeg); 782 $canvas-> clipRect( $v ? 783 ( 0, $i + $complete + 1, $x, $y) : 784 ( $i + $complete + 1, 0, $x, $y) 785 ); 786 $canvas-> color( $clFore); 787 $canvas-> text_shape_out( $s, $xBeg, $yBeg); 788 } 789 } 790} 791 792sub set_bounds 793{ 794 my ( $self, $min, $max) = @_; 795 $max = $min if $max < $min; 796 ( $self-> { min}, $self-> { max}) = ( $min, $max); 797 my $oldValue = $self-> {value}; 798 $self-> value( $max) if $self-> {value} > $max; 799 $self-> value( $min) if $self-> {value} < $min; 800} 801 802sub value { 803 return $_[0]-> {value} unless $#_; 804 my $v = $_[1] < $_[0]-> {min} ? $_[0]-> {min} : ($_[1] > $_[0]-> {max} ? $_[0]-> {max} : $_[1]); 805 $v -= $_[0]-> {min}; 806 if ($_[0]->indeterminate) { 807 $_[0]-> {value} = $v; 808 $_[0]-> repaint; 809 } 810 else { 811 my $old = $_[0]-> {value}; 812 if (abs($old - $v) >= $_[0]-> {threshold}) { 813 my ($x, $y) = $_[0]-> size; 814 my $i = $_[0]-> {indent}; 815 my $range = ( $_[0]-> {max} - $_[0]-> {min}) || 1; 816 my $x1 = $i + ($x - $i*2) * $old / $range; 817 my $x2 = $i + ($x - $i*2) * $v / $range; 818 ($x1, $x2) = ( $x2, $x1) if $x1 > $x2; 819 my $s = $_[0]-> {string}; 820 $_[0]-> {value} = $v; 821 $_[0]-> notify(q(Stringify), $v, \$_[0]-> {string}); 822 ( $_[0]-> {string} eq $s) ? 823 $_[0]-> invalidate_rect( $x1, 0, $x2+1, $y) : 824 $_[0]-> repaint; 825 } 826 } 827} 828 829sub on_stringify 830{ 831 my ( $self, $value, $sref) = @_; 832 $$sref = sprintf( "%2d%%", $value * 100.0 / (($_[0]-> {max} - $_[0]-> {min})||1)); 833 $self-> clear_event; 834} 835 836sub indent {($#_)?($_[0]-> {indent} = $_[1],$_[0]-> repaint) :return $_[0]-> {indent};} 837sub relief {($#_)?($_[0]-> {relief} = $_[1],$_[0]-> repaint) :return $_[0]-> {relief};} 838sub vertical {($#_)?($_[0]-> {vertical} = $_[1],$_[0]-> repaint):return $_[0]-> {vertical};} 839sub min {($#_)?$_[0]-> set_bounds($_[1], $_[0]-> {'max'}) : return $_[0]-> {min};} 840sub max {($#_)?$_[0]-> set_bounds($_[0]-> {'min'}, $_[1]) : return $_[0]-> {max};} 841sub threshold {($#_)?($_[0]-> {threshold} = $_[1]):return $_[0]-> {threshold};} 842 843sub indeterminate { 844 my ($self, $indeterminate) = @_; 845 return $self-> {indeterminate} unless $#_; 846 847 # Create the timer for the motion in indeterminate mode 848 # if it is not still created 849 unless ( $self->{timer} ) { 850 $self->{timer} = $self->insert( Timer => 851 name => 'Timer', 852 timeout => 25, 853 delegations => ['Tick'], 854 ); 855 } 856 857 # When the style property is changed, reset the timer frequency 858 # and the start_angle and for style circle the end_angle, too 859 if ( $indeterminate) { 860 $self->{timer}->start; 861 } 862 863 else { 864 $self->{timer}->stop; 865 } 866 $self->{indeterminate} = $indeterminate; 867 868} 869 870sub Timer_Tick 871{ 872 my $self = shift; 873 my $newval = $self->value; 874 my $sliderLength = $self->sliderLength; 875 $newval = $newval+1 if ($self->direction == 1); 876 $newval = $newval-1 if ($self->direction == 0); 877 $self->value($newval); 878 $self->direction(0) if ($newval == 100); 879 $self->direction(1) if ($newval == $sliderLength); 880 $self->repaint; 881} 882 883sub direction {($#_)?($_[0]-> {direction} = $_[1]) :return $_[0]-> {direction};} 884sub sliderLength {($#_)?($_[0]-> {sliderLength} = $_[1]) :return $_[0]-> {sliderLength};} 885 886# slider standard schemes 887package 888 ss; 889use constant Gauge => 0; 890use constant Axis => 1; 891use constant Thermometer => 2; 892use constant StdMinMax => 3; 893 894package Prima::AbstractSlider; 895use vars qw(@ISA); 896@ISA = qw(Prima::Widget); 897 898{ 899my %RNT = ( 900 %{Prima::Widget-> notification_types()}, 901 Track => nt::Default, 902); 903sub notification_types { return \%RNT; } 904} 905 906sub profile_default 907{ 908 return { 909 %{$_[ 0]-> SUPER::profile_default}, 910 autoHeight => 0, 911 autoWidth => 0, 912 autoTrack => 1, 913 increment => 10, 914 min => 0, 915 max => 100, 916 ownerBackColor => 1, 917 readOnly => 0, 918 scheme => undef, 919 selectable => 1, 920 snap => 0, 921 step => 1, 922 ticks => undef, 923 value => 0, 924 widgetClass => wc::Slider, 925 } 926} 927 928 929sub init 930{ 931 my $self = shift; 932 for ( qw( min max readOnly snap value autoTrack autoWidth autoHeight)) 933 {$self-> {$_}=0} 934 for ( qw( tickVal tickLen tickTxt )) { $self-> {$_} = [] }; 935 my %profile = $self-> SUPER::init( @_); 936 for ( qw( step min max increment readOnly ticks snap value autoTrack autoHeight autoWidth)) 937 {$self-> $_($profile{$_});} 938 $self-> scheme( $profile{scheme}) if defined $profile{scheme}; 939 return %profile; 940} 941 942sub autoTrack { $#_ ? $_[0]-> {autoTrack} = $_[1] : $_[0]-> {autoTrack} } 943sub autoWidth { $#_ ? $_[0]-> {autoWidth} = $_[1] : $_[0]-> {autoWidth} } 944sub autoHeight { $#_ ? $_[0]-> {autoHeight} = $_[1] : $_[0]-> {autoHeight} } 945 946sub on_mouseclick 947{ 948 my $self = shift; 949 $self-> clear_event; 950 return unless pop; 951 $self-> clear_event unless $self-> notify( "MouseDown", @_); 952} 953 954sub on_mousewheel 955{ 956 my ( $self, $mod, $x, $y, $z) = @_; 957 $self-> set_next_value( $self-> {step} * $z / 120); 958 $self-> clear_event; 959} 960 961sub set_next_value 962{ 963 my ( $self, $dir) = @_; 964 $dir *= -1 if $self-> {min} > $self-> {max}; 965 if ( $self-> snap) { 966 my $v = $self-> value; 967 my $w = $v; 968 return if ( $v + $dir > $self-> {min} and $v + $dir > $self-> {max}) or 969 ( $v + $dir < $self-> {min} and $v + $dir < $self-> {max}); 970 $self-> value( $v += $dir) while $self-> {value} == $w; 971 } else { 972 $self-> value( $self-> value + $dir); 973 } 974} 975 976sub update_geom_sizes {} 977 978sub set_read_only 979{ 980 $_[0]-> {readOnly} = $_[1]; 981 $_[0]-> repaint; 982 $_[0]-> notify(q(MouseUp),0,0,0) if defined $_[0]-> {mouseTransaction}; 983} 984 985 986sub set_snap 987{ 988 $_[0]-> {snap} = $_[1]; 989 $_[0]-> value( $_[0]-> value) if $_[1]; 990} 991 992sub set_step 993{ 994 my $i = $_[1]; 995 $i = 1 if $i == 0; 996 $_[0]-> {step} = $i; 997} 998 999sub get_ticks 1000{ 1001 my $self = $_[0]; 1002 my $i; 1003 my ( $tv, $tl, $tt) = ($self-> {tickVal}, $self-> {tickLen}, $self-> {tickTxt}); 1004 my @t; 1005 for ( $i = 0; $i < scalar @{$tv}; $i++) { 1006 push ( @t, { 1007 value => $$tv[$i], 1008 height => $$tl[$i], 1009 text => $$tt[$i] 1010 }); 1011 } 1012 return @t; 1013} 1014 1015sub set_ticks 1016{ 1017 my $self = shift; 1018 return unless defined $_[0]; 1019 my @ticks = (@_ == 1 and ref($_[0]) eq q(ARRAY)) ? @{$_[0]} : @_; 1020 my @val; 1021 my @len; 1022 my @txt; 1023 for ( @ticks) { 1024 next unless exists $$_{value}; 1025 push( @val, $$_{value}); 1026 push( @len, exists($$_{height}) ? $$_{height} : 0); 1027 push( @txt, exists($$_{text}) ? $$_{text} : undef); 1028 } 1029 $self-> {tickVal} = \@val; 1030 $self-> {tickLen} = \@len; 1031 $self-> {tickTxt} = \@txt; 1032 $self-> {scheme} = undef; 1033 $self-> update_geom_sizes; 1034 $self-> value( $self-> value); 1035 $self-> repaint; 1036} 1037 1038sub set_bound 1039{ 1040 my ( $self, $val, $bound) = @_; 1041 $self-> {$bound} = $val; 1042 $self-> scheme($self-> {scheme}) if defined $self-> {scheme}; 1043 $self-> repaint; 1044} 1045 1046sub set_scheme 1047{ 1048 my ( $self, $s) = @_; 1049 unless ( defined $s) { 1050 $self-> {scheme} = undef; 1051 return; 1052 } 1053 my ( $max, $min) = ( $self-> {max}, $self-> {min}); 1054 $self-> ticks([]), return if $max == $min; 1055 1056 my @t; 1057 my $i; 1058 my $inc = $self-> {increment}; 1059 if ( $s == ss::Gauge) { 1060 for ( $i = $min; $i <= $max; $i += $inc) { 1061 push ( @t, { value => $i, height => 4, text => $i }); 1062 } 1063 } elsif ( $s == ss::Axis) { 1064 for ( $i = $min; $i <= $max; $i += $inc) { 1065 push ( @t, { value => $i, height => 6, text => $i }); 1066 if ( $i < $max) { 1067 for ( 1..4) { 1068 my $v = $i + $inc / 5 * $_; 1069 last if $v > $max; 1070 push ( @t, { value => $v, height => 3 }); 1071 } 1072 } 1073 } 1074 push ( @t, { value => $max, height => 6, text => $max }) if $i != $max; 1075 } elsif ( $s == ss::StdMinMax) { 1076 push ( @t, { value => $min, height => 6, text => "Min" }); 1077 push ( @t, { value => $max, height => 6, text => "Max" }); 1078 } elsif ( $s == ss::Thermometer ) { 1079 for ( $i = $min; $i <= $max; $i += $inc) { 1080 push ( @t, { 1081 value => $i, 1082 height => 6, 1083 text => $i 1084 }); 1085 if ( $i < $max) { 1086 my $j; 1087 for ( $j = 1; $j < 10; $j++) { 1088 my $v = $i + $inc / 10 * $j; 1089 last if $v > $max; 1090 push ( @t, { 1091 value => $v, 1092 height => $j == 5 ? 5 : 3 1093 }); 1094 } 1095 } 1096 } 1097 push ( @t, { value => $max, height => 6, text => $max }) if $i != $max; 1098 } 1099 $self-> ticks( @t); 1100 $self-> {scheme} = $s; 1101} 1102 1103sub increment 1104{ 1105 return $_[0]-> {increment} unless $#_; 1106 my ( $self, $increment) = @_; 1107 $self-> {increment} = $increment; 1108 if ( defined $self-> {scheme}) { 1109 $self-> scheme( $self-> {scheme}); 1110 $self-> repaint; 1111 } 1112} 1113sub readOnly {($#_)?$_[0]-> set_read_only ($_[1]):return $_[0]-> {readOnly};} 1114sub ticks {($#_)?shift-> set_ticks (@_):return $_[0]-> get_ticks;} 1115sub snap {($#_)?$_[0]-> set_snap ($_[1]):return $_[0]-> {snap};} 1116sub step {($#_)?$_[0]-> set_step ($_[1]):return $_[0]-> {step};} 1117sub scheme {($#_)?shift-> set_scheme (@_):return $_[0]-> {scheme}} 1118sub value {($#_)?$_[0]-> {value} = $_[1] :return $_[0]-> {value};} 1119sub min {($#_)?$_[0]-> set_bound($_[1],q(min)):return $_[0]-> {min};} 1120sub max {($#_)?$_[0]-> set_bound($_[1],q(max)):return $_[0]-> {max};} 1121 1122 1123# linear slider tick alignment 1124package 1125 tka; 1126use constant Normal => 0; 1127use constant Alternative => 1; 1128use constant Dual => 2; 1129 1130package Prima::Slider; 1131use vars qw(@ISA); 1132@ISA = qw(Prima::AbstractSlider); 1133 1134sub profile_default 1135{ 1136 return { 1137 %{$_[ 0]-> SUPER::profile_default}, 1138 borderWidth => 0, 1139 ribbonStrip => 0, 1140 shaftBreadth => 6, 1141 knobBreadth => 12, 1142 tickAlign => tka::Normal, 1143 vertical => 0, 1144 scheme => ss::Gauge, 1145 } 1146} 1147 1148sub profile_check_in 1149{ 1150 my ( $self, $p, $default) = @_; 1151 $p-> { autoWidth} = 1 1152 if !exists $p->{autoWidth} and (($p->{vertical} // $default->{vertical}) == 1); 1153 $p-> { autoHeight} = 1 1154 if !exists $p->{autoHeight} and (($p->{vertical} // $default->{vertical}) == 0); 1155 $p-> { autoHeight} = 0 1156 if exists $p-> {height} || exists $p-> {size} || exists $p-> {rect} || 1157 ( exists $p-> {top} && exists $p-> {bottom}); 1158 $p-> { autoWidth} = 0 1159 if exists $p-> {width} || exists $p-> {size} || exists $p-> {rect} || 1160 ( exists $p-> {left} && exists $p-> {right}); 1161 my $sc = $::application->uiScaling; 1162 $p->{$_} = ( exists($p->{$_}) ? $p->{$_} : $default->{$_} ) * $sc for qw(shaftBreadth knobBreadth ); 1163 $self-> SUPER::profile_check_in( $p, $default); 1164} 1165 1166sub init 1167{ 1168 my $self = shift; 1169 $self-> {$_} = 0 1170 for qw( vertical tickAlign ribbonStrip shaftBreadth borderWidth knobBreadth); 1171 my %profile = $self-> SUPER::init( @_); 1172 $self-> $_($profile{$_}) 1173 for qw( vertical tickAlign ribbonStrip shaftBreadth borderWidth knobBreadth); 1174 return %profile; 1175} 1176 1177sub on_paint 1178{ 1179 my ( $self, $canvas) = @_; 1180 my @clr; 1181 my $prelight; 1182 1183 my $enabled = $self->enabled; 1184 1185 if ( $enabled ) { 1186 @clr = ( $self-> color, $self-> backColor); 1187 $prelight = $self-> prelight_color($clr[1], 1.5) if $self->{prelight}; 1188 } else { 1189 @clr = ( $self-> disabledColor, $self-> disabledBackColor) 1190 } 1191 my @c3d = ( $self-> dark3DColor, $self-> light3DColor); 1192 my @cht = ( $self-> hiliteColor, $self-> hiliteBackColor); 1193 my @glyph_deltas = ([$clr[0], 0, 0]); 1194 unshift @glyph_deltas, [cl::White, 1, -1] unless $enabled; 1195 1196 my @size = $canvas-> size; 1197 my ( 1198 $sb, $v, 1199 $range, $min, 1200 $tval, $tlen, $ttxt, 1201 $ta, $kb 1202 ) = ( 1203 $self-> {shaftBreadth}, $self-> {vertical}, 1204 abs($self-> {max} - $self-> {min}) || 1, $self-> {min}, 1205 $self-> {tickVal}, $self-> {tickLen}, $self-> {tickTxt}, 1206 $self-> {tickAlign}, $self->{knobBreadth}, 1207 ); 1208 if ( $ta == tka::Normal) { 1209 $ta = 1; 1210 } elsif ( $ta == tka::Alternative) { 1211 $ta = 2; 1212 } else { 1213 $ta = 3; 1214 } 1215 1216 unless ( $self-> transparent) { 1217 $canvas-> color( $clr[1]); 1218 $canvas-> bar(0,0,@size); 1219 } 1220 $sb = ( $v ? $size[0] : $size[1]) / 6 unless $sb; 1221 $sb = 2 unless $sb; 1222 1223 my ($br, $bh, $mw, $bw); 1224 if ( $v ) { 1225 $bh = $canvas-> font-> height; 1226 $br = $size[1] - 2 * $bh - 2; 1227 } else { 1228 $mw = $canvas-> font-> width; 1229 $bw = $mw + $self-> {borderWidth}; 1230 $br = $size[0] - 2 * $bw - 2; 1231 } 1232 1233 # do we have to remove small dashes? 1234 my $remove_dashes_shorter_than = 0; 1235 my $check_dashes = sub { 1236 my ( $height, $set_threshold ) = @_; 1237 my $lastval = -1_000_000; 1238 for ( my $i = 1; $i < scalar @{$tval} - 1; $i++) { 1239 next if $$tlen[$i] > $height || $$tlen[$i] < $remove_dashes_shorter_than; 1240 my $val = int( abs( $$tval[$i] - $min) * ( $br - 3) / $range + .5); 1241 $remove_dashes_shorter_than = $set_threshold, last if abs($val - $lastval) < 4; 1242 $lastval = $val; 1243 } 1244 }; 1245 if ( $self->{scheme} == ss::Thermometer || $self->{scheme} == ss::Axis ) { 1246 $check_dashes->(5, 5); 1247 $check_dashes->(12, 12); 1248 } elsif ( $self->{scheme} == ss::Axis ) { 1249 $check_dashes->(3, 3); 1250 } else { 1251 $check_dashes->(12, 12); 1252 } 1253 1254 if ( $v) { 1255 my $bw = ( $size[0] - $sb) / 2; 1256 return if $size[1] <= $kb * ($self-> {readOnly} ? 1 : 0) + 2 * $bh + 2; 1257 1258 $canvas-> translate((( $ta == 1) ? 1 : -1) * ( $bw - $sb - $kb), 0) 1259 if $ta < 3; 1260 $canvas-> rect3d( 1261 $bw, $bh, $bw + $sb - 1, $bh + $br - 1, 1, 1262 @c3d, $cht[1] 1263 ), return unless $range; 1264 1265 my $val = $bh + 1 + abs( $self-> {value} - $min) * ( $br - 3) / $range; 1266 if ( $self-> {ribbonStrip}) { 1267 $canvas-> rect3d( $bw, $bh, $bw + $sb - 1, $bh + $br - 1, 1, @c3d); 1268 $canvas-> color( $cht[0]); 1269 $canvas-> bar( $bw + 1, $bh + 1, $bw + $sb - 2, $val); 1270 $canvas-> color( $cht[1]); 1271 $canvas-> bar( $bw + 1, $val + 1, $bw + $sb - 2, $bh + $br - 2); 1272 } else { 1273 $canvas-> rect3d( $bw, $bh, $bw + $sb - 1, $bh + $br - 1, 1274 1, @c3d, $cht[1]); 1275 $canvas-> color( $clr[0]); 1276 $canvas-> line( $bw + 1, $val, $bw + $sb - 2, $val) 1277 if $self-> {readOnly}; 1278 } 1279 my $i; 1280 my @tr = $self->translate; 1281 my @texts; 1282 for my $glyph_delta ( @glyph_deltas ) { 1283 my ( $color, $delta_x, $delta_y ) = @$glyph_delta; 1284 $canvas-> color( $color ); 1285 $self->translate($tr[0] + $delta_x, $tr[1] + $delta_y); 1286 for ( $i = 0; $i < scalar @{$tval}; $i++) { 1287 my $val = $bh + 1 + abs( $$tval[$i] - $min) * ( $br - 3) / $range; 1288 if ( $$tlen[ $i]) { 1289 next if 1290 defined($remove_dashes_shorter_than) && 1291 $remove_dashes_shorter_than > $$tlen[$i] && 1292 $i != 0 && $i != $#$tval; 1293 $canvas-> line( 1294 $bw + $sb + 3, $val, 1295 $bw + $sb + $$tlen[ $i] + 3, $val 1296 ) if $ta & 2; 1297 $canvas-> line( 1298 $bw - 4, $val, 1299 $bw - 4 - $$tlen[ $i], $val 1300 ) if $ta & 1; 1301 } 1302 push @texts, [ 1303 $$ttxt[ $i], 1304 ( $ta == 2) ? 1305 $bw + $sb + $$tlen[ $i] + 5 : 1306 $bw - $$tlen[ $i] - 5 - $canvas-> get_text_width( $$ttxt[ $i]), 1307 $val - $bh / 2 1308 ] if defined $$ttxt[ $i]; 1309 } 1310 my $size = $size[1] - $bh - 2; 1311 my $fh = $bh + 1; 1312 if ($size < $fh) { 1313 @texts = (); 1314 } elsif ( $size < $fh * 2 || @texts == 1) { 1315 @texts = ($texts[0]); 1316 } elsif ( $size < $fh * 3 || @texts == 2) { 1317 @texts = @texts[0,-1]; 1318 } else { 1319 my @t = ($texts[0]); 1320 $size -= $fh * 1.5; 1321 my $y = $texts[0][2] + $fh; 1322 for my $t ( @texts[1 .. $#texts - 1] ) { 1323 next if $t->[2] < $y; 1324 last if $t->[2] > $size; 1325 push @t, $t; 1326 $y = $t->[2] + $fh; 1327 } 1328 @texts = (@t, $texts[-1]); 1329 } 1330 $canvas->text_shape_out(@$_) for @texts; 1331 } 1332 unless ( $self-> {readOnly}) { 1333 my @jp = map { int( $_ + .5 ) } ( 1334 $bw - 4, $val - $kb / 2, 1335 $bw - 4, $val + $kb / 2, 1336 $bw + $sb + 1, $val + $kb / 2, 1337 $bw + $sb + 1 + $kb/2, $val, 1338 $bw + $sb + 1, $val - $kb / 2, 1339 ); 1340 my $rgn = Prima::Region->new( polygon => \@jp); 1341 $rgn->offset( $canvas->translate ); 1342 $canvas-> region( $rgn ); 1343 $canvas-> new_gradient( 1344 palette => [ $c3d[0], ($self->{prelight} ? $prelight : $clr[1]) ], 1345 poly => [0,0,0.3,0.7,1,1], 1346 vertical => 0, 1347 )-> bar( $jp[0]+2,$jp[1]+2,$jp[6]-2,$jp[3]-2); 1348 $canvas-> color( 0x404040); 1349 $canvas-> polyline([@jp[6..9,0,1]]); 1350 $canvas-> color( $c3d[1]); 1351 $canvas-> polyline([$jp[0]+1,$jp[1]+1,$jp[2]+1,$jp[3]-1,$jp[4],$jp[5]-1,$jp[6]-1,$jp[7]]); 1352 $canvas-> line($jp[0]+2, $jp[7]-1, $jp[6]-2, $jp[7]-1); 1353 $canvas-> color( $c3d[0]); 1354 $canvas-> polyline([$jp[6]-1,@jp[7,8],$jp[9]+1,$jp[0],$jp[1]+1,@jp[2..7]]); 1355 $canvas-> line($jp[0]+2, $jp[7]+1, $jp[6]-1, $jp[7]+1); 1356 } 1357 } else { 1358 my $bh = ( $size[1] - $sb) / 2; 1359 my $fh = $canvas-> font-> height; 1360 return if $size[0] <= $kb * ($self-> {readOnly} ? 1 : 0) + 2 * $bw + 2; 1361 1362 $canvas-> translate( 0, (( $ta == 1) ? -1 : 1) * ( $bh - $sb - $kb)) 1363 if $ta < 3; 1364 $canvas-> rect3d( $bw, $bh, $bw + $br - 1, $bh + $sb - 1, 1, @c3d, $cht[1]), return 1365 unless $range; 1366 my $val = $bw + 1 + abs( $self-> {value} - $min) * ( $br - 3) / $range; 1367 1368 if ( $self-> {ribbonStrip}) { 1369 $canvas-> rect3d( $bw, $bh, $bw + $br - 1, $bh + $sb - 1, 1, @c3d); 1370 $canvas-> color( $cht[0]); 1371 $canvas-> bar( $bw+1, $bh+1, $val, $bh + $sb - 2); 1372 $canvas-> color( $cht[1]); 1373 $canvas-> bar( $val+1, $bh+1, $bw + $br - 2, $bh + $sb - 2); 1374 } else { 1375 $canvas-> rect3d( $bw, $bh, $bw + $br - 1, $bh + $sb - 1, 1, @c3d, $cht[1]); 1376 $canvas-> color( $clr[0]); 1377 $canvas-> line( $val, $bh+1, $val, $bh + $sb - 2) if $self-> {readOnly}; 1378 } 1379 my $i; 1380 1381 my @texts; 1382 my @tr = $self->translate; 1383 for my $glyph_delta ( @glyph_deltas ) { 1384 my ( $color, $delta_x, $delta_y ) = @$glyph_delta; 1385 $canvas-> color( $color ); 1386 $self->translate($tr[0] + $delta_x, $tr[1] + $delta_y); 1387 for ( $i = 0; $i < scalar @{$tval}; $i++) { 1388 my $val = int( 1 + $bw + abs( $$tval[$i] - $min) * ( $br - 3) / $range + .5); 1389 if ( $$tlen[ $i]) { 1390 next if 1391 defined($remove_dashes_shorter_than) && 1392 $remove_dashes_shorter_than > $$tlen[$i] && 1393 $i != 0 && $i != $#$tval; 1394 $canvas-> line( $val, $bh + $sb + 3, $val, $bh + $sb + $$tlen[ $i] + 3) 1395 if $ta & 1; 1396 $canvas-> line( $val, $bh - 4, $val, $bh - 4 - $$tlen[ $i]) 1397 if $ta & 2; 1398 } 1399 1400 next unless defined $$ttxt[ $i]; 1401 my $tw = int( $canvas-> get_text_width( $$ttxt[ $i]) / 2 + .5); 1402 my $x = $val - $tw; 1403 next if $x >= $size[0] or $val + $tw < 0; 1404 push @texts, [ 1405 $$ttxt[$i], $val, $tw, 1406 ( $ta == 2) ? $bh - $$tlen[ $i] - 5 - $fh : $bh + $sb + $$tlen[ $i] + 5, 1407 $size[0] 1408 ]; 1409 } 1410 1411 1412 if ( @texts) { 1413 # see that leftmost val fits 1414 if ( $texts[0]->[1] - $texts[0]->[2] < 0) { 1415 $texts[0]->[1] = $texts[0]->[2]; 1416 shift @texts 1417 if $texts[0]->[1] + $texts[0]->[2] > $size[0]; 1418 goto NO_LABELS unless @texts; 1419 } 1420 1421 # see that rightmost text fits 1422 my ( $rightmost_val, $rightmost_label_width) = ( 1423 $texts[-1]->[1], $texts[-1]->[2]); 1424 $rightmost_val = $size[0] - 1 - $rightmost_label_width 1425 if $rightmost_val > $size[0] - 1 - $rightmost_label_width; 1426 if ( 1 < @texts and $rightmost_val < 0) { 1427 # skip it 1428 pop @texts; 1429 goto NO_LABELS unless @texts; 1430 } else { 1431 $texts[-1]->[1] = $rightmost_val; 1432 my $lv = 2 * $rightmost_label_width + $mw; 1433 $$_[-1] -= $lv for @texts[0..$#texts-1]; 1434 $texts[-1][-1] += $mw; 1435 } 1436 1437 # draw labels 1438 my $lastx = 0; 1439 for ( @texts) { 1440 my ( $text, $val, $half_width, $y, $xlim) = @$_; 1441 my $x = $val - $half_width; 1442 next if $x < $lastx or $x < 0 or $val + $half_width >= $xlim; 1443 $lastx = $val + $half_width + $mw; 1444 $canvas-> text_shape_out( $text, $x, $y); 1445 } 1446 } 1447 NO_LABELS: 1448 } 1449 1450 unless ( $self-> {readOnly}) { 1451 my @jp = map { int($_ + .5) } ( 1452 $val - $kb / 2, $bh - 2, 1453 $val - $kb / 2, $bh + $sb + 3, 1454 $val + $kb / 2, $bh + $sb + 3, 1455 $val + $kb / 2, $bh - 2, 1456 $val, $bh - $kb / 2 - 2, 1457 ); 1458 my $rgn = Prima::Region->new( polygon => \@jp); 1459 $rgn->offset( $canvas->translate ); 1460 $canvas-> region( $rgn ); 1461 $canvas-> new_gradient( 1462 palette => [ ($self->{prelight} ? $prelight : $clr[1]), $c3d[0] ], 1463 poly => [0,0,0.7,0.3,1,1], 1464 vertical => 1, 1465 )-> bar( $jp[0]+2,$jp[9],$jp[4]-2,$jp[3]); 1466 $canvas-> color( 0x404040 ); 1467 $canvas-> polyline([@jp[4..9]]); 1468 $canvas-> color( $c3d[0]); 1469 $canvas-> polyline([ 1470 @jp[8,9,0..3],$jp[4]-1,$jp[5], 1471 $jp[6]-1,$jp[7],$jp[8],$jp[9]+1 1472 ]); 1473 $canvas-> line($jp[8]-1,$jp[3]-2,$jp[8]-1,$jp[9]) if $kb > 10; 1474 $canvas-> color( $c3d[1]); 1475 $canvas-> polyline([$jp[8],$jp[9]+1,$jp[0]+1,$jp[1],$jp[2]+1,$jp[3]-1,$jp[4]-2,$jp[5]-1]); 1476 $canvas-> line($jp[8]+1,$jp[3]-2,$jp[8]+1,$jp[9]) if $kb > 10; 1477 } 1478 } 1479} 1480 1481sub on_fontchanged 1482{ 1483 my $self = shift; 1484 $self->update_geom_sizes; 1485 $self->repaint; 1486} 1487 1488sub update_geom_sizes 1489{ 1490 my $self = shift; 1491 my $maxtlen = 0; 1492 for ( @{ $self->{tickLen}}) { 1493 $maxtlen = $_ if $maxtlen < $_; 1494 } 1495 $maxtlen *= 2 if $self->tickAlign == tka::Dual; 1496 if ( $self->vertical ) { 1497 return unless $self->autoWidth; 1498 my $maxtwid = 0; 1499 $self->begin_paint_info; 1500 for ( grep { defined } @{ $self->{tickTxt}}) { 1501 my $w = $self->get_text_width($_); 1502 $maxtwid = $w if $maxtwid < $w; 1503 } 1504 $self->end_paint_info; 1505 my $x = $maxtlen + $maxtwid * 2 + $self->shaftBreadth + $self->borderWidth + 5 + $self->knobBreadth; 1506 $self->geomWidth($x); 1507 } else { 1508 return unless $self->autoHeight; 1509 my $y = $maxtlen + $self->font->height * 2 + $self->shaftBreadth + $self->borderWidth + 5 + $self->knobBreadth; 1510 $self->geomHeight($y); 1511 } 1512} 1513 1514sub pos2info 1515{ 1516 my ( $self, $x, $y) = @_; 1517 my @size = $self-> size; 1518 return if $self-> {max} == $self-> {min}; 1519 if ( $self-> {vertical}) { 1520 my $bh = $self-> font-> height; 1521 my $val = 1522 $bh + 1523 1 + 1524 abs( $self-> {value} - $self-> {min}) * 1525 ( $size[1] - 2 * $bh - 5) / 1526 ( abs($self-> {max} - $self-> {min}) || 1); 1527 my $ret1 = 1528 $self-> {min} + 1529 ( $y - $bh - 1) * 1530 abs($self-> {max} - $self-> {min}) / 1531 (( $size[1] - 2 * $bh - 5) || 1); 1532 1533 if ( $y < $val - $self->knobBreadth / 2 or $y >= $val + $self->knobBreadth / 2) { 1534 return 0, $ret1; 1535 } else { 1536 return 1, $ret1, $y - $val; 1537 } 1538 } else { 1539 my $bw = $self-> font-> width + $self->{borderWidth}; 1540 my $val = 1541 $bw + 1542 1 + 1543 abs( $self-> {value} - $self-> {min}) * 1544 ( $size[0] - 2 * $bw - 5) / 1545 (abs($self-> {max} - $self-> {min}) || 1); 1546 my $ret1 = 1547 $self-> {min} + 1548 ( $x - $bw - 1) * 1549 abs($self-> {max} - $self-> {min}) / 1550 (( $size[0] - 2 * $bw - 5) || 1); 1551 1552 if ( $x < $val - $self->knobBreadth / 2 or $x >= $val + $self->knobBreadth / 2) { 1553 return 0, $ret1; 1554 } else { 1555 return 1, $ret1, $x - $val; 1556 } 1557 } 1558} 1559 1560sub on_mousedown 1561{ 1562 my ( $self, $btn, $mod, $x, $y) = @_; 1563 return if $self-> {readOnly}; 1564 return if $self-> {mouseTransaction}; 1565 return if $btn != mb::Left; 1566 my ($info, $pos, $ap) = $self-> pos2info( $x, $y); 1567 return unless defined $info; 1568 delete $self->{prelight}; 1569 if ( $info == 0) { 1570 $self-> value( $pos); 1571 return; 1572 } 1573 $self-> {aperture} = $ap; 1574 $self-> {mouseTransaction} = 1; 1575 $self-> capture(1); 1576 $self-> clear_event; 1577} 1578 1579sub on_mouseup 1580{ 1581 my ( $self, $btn, $mod, $x, $y) = @_; 1582 return if $btn != mb::Left; 1583 return unless $self-> {mouseTransaction}; 1584 $self-> {mouseTransaction} = undef; 1585 $self-> capture(0); 1586 $self-> notify( 'Change') unless $self-> {autoTrack}; 1587} 1588 1589sub on_mousemove 1590{ 1591 my ( $self, $mod, $x, $y) = @_; 1592 unless ($self-> {mouseTransaction}) { 1593 if ( $self-> enabled ) { 1594 my ($prelight) = $self-> pos2info( $x, $y); 1595 $prelight = (!defined($prelight) || ($prelight != 1)) ? undef : 1; 1596 if (($prelight // 0) != ($self->{prelight} // 0)) { 1597 $self->{prelight} = $prelight; 1598 $self->repaint; 1599 } 1600 } 1601 return; 1602 } 1603 $self-> {vertical} ? $y : $x -= $self-> {aperture}; 1604 my ( $info, $pos) = $self-> pos2info( $x, $y); 1605 return unless defined $info; 1606 my $ov = $self-> {value}; 1607 $self-> {suppressNotify} = 1 unless $self-> {autoTrack}; 1608 $self-> value( $pos); 1609 $self-> {suppressNotify} = 0; 1610 $self-> notify(q(Track)) if !$self-> {autoTrack} && $ov != $self-> {value}; 1611} 1612 1613sub on_mouseleave 1614{ 1615 my $self = shift; 1616 $self-> repaint if defined( delete $self->{prelight} ); 1617} 1618 1619sub on_keydown 1620{ 1621 my ( $self, $code, $key, $mod) = @_; 1622 return if $self-> {readOnly}; 1623 if ( $key == kb::Home || $key == kb::PgUp) { 1624 $self-> value( $self-> {vertical} ? $self-> {max} : $self-> {min}); 1625 $self-> clear_event; 1626 return; 1627 } 1628 if ( $key == kb::End || $key == kb::PgDn) { 1629 $self-> value( $self-> {vertical} ? $self-> {min} : $self-> {max}); 1630 $self-> clear_event; 1631 return; 1632 } 1633 if ( $key == kb::Left || $key == kb::Right || $key == kb::Up || $key == kb::Down) { 1634 my $s = $self-> {step}; 1635 $self-> clear_event; 1636 $self-> set_next_value(( $key == kb::Left || $key == kb::Down) ? -$s : $s); 1637 } 1638} 1639 1640sub set_vertical 1641{ 1642 $_[0]-> {vertical} = $_[1]; 1643 $_[0]-> update_geom_sizes; 1644 $_[0]-> repaint; 1645} 1646 1647sub set_tick_align 1648{ 1649 my ( $self, $ta) = @_; 1650 $ta = tka::Normal if $ta != tka::Alternative and $ta != tka::Dual; 1651 return if $ta == $self-> {tickAlign}; 1652 $self-> {tickAlign} = $ta; 1653 $self-> update_geom_sizes; 1654 $self-> repaint; 1655} 1656 1657sub set_ribbon_strip 1658{ 1659 $_[0]-> {ribbonStrip} = $_[1]; 1660 $_[0]-> repaint; 1661} 1662 1663sub set_shaft_breadth 1664{ 1665 my ( $self, $sb) = @_; 1666 $sb = 0 if $sb < 0; 1667 return if $sb == $self-> {shaftBreadth}; 1668 $self-> {shaftBreadth} = $sb; 1669 $self-> update_geom_sizes; 1670 $self-> repaint; 1671} 1672 1673sub set_bound 1674{ 1675 my ( $self, $val, $bound) = @_; 1676 $self-> {$bound} = $val; 1677 $self-> scheme($self-> {scheme}) if defined $self-> {scheme}; 1678 $self-> repaint; 1679} 1680 1681sub value 1682{ 1683 if ($#_) { 1684 my ( $self, $value) = @_; 1685 my ( $min, $max) = ( $self-> {min}, $self-> {max}); 1686 my $old = $self-> {value}; 1687 if ( $self-> {snap}) { 1688 my ( $minDist, $thatVal, $i) = ( abs( $min - $max)); 1689 my $tval = $self-> {tickVal}; 1690 for ( $i = 0; $i < scalar @{$tval}; $i++) { 1691 my $j = $$tval[ $i]; 1692 $minDist = abs(($thatVal = $j) - $value) 1693 if abs( $j - $value) < $minDist; 1694 } 1695 $value = $thatVal if defined $thatVal; 1696 } elsif ( $self-> {step} != 0 ) { 1697 $value = int ( $value / $self-> {step} ) * $self-> {step}; 1698 } 1699 $value = $min if $value < $min; 1700 $value = $max if $value > $max; 1701 return if $old == $value; 1702 $self-> {value} = $value; 1703 my @size = $self-> size; 1704 my $sb = $self-> {shaftBreadth}; 1705 if ( $self-> {vertical}) { 1706 $sb = $size[0] / 6 unless $sb; 1707 $sb = 2 unless $sb; 1708 my $bh = $self-> font-> height; 1709 my $bw = ( $size[0] - $sb) / 2; 1710 my $v1 = $bh + 1 + abs( $self-> {value} - $self-> {min}) * 1711 ( $size[1] - 2 * $bh - 5) / (abs($self-> {max} - $self-> {min})||1); 1712 my $v2 = $bh + 1 + abs( $old - $self-> {min}) * 1713 ( $size[1] - 2 * $bh - 5) / (abs($self-> {max} - $self-> {min})||1); 1714 ( $v2, $v1) = ( $v1, $v2) if $v1 > $v2; 1715 my $kb = $self-> knobBreadth / 2; 1716 my $xd = 0; 1717 $xd = (( $self-> {tickAlign} == tka::Normal) ? 1 : -1) * 1718 ( $bw - $sb - $self->knobBreadth) if $self-> {tickAlign} != tka::Dual; 1719 $self-> invalidate_rect( 1720 map { int($_ + .5) } 1721 $bw - 4 + $xd, $v1 - $kb, $bw + $sb * 2 + 3 + $xd, $v2 + $kb + 1 1722 ); 1723 } else { 1724 $sb = $size[1] / 6 unless $sb; 1725 $sb = 2 unless $sb; 1726 my $bw = $self-> font-> width + $self-> {borderWidth}; 1727 my $bh = ( $size[1] - $sb) / 2; 1728 my $v1 = $bw + 1 + abs( $self-> {value} - $self-> {min}) * 1729 ( $size[0] - 2 * $bw - 5) / (abs($self-> {max} - $self-> {min})||1); 1730 my $v2 = $bw + 1 + abs( $old - $self-> {min}) * 1731 ( $size[0] - 2 * $bw - 5) / (abs($self-> {max} - $self-> {min})||1); 1732 ( $v2, $v1) = ( $v1, $v2) if $v1 > $v2; 1733 my $kb = $self-> knobBreadth / 2; 1734 my $yd = 0; 1735 $yd = (( $self-> {tickAlign} == tka::Normal) ? -1 : 1) * 1736 ( $bh - $sb - $self->knobBreadth) if $self-> {tickAlign} != tka::Dual; 1737 $self-> invalidate_rect( 1738 map { int($_ + .5) } 1739 $v1 - $kb, $bh - $kb - 2 + $yd, 1740 $v2 + $kb + 1, $bh + $sb + 5 + $yd 1741 ); 1742 } 1743 $self-> notify(q(Change)) unless $self-> {suppressNotify}; 1744 } else { 1745 return $_[0]-> {value}; 1746 } 1747} 1748sub vertical {($#_)?$_[0]-> set_vertical ($_[1]):return $_[0]-> {vertical};} 1749sub tickAlign {($#_)?$_[0]-> set_tick_align ($_[1]):return $_[0]-> {tickAlign};} 1750sub ribbonStrip {($#_)?$_[0]-> set_ribbon_strip($_[1]):return $_[0]-> {ribbonStrip};} 1751sub shaftBreadth{($#_)?$_[0]-> set_shaft_breadth($_[1]):return $_[0]-> {shaftBreadth};} 1752 1753sub knobBreadth 1754{ 1755 return $_[0]->{knobBreadth} unless $#_; 1756 my ( $self, $kb) = @_; 1757 $kb = 4 if $kb < 4; 1758 $kb &= ~1; # must divide by 2 1759 return if $kb == $self-> {knobBreadth}; 1760 $self-> {knobBreadth} = $kb; 1761 $self-> update_geom_sizes; 1762 $self-> repaint; 1763} 1764 1765sub borderWidth 1766{ 1767 return $_[0]-> {borderWidth} unless $#_; 1768 my ( $self, $bw) = @_; 1769 $bw = 0 if $bw < 0; 1770 $self-> {borderWidth} = $bw; 1771 $self-> update_geom_sizes; 1772 $self-> repaint; 1773} 1774 1775package Prima::CircularSlider; 1776use vars qw(@ISA); 1777@ISA = qw(Prima::AbstractSlider Prima::MouseScroller); 1778 1779{ 1780my %RNT = ( 1781 %{Prima::AbstractSlider-> notification_types()}, 1782 Stringify => nt::Action, 1783); 1784sub notification_types { return \%RNT; } 1785} 1786 1787sub profile_default 1788{ 1789 return { 1790 %{$_[ 0]-> SUPER::profile_default}, 1791 buttons => 1, 1792 stdPointer => 0, 1793 buttonWidth => 10, 1794 } 1795} 1796 1797sub profile_check_in 1798{ 1799 my ( $self, $p, $default) = @_; 1800 1801 my $sc = $::application->uiScaling; 1802 $p->{$_} = ( exists($p->{$_}) ? $p->{$_} : $default->{$_} ) * $sc for qw(buttonWidth); 1803 $self-> SUPER::profile_check_in( $p, $default); 1804} 1805 1806sub init 1807{ 1808 my $self = shift; 1809 $self-> {$_}=0 for qw( buttons pressState circX circY br butt1X butt1Y butt2X buttonWidth); 1810 $self-> {string} = ''; 1811 my %profile = $self-> SUPER::init( @_); 1812 $self-> $_($profile{$_}) for qw( buttons stdPointer buttonWidth); 1813 $self-> reset; 1814 return %profile; 1815} 1816 1817sub setup 1818{ 1819 $_[0]-> SUPER::setup; 1820 $_[0]-> notify(q(Stringify), $_[0]-> {value}, \$_[0]-> {string}); 1821 $_[0]-> repaint; 1822} 1823 1824sub set_text 1825{ 1826 my ( $self, $caption) = @_; 1827 $self-> SUPER::set_text( $caption ); 1828 $self-> {accel} = lc($1) if $caption =~ /~([a-z0-9])/i; 1829 $self-> repaint; 1830} 1831 1832sub reset 1833{ 1834 my $self = $_[0]; 1835 my @size = $self-> size; 1836 my $fh = $self-> font-> height; 1837 my $bw = $self->buttonWidth; 1838 my $bw_fh = ( $bw > $fh ) ? $bw : $fh; 1839 my $br = ($size[0] > ( $size[1] - $bw_fh)) ? ( $size[1] - $bw_fh) : $size[0]; 1840 $self->begin_paint_info; 1841 1842 # first calculate a minimum viable dial radius 1843 my $tx1 = $self->get_text_width( $self-> min, 1 ); 1844 my $tx2 = $self->get_text_width( $self-> max, 1 ); 1845 $tx1 = $tx2 if $tx1 < $tx2; 1846 $tx1 = $fh if $tx1 < $fh; 1847 $tx1 /= 2; 1848 $tx1 += 4 + 10; 1849 my $min_viable_rad = $tx1; 1850 my $rad = $self-> {radius} = ($tx1 < ($br * 0.5)) ? $tx1 : ($br * 0.5); 1851 1852 # circle center 1853 $self-> {br} = $br; 1854 $self-> {circX} = int($size[0]/2 + .5); 1855 $self-> {circY} = int(($size[1] + $bw_fh) / 2 + .5); 1856 1857 my $i; 1858 my ( $tval, $tlen, $ttxt) = ( $self-> {tickVal}, $self-> {tickLen}, $self-> {tickTxt}); 1859 my @ext = (0,0,0,0); 1860 for ( $i = 0; $i < scalar @{$tval}; $i++) { 1861 my $r = $rad + 3 + $$tlen[ $i]; 1862 my ( $cos, $sin) = $self-> offset2data( $$tval[$i]); 1863 if ( $$tlen[$i]) { 1864 my @outer = ($r * $cos, $r * $sin); 1865 $ext[0] = $outer[0] if $ext[0] > $outer[0]; 1866 $ext[1] = $outer[1] if $ext[1] > $outer[1]; 1867 $ext[2] = $outer[0] if $ext[2] < $outer[0]; 1868 $ext[3] = $outer[1] if $ext[3] < $outer[1]; 1869 } 1870 $r += 3; 1871 if ( defined $$ttxt[ $i]) { 1872 my $w = $self-> get_text_width( $$ttxt[ $i], 1); 1873 my $y = $r * $sin - $fh / 2 * ( 1 - $sin); 1874 my $x = $r * $cos - ( 1 - $cos) * $w / 2; 1875 my $r = $x + $w; 1876 my $t = $y + $fh; 1877 $ext[0] = $x if $ext[0] > $x; 1878 $ext[1] = $y if $ext[1] > $y; 1879 $ext[2] = $r if $ext[2] < $r; 1880 $ext[3] = $t if $ext[3] < $t; 1881 } 1882 } 1883 $ext[$_] = int($ext[$_] - .5) for 0,1; 1884 $ext[$_] = int($ext[$_] + .5) for 2,3; 1885 1886 my @sz = ( $ext[2] - $ext[0], $ext[3] - $ext[1] ); 1887 my @d = ( 1888 $self->{circX} + $ext[0], 1889 $self->{circY} + $ext[1], 1890 $size[0] - $self->{circX} - $ext[2], 1891 $size[1] - $self->{circY} - $ext[3], 1892 ); 1893 $self-> {show_scale} = ! grep { $_ < 0 } @d; 1894 1895GROW_CIRCLE: 1896 @ext = (0,0,0,0) unless $self->{show_scale}; 1897 1898 # can grow the circle? 1899 $ext[0] = -$rad if $ext[0] > -$rad; 1900 $ext[1] = -$rad if $ext[1] > -$rad; 1901 $ext[2] = $rad if $ext[2] < $rad; 1902 $ext[3] = $rad if $ext[3] < $rad; 1903 $ext[$_] -= 2 for 0,1; 1904 $ext[$_] += 2 for 2,3; 1905 @sz = ( $ext[2] - $ext[0], $ext[3] - $ext[1] ); 1906 1907 if ( $sz[0] < $size[0] && $sz[1] < $size[1] - $bw - $fh) { 1908 my @d = ( 1909 $self->{circX} + $ext[0], 1910 $self->{circY} + $ext[1], 1911 $size[0] - $self->{circX} - $ext[2], 1912 $size[1] - $self->{circY} - $ext[3], 1913 ); 1914 my $min = $d[0]; 1915 for ( @d ) { 1916 $min = $_ if $min > $_; 1917 } 1918 $min--; 1919 if ( $min > 0 ) { 1920 $self->{radius} += $min; 1921 $ext[$_] -= $min for 0,1; 1922 $ext[$_] += $min for 2,3; 1923 } 1924 } 1925 1926 # buttons X location 1927 $self-> {butt1X} = int( $size[0] / 2 - $self->{radius} - $bw / 2 + .5); 1928 $self-> {butt2X} = int( $size[0] / 2 + $self->{radius} - $bw / 2 + .5); 1929 if ($self->{butt1X} < 1) { 1930 $self->{butt2X} += $self->{butt1X} - 2; 1931 $self->{butt1X} = 1; 1932 } 1933 if ( $self->{butt1X} + $bw + 1 > $self->{butt2X} ) { 1934 my $d = $bw - $self->{butt2X} + $self->{butt1X}; 1935 $self->{butt1X} -= $d / 2 + 1; 1936 $self->{butt2X} += $d / 2 + 1; 1937 } 1938 1939 # Y location for title and buttons 1940 my $lowest = $self->{circY} + $ext[1]; 1941 my $fd = $self->font->descent; 1942 $self-> {textY} = ($lowest > $fh + 2) ? ($lowest - $fh) / 2 + 1 : 2; 1943 $self-> {textY} += $fd; 1944 $self-> {butt1Y} = ($lowest > $bw + 2) ? ($lowest - $bw) / 2 + 1 : 2; 1945 $self-> {show_text} = 1; 1946 my $title_width = $self->get_text_width($self->text, 1); 1947 if ( $title_width > $size[0] ) { 1948 $self->{show_text} = 0; 1949 } elsif ( $title_width > $self->{butt2X} - $self->{butt1X} - $bw - 2 ) { 1950 if ( $title_width + 2 + $bw * 2 < $size[0] ) { 1951 # move buttons aparts by x to accomodate title 1952 $self->{butt1X} = ($size[0] - $title_width) / 2 - $bw - 1; 1953 $self->{butt2X} = ($size[0] + $title_width) / 2 + 1; 1954 } elsif ( $lowest > $fh + $bw + 2 ) { 1955 # draw buttons and title on separate lines 1956 my $d = ($bw_fh + 1 ) / 2; 1957 $self->{textY} -= $d; 1958 $self->{butt1Y} += $d; 1959 } else { 1960 $self->{show_text} = 0; 1961 } 1962 } 1963 1964 $self->{show_dial} = $self->{radius} >= $min_viable_rad; 1965 if ( !$self->{show_dial} && $self->{show_scale} ) { 1966 # try to grow the circle again 1967 $self->{show_scale} = 0; 1968 goto GROW_CIRCLE; 1969 } 1970 1971 $self->end_paint_info; 1972 1973 # hints 1974 if ( 1975 $self->{show_text} && 1976 !$self->{show_scale} && 1977 $self->{show_dial} && 1978 $self->{textY} + $fh > $self->{circY} - $self->{radius} 1979 ) { 1980 # if text is over the expanded dial ( when @ext is empty ), move it or hide 1981 $self->{textY} = $fd; 1982 if ( $self->{textY} + $fh > $self->{circY} - $self->{radius}) { 1983 $self->{show_text} = 0; 1984 } 1985 } 1986 if ( !$self->{show_text} && $self->{butt1Y} + $bw > $size[1] ) { 1987 # no text, don't need to fit buttons together with the text 1988 $self->{butt1Y} = ( $size[1] - $bw ) / 2; 1989 } 1990 if ( !$self->{show_dial} ) { 1991 # do not obscure the value as much as possible 1992 $self->{show_text} = 0 1993 if $self->{show_text} && $fh + $bw + $fh > $size[1]; 1994 $self->{butt1Y} = $self->{textY} = 0; 1995 } 1996 $self->{valueY} = $self->{circY} - $fh / 2; 1997 if ($self->{valueY} + $fh - 2 > $size[1]) { 1998 $self->{valueY} = 0; 1999 if ( $self->{butt1X} + $bw + 2 >= $self->{butt2X} ) { 2000 $self->{butt1X} = 1; 2001 $self->{butt2X} = $size[0] - $bw - 1; 2002 } 2003 } 2004} 2005 2006sub offset2pt 2007{ 2008 my ( $self, $width, $height, $value, $radius) = @_; 2009 my $a = 225 * 3.14159 / 180 - ( 270 * 3.14159 / 180) * ( $value - $self-> {min}) / 2010 (abs( $self-> {min} - $self-> {max})||1); 2011 return $width + $radius * cos($a), $height + $radius * sin($a); 2012} 2013 2014sub offset2data 2015{ 2016 my ( $self, $value) = @_; 2017 my $a = 225 * 3.14159 / 180 - ( 270 * 3.14159 / 180) * abs( $value - $self-> {min})/ 2018 (abs( $self-> {min} - $self-> {max})||1); 2019 return cos($a), sin($a); 2020} 2021 2022sub on_paint 2023{ 2024 my ( $self, $canvas) = @_; 2025 my @clr; 2026 my $prelight; 2027 if ( $self->enabled ) { 2028 @clr = ( $self-> color, $self-> backColor); 2029 $prelight = $self->prelight_color($clr[1]) if $self->{prelight}; 2030 } else { 2031 @clr = ( $self-> disabledColor, $self-> disabledBackColor); 2032 } 2033 my @c3d = ( $self-> dark3DColor, $self-> light3DColor); 2034 my @cht = ( $self-> hiliteColor, $self-> hiliteBackColor); 2035 my @size = $canvas-> size; 2036 my ( $range, $min, $tval, $tlen, $ttxt, $bw) = 2037 ( abs($self-> {max} - $self-> {min}), $self-> {min}, $self-> {tickVal}, 2038 $self-> {tickLen}, $self-> {tickTxt}, $self->{buttonWidth} ); 2039 2040 if ( defined $self-> {singlePaint}) { 2041 my @clip1 = @{$self-> {expectedClip}}; 2042 my @clip2 = $self-> clipRect; 2043 my $i; 2044 for ( $i = 0; $i < 4; $i++) { 2045 $self-> {singlePaint} = undef, last if $clip1[$i] != $clip2[$i]; 2046 } 2047 } 2048 2049 $canvas-> color( $clr[1]); 2050 $canvas-> bar( 0, 0, @size) if !$self-> transparent && !defined $self-> {singlePaint}; 2051 my $fh = $canvas-> font-> height; 2052 my $br = $self-> {br}; 2053 my $rad = $self-> {radius}; 2054 my @cpt = ( $self-> {circX}, $self-> {circY}, $rad*2+1, $rad*2+1); 2055 2056 goto AFTER_DIAL unless $self->{show_dial}; 2057 if ( defined $self-> {singlePaint}) { 2058 my $drad = 5; 2059 my $radx = $rad; 2060 for my $lw ( 2..4) { 2061 $radx -= 100; 2062 last if $radx < 0; 2063 $drad++; 2064 } 2065 $canvas-> color( $prelight ) if $self->{prelight}; 2066 $canvas-> fill_ellipse( @cpt[0..1], $rad*2-$drad, $rad*2-$drad); 2067 $canvas-> color( $clr[0]); 2068 } else { 2069 if ($self->{prelight}) { 2070 $canvas-> color( $prelight ); 2071 $canvas-> fill_ellipse( @cpt[0..1], $rad*2-5, $rad*2-5); 2072 } 2073 2074 my $radx = $rad; 2075 my $da = 0; 2076 my $dp = 2; 2077 $canvas-> lineWidth(2); 2078 for my $lw (2..4) { 2079 $canvas-> color( $c3d[1]); 2080 $canvas-> arc( @cpt[0..1], $cpt[2]-$dp, $cpt[3]-$dp, 65 + $da, 235 - $da); 2081 $canvas-> color( $c3d[0]); 2082 $canvas-> arc( @cpt[0..1], $cpt[2]-$dp, $cpt[3]-$dp, 255 + $da, 405 - $da); 2083 $radx -= 100; 2084 $da += 20; 2085 $dp++; 2086 last if $radx < 0; 2087 } 2088 $canvas-> lineWidth(0); 2089 $canvas-> color( $clr[0]); 2090 $canvas-> ellipse( @cpt); 2091 } 2092 2093 if ( $self-> {stdPointer}) { 2094 my $dev = $range * 0.03; 2095 my @j = ( 2096 $self-> offset2pt( @cpt[0,1], $self-> {value}, $rad * 0.8), 2097 $self-> offset2pt( @cpt[0,1], $self-> {value} + $dev, $rad * 0.6), 2098 $self-> offset2pt( @cpt[0,1], $self-> {value} - $dev, $rad * 0.6), 2099 ); 2100 $self-> fillpoly( \@j); 2101 } else { 2102 my @cxt = ( $self-> offset2pt( @cpt[0,1], $self-> {value}, $rad - 10), 4, 4); 2103 my $knob = $::application->uiScaling * 3; 2104 $canvas-> lineWidth(2); 2105 $canvas-> color( $c3d[0]); 2106 $canvas-> arc( @cxt[0..1], $knob, $knob, 65, 235); 2107 $canvas-> color( $c3d[1]); 2108 $canvas-> arc( @cxt[0..1], $knob, $knob, 255, 405); 2109 $canvas-> lineWidth(0); 2110 } 2111AFTER_DIAL: 2112 $canvas-> color( $clr[0]); 2113 2114 if ( $self-> {show_scale} && !defined $self-> {singlePaint}) { 2115 my $i; 2116 for ( $i = 0; $i < scalar @{$tval}; $i++) { 2117 my $r = $rad + 3 + $$tlen[ $i]; 2118 my ( $cos, $sin) = $self-> offset2data( $$tval[$i]); 2119 $canvas-> line( $self-> offset2pt( @cpt[0,1], $$tval[$i], $rad + 3), 2120 $cpt[0] + $r * $cos, $cpt[1] + $r * $sin 2121 ) if $$tlen[ $i]; 2122 $r += 3; 2123 if ( defined $$ttxt[ $i]) { 2124 my $y = $cpt[1] + $r * $sin - $fh / 2 * ( 1 - $sin); 2125 my $x = $cpt[0] + $r * $cos - 2126 ( 1 - $cos) * 2127 $canvas-> get_text_width( $$ttxt[ $i], 1) / 2; 2128 $canvas-> text_shape_out( $$ttxt[ $i], $x, $y); 2129 } 2130 } 2131 } 2132 2133 my $ttw = $canvas-> get_text_width( $self-> {string}, 1); 2134 $canvas-> text_shape_out( $self-> {string}, ( $size[0] - $ttw) / 2, $self->{valueY}); 2135 return if defined $self-> {singlePaint}; 2136 2137 my $text = $self->text; 2138 $text =~ s/\~//; 2139 $ttw = $canvas-> get_text_width( $text, 1); 2140 $canvas-> draw_text( $self->text, 2141 ( $size[0] - $ttw) / 2, $self->{textY}, 2142 ( $size[0] + $ttw) / 2, $self->{textY} + $fh, 2143 dt::DrawMnemonic|dt::NoWordWrap|dt::Default) 2144 if $self->{show_text}; 2145 2146 if ( $self-> {buttons}) { 2147 my $s = $self-> {pressState}; 2148 my @cbd = reverse @c3d; 2149 my $at = 0; 2150 $at = 1, @cbd = reverse @cbd if $s & 1; 2151 2152 $canvas-> rect3d( 2153 $self-> { butt1X}, $self-> { butt1Y}, $self-> { butt1X} + $bw, 2154 $self-> { butt1Y} + $bw, 1, @cbd, $clr[1] 2155 ); 2156 $canvas-> line( 2157 $self-> { butt1X} + 2 + $at, $self-> { butt1Y} + $bw / 2 - $at, 2158 $self-> { butt1X} - 2 + + $bw + $at, $self-> {butt1Y} + $bw / 2 - $at 2159 ); 2160 2161 @cbd = reverse @c3d; $at = 0; 2162 $at = 1, @cbd = reverse @cbd if $s & 2; 2163 $canvas-> rect3d( 2164 $self-> { butt2X}, $self-> { butt1Y}, $self-> { butt2X} + $bw, 2165 $self-> { butt1Y} + $bw, 1, @cbd, $clr[1] 2166 ); 2167 $canvas-> line( 2168 $self-> { butt2X} + 2 + $at, $self-> { butt1Y} + $bw / 2 - $at, 2169 $self-> { butt2X} - 2 + + $bw + $at, $self-> {butt1Y} + $bw / 2 - $at 2170 ); 2171 $canvas-> line( 2172 $self-> { butt2X} + $bw / 2 + $at, $self-> { butt1Y} + 2 - $at, 2173 $self-> { butt2X} + $bw / 2 + $at, $self-> { butt1Y} - 2 - $at + $bw 2174 ); 2175 } 2176 2177 $canvas-> rect_focus( 2178 ( $size[0] - $ttw) / 2 - 1, $self->{textY} - 1, 2179 ( $size[0] + $ttw) / 2 + 1, $self->{textY} + $fh + 1 2180 ) if $self->{show_text} && $self-> focused && ( length( $self-> text) > 0); 2181} 2182 2183sub on_keydown 2184{ 2185 my ( $self, $code, $key, $mod) = @_; 2186 return if $self-> {readOnly}; 2187 if ( $key == kb::Home || $key == kb::PgUp) { 2188 $self-> value( $self-> {min}); 2189 $self-> clear_event; 2190 return; 2191 } 2192 if ( $key == kb::End || $key == kb::PgDn) { 2193 $self-> value( $self-> {max}); 2194 $self-> clear_event; 2195 return; 2196 } 2197 if ( $key == kb::Left || $key == kb::Right || $key == kb::Up || $key == kb::Down) { 2198 my $s = $self-> {step}; 2199 $self-> clear_event; 2200 $self-> set_next_value(( $key == kb::Left || $key == kb::Down) ? -$s : $s); 2201 } 2202} 2203 2204sub on_translateaccel 2205{ 2206 my ( $self, $code, $key, $mod) = @_; 2207 if ( 2208 defined $self-> {accel} && 2209 ($key == kb::NoKey) && 2210 lc chr $code eq $self-> { accel} 2211 ) { 2212 $self-> clear_event; 2213 $self-> select; 2214 } 2215} 2216 2217sub xy2val 2218{ 2219 my ( $self, $x, $y) = @_; 2220 $x -= $self-> {circX}; 2221 $y -= $self-> {circY}; 2222 my $a = atan2( $y, $x); 2223 my $pi = atan2( 0, -1); 2224 $a += $pi / 2; 2225 $a += $pi * 2 if $a < 0; 2226 $a = $self-> {min} + abs( $self-> {max} - $self-> {min}) * ( $pi * 1.75 - $a) * 2 / ( 3 * $pi); 2227 my $s = $self-> {step}; 2228 $a = int( $a) if int( $s) - $s == 0; 2229 my $inCircle = ( abs($x) < $self-> {radius} + 3 and abs($y) < $self-> {radius} + 3); 2230 return $a, $inCircle; 2231} 2232 2233sub on_mousedown 2234{ 2235 my ( $self, $btn, $mod, $x, $y) = @_; 2236 return if $self-> {readOnly}; 2237 return if $self-> {mouseTransaction}; 2238 return if $btn != mb::Left; 2239 my @butt = ( 2240 $self-> {butt1X}, $self-> {butt1Y}, 2241 $self-> {butt2X}, $self-> {butt1X} + $self->buttonWidth, 2242 $self-> {butt1Y} + $self->buttonWidth, $self-> {butt2X} + $self->buttonWidth 2243 ); 2244 if ( $self-> {buttons} and $y >= $butt[1] and $y < $butt[4]) { 2245 if ( $x >= $butt[0] and $x < $butt[3]) { 2246 $self-> {pressState} = 1; 2247 $self-> invalidate_rect( @butt[0..1], $butt[3] + 1, $butt[4] + 1); 2248 } 2249 if ( $x >= $butt[2] and $x < $butt[5]) { 2250 $self-> {pressState} = 2; 2251 $self-> invalidate_rect( $butt[2], $butt[1], $butt[5] + 1, $butt[4] + 1); 2252 } 2253 if ( $self-> {pressState} > 0) { 2254 $self-> {mouseTransaction} = $self-> {pressState}; 2255 $self-> update_view; 2256 $self-> capture(1); 2257 $self-> scroll_timer_start; 2258 $self-> scroll_timer_semaphore(0); 2259 $self-> value( $self-> value + 2260 $self-> step * (($self-> {pressState} == 1) ? -1 : 1)); 2261 return; 2262 } 2263 } 2264 return unless $self->{show_dial}; 2265 2266 my ( $val, $inCircle) = $self-> xy2val( $x, $y); 2267 return unless $inCircle; 2268 $self-> {mouseTransaction} = 3; 2269 $self-> capture(1); 2270 $self-> value( $val); 2271 $self-> clear_event; 2272} 2273 2274sub on_mouseup 2275{ 2276 my ( $self, $btn, $mod, $x, $y) = @_; 2277 return if $btn != mb::Left; 2278 return unless $self-> {mouseTransaction}; 2279 my @butt = ( 2280 $self-> {butt1X}, $self-> {butt1Y}, $self-> {butt2X}, 2281 $self-> {butt1X} + $self->buttonWidth, $self-> {butt1Y} + $self->buttonWidth, 2282 $self-> {butt2X} + $self->buttonWidth 2283 ); 2284 $self-> scroll_timer_stop; 2285 $self-> {pressState} = 0; 2286 if ( $self-> {mouseTransaction} == 1) { 2287 $self-> invalidate_rect( @butt[0..1], $butt[3] + 1, $butt[4] + 1); 2288 $self-> update_view; 2289 } 2290 if ( $self-> {mouseTransaction} == 2) { 2291 $self-> invalidate_rect( $butt[2], $butt[1], $butt[5] + 1, $butt[4] + 1); 2292 $self-> update_view; 2293 } 2294 my $mt = $self-> {mouseTransaction}; 2295 $self-> {mouseTransaction} = undef; 2296 $self-> capture(0); 2297 $self-> notify( 'Change') if $mt == 3 && !$self-> {autoTrack}; 2298} 2299 2300sub on_mousemove 2301{ 2302 my ( $self, $mod, $x, $y) = @_; 2303 unless ($self-> {mouseTransaction}) { 2304 if ( $self-> enabled ) { 2305 my ( undef, $prelight) = $self-> xy2val( $x, $y); 2306 if (($prelight // 0) != ($self->{prelight} // 0)) { 2307 $self->{prelight} = $prelight; 2308 $self->repaint_circle; 2309 } 2310 } 2311 return; 2312 } 2313 if ( $self-> {mouseTransaction} == 3) { 2314 my $ov = $self-> {value}; 2315 $self-> {suppressNotify} = 1 unless $self-> {autoTrack}; 2316 $self-> value( $self-> xy2val( $x, $y)); 2317 $self-> {suppressNotify} = 0; 2318 $self-> notify(q(Track)) if !$self-> {autoTrack} && $ov != $self-> {value}; 2319 } elsif ( $self-> {pressState} > 0) { 2320 $self-> scroll_timer_start unless $self-> scroll_timer_active; 2321 return unless $self-> scroll_timer_semaphore; 2322 $self-> scroll_timer_semaphore(0); 2323 $self-> value( $self-> value + 2324 $self-> step * (( $self-> {mouseTransaction} == 1) ? -1 : 1)); 2325 } else { 2326 $self-> scroll_timer_stop; 2327 } 2328} 2329 2330sub on_mouseleave 2331{ 2332 my $self = shift; 2333 $self-> repaint_circle if defined( delete $self->{prelight} ); 2334} 2335 2336sub on_mouseclick 2337{ 2338 my $self = shift; 2339 $self-> clear_event; 2340 return unless pop; 2341 $self-> clear_event unless $self-> notify( "MouseDown", @_); 2342} 2343 2344sub on_size { $_[0]-> reset; } 2345sub on_fontchanged { $_[0]-> reset; } 2346sub on_enter { $_[0]-> repaint; } 2347sub on_leave { $_[0]-> repaint; } 2348 2349sub on_stringify 2350{ 2351 my ( $self, $value, $sref) = @_; 2352 $$sref = $value; 2353 $self-> clear_event; 2354} 2355 2356sub set_buttons 2357{ 2358 $_[0]-> {buttons} = $_[1]; 2359 $_[0]-> repaint; 2360} 2361 2362sub set_std_pointer 2363{ 2364 $_[0]-> {stdPointer} = $_[1]; 2365 $_[0]-> repaint; 2366} 2367 2368sub stdPointer {($#_)?$_[0]-> set_std_pointer ($_[1]):return $_[0]-> {stdPointer};} 2369sub buttons {($#_)?$_[0]-> set_buttons ($_[1]):return $_[0]-> {buttons};} 2370 2371sub repaint_circle 2372{ 2373 my $self = shift; 2374 $self-> {singlePaint} = 1; 2375 my $radius = $self->{radius} // 0; 2376 my @clip = ( 2377 int( $self-> {circX} - $radius), 2378 int( $self-> {circY} - $radius), 2379 int( $self-> {circX} + $radius), 2380 int( $self-> {circY} + $radius), 2381 ); 2382 $self-> {expectedClip} = \@clip; 2383 $self-> invalidate_rect( @clip[0..1], $clip[2]+1, $clip[3]+1); 2384 $self-> update_view; 2385 $self-> {singlePaint} = undef; 2386} 2387 2388sub value 2389{ 2390 return $_[0]-> {value} unless $#_; 2391 my ( $self, $value) = @_; 2392 my ( $min, $max) = ( $self-> {min}, $self-> {max}); 2393 my $old = $self-> {value}; 2394 $value = $min if $value < $min; 2395 $value = $max if $value > $max; 2396 if ( $self-> {snap}) { 2397 my ( $minDist, $thatVal, $i) = ( abs( $min - $max)); 2398 my $tval = $self-> {tickVal}; 2399 for ( $i = 0; $i < scalar @{$tval}; $i++) { 2400 my $j = $$tval[ $i]; 2401 $minDist = abs(($thatVal = $j) - $value) if abs( $j - $value) < $minDist; 2402 } 2403 $value = $thatVal if defined $thatVal; 2404 } elsif ( $self-> {step} != 0 ) { 2405 $value = int ( $value / $self-> {step} ) * $self-> {step}; 2406 } 2407 return if $old == $value; 2408 2409 $self-> {value} = $value; 2410 $self-> notify(q(Stringify), $value, \$self-> {string}); 2411 $self-> repaint_circle; 2412 $self-> notify(q(Change)) unless $self-> {suppressNotify}; 2413} 2414 2415sub buttonWidth 2416{ 2417 return $_[0]->{buttonWidth} unless $#_; 2418 my ( $self, $bw) = @_; 2419 $bw = 1 if $bw < 1; 2420 return if $bw == $self-> {buttonWidth}; 2421 $self-> {buttonWidth} = $bw; 2422 $self-> repaint; 2423} 2424 2425package Prima::ProgressBar; 2426use vars qw(@ISA); 2427@ISA = qw(Prima::Widget); 2428 2429my $TIMER_SILENT_PERIOD = 2000; 2430my $TIMER_ACTIVE_PERIOD = 10; 2431my $TAB_STEP = 10; 2432my $INDENT = 1; 2433my $USE_ANIMATION; 2434 2435sub profile_default 2436{ 2437 return { 2438 %{$_[ 0]-> SUPER::profile_default}, 2439 buffered => 1, 2440 color => cl::Green, 2441 max => 100, 2442 min => 0, 2443 value => 0, 2444 } 2445} 2446 2447sub init 2448{ 2449 my $self = shift; 2450 $self->{$_} = 0 for qw( value min max ); 2451 $self->{cache} = { 2452 size => [0,0], 2453 }; 2454 $self->{tabmode} = 'silent'; 2455 $self->{tabpos} = 0; 2456 my %profile = $self-> SUPER::init(@_); 2457 $self->$_($profile{$_}) for qw( min max value); 2458 2459 $USE_ANIMATION //= $self->can_draw_alpha; 2460 2461 $self->insert( 'Prima::Timer' => 2462 name => 'Timer', 2463 delegations => ['Tick'], 2464 ) if $USE_ANIMATION; 2465 $self-> next_tick if $self-> visible; 2466 2467 return %profile; 2468} 2469 2470sub mask2icon 2471{ 2472 my ( $mask, $color ) = @_; 2473 my $bits = Prima::Image->new( 2474 size => [ $mask-> size ], 2475 type => im::Byte, 2476 backColor => $color, 2477 ); 2478 $bits-> clear; 2479 my $icon = Prima::Icon-> create_combined($bits, $mask); 2480 $icon->premultiply_alpha; 2481 return $icon; 2482} 2483 2484sub create_tab 2485{ 2486 my ( $self, $x, $y ) = @_; 2487 2488 my $tab_mask = Prima::Image->new( 2489 size => [ int($x / 5 + .5), 1 ], 2490 type => im::Byte, 2491 backColor => cl::White, 2492 ); 2493 $tab_mask-> clear; 2494 $tab_mask-> put_image(0,0,$tab_mask,rop::SrcOut | rop::DstAlpha | ( 128 << rop::DstAlphaShift ) ); 2495 2496 my $tabend_mask = Prima::Image->new( 2497 size => [ $y * 2, 1 ], 2498 type => im::Byte, 2499 color => cl::Black, 2500 ); 2501 $tabend_mask->new_gradient( 2502 palette => [cl::Black, cl::White, cl::Black], 2503 )->bar(0, 0, $y * 2, 1, 1); 2504 my $tabend = mask2icon( $tabend_mask, cl::Black ); 2505 2506 $tab_mask-> put_image_indirect( $tabend_mask, 0, 0, $y, 0, $y, 1, $y, 1, rop::SrcOver | rop::ConstantColor | rop::Premultiply); 2507 $tab_mask-> put_image_indirect( $tabend_mask, $tab_mask-> width - $y, 0, 0, 0, $y, 1, $y, 1, rop::SrcOver | rop::ConstantColor | rop::Premultiply); 2508 2509 $self->{cache}->{tab} = mask2icon( $tab_mask, cl::White )-> bitmap; 2510 $self->{cache}->{tabx} = $tab_mask-> width; 2511} 2512 2513sub recalc_images 2514{ 2515 my ( $self, $x, $y ) = @_; 2516 2517 $x ||= 1; 2518 $y ||= 1; 2519 my $cache = $self->{cache}; 2520 return if $cache->{size}->[0] == $x && $cache->{size}->[1] == $y; 2521 2522 my $recalc_x = $cache->{size}->[1] != $y; 2523 if ( !$recalc_x && $cache->{size}->[0] != $x ) { 2524 my $tabx = $x / 5; 2525 my $diff = abs( $cache->{tabx} - $tabx ) / $tabx; 2526 $recalc_x = 1 if $diff < 0.8 || $diff > 1.2; 2527 } 2528 2529 $self->create_tab( $x, $y ) if $recalc_x; 2530} 2531 2532sub next_tick 2533{ 2534 my $self = shift; 2535 2536 return unless $USE_ANIMATION; 2537 2538 my $timer = $self-> Timer; 2539 if ( $self->{tabmode} eq 'silent' ) { 2540 if ( $timer-> get_active ) { 2541 $self->{tabmode} = 'show'; 2542 $self->{tabpos} = - $self->{cache}->{tabx}; 2543 $timer->timeout( $TIMER_ACTIVE_PERIOD ); 2544 } else { 2545 $timer->timeout( $TIMER_SILENT_PERIOD ); 2546 } 2547 $timer->start; 2548 } elsif ( $self->{tabpos} < $self-> width ) { 2549 $self->{tabpos} += $TAB_STEP; 2550 $self->repaint; 2551 } else { 2552 $self->{tabmode} = 'silent'; 2553 $timer->timeout( $TIMER_SILENT_PERIOD ); 2554 $timer->start; 2555 $self->repaint; 2556 } 2557} 2558 2559sub on_size 2560{ 2561 my ( $self, $ox, $oy, $x, $y ) = @_; 2562 $self->recalc_images( $x, $y ); 2563} 2564 2565sub on_hide 2566{ 2567 my $self = shift; 2568 $self->Timer1->stop; 2569 $self->{tabmode} = 'silent'; 2570} 2571 2572sub on_show 2573{ 2574 shift->next_tick; 2575} 2576 2577sub on_paint 2578{ 2579 my ($self,$canvas) = @_; 2580 2581 my ($xa1, $xa2, $xb1, $xb2, $y1, $y2); 2582 2583 my @sz = $self-> size; 2584 my $indent = $INDENT; 2585 my $range = $self->{max} - $self->{min}; 2586 $y1 = $indent; 2587 $y2 = $sz[1] - 1; 2588 if ( $self->{value} == $self->{min} || $sz[0] == 0 || $sz[1] == 0 || $range == 0) { 2589 $xa1 = $xa2 = -1; 2590 } else { 2591 $xa1 = $indent; 2592 $xa2 = ( $self->{value} == $self->{max} ) ? 2593 $sz[0] - $indent : 2594 (( $sz[0] - $indent * 2 ) * $self->{value} / $range + $indent); 2595 } 2596 if ( $self->{value} == $self->{max} || $sz[0] == 0 || $sz[1] == 0 || $range == 0) { 2597 $xb1 = $xb2 = -1; 2598 } else { 2599 $xb1 = ( $xa2 < 0 ) ? $indent : ( $xa2 + 1 ); 2600 $xb2 = $sz[0] - $indent; 2601 } 2602 2603 $canvas-> new_gradient( 2604 palette => [ cl::Black, $self->color, cl::White ], 2605 poly => [ 0, 0.25, 1, 0.75, 0.75, 0.25 + 0.5 * 0.75 ], 2606 )-> bar( $xa1, $y1, $xa2, $y2 ) if $xa1 > 0; 2607 2608 $canvas-> new_gradient( 2609 palette => [ cl::Black, cl::Gray, cl::White ], 2610 poly => [ 0, 0.25, 1, 0.75, 0.75, 0.25 + 0.5 * 0.75 ], 2611 )-> bar( $xb1, $y1, $xb2, $y2 ) if $xb1 > 0; 2612 2613 $canvas-> color(cl::Gray); 2614 $canvas-> rectangle( 0, 0, $sz[0] - 1, $sz[1] - 1); 2615 2616 if ( $self->{tabmode} eq 'show' && $xa1 > 0) { 2617 $canvas->clipRect(0, 0, $xa2, $sz[1]); 2618 $canvas->put_image( $self->{tabpos}, $_, $self->{cache}->{tab}, rop::SrcOver ) for 0 .. $sz[1]; 2619 } 2620} 2621 2622sub set_bounds 2623{ 2624 my ( $self, $min, $max) = @_; 2625 $max = $min if $max < $min; 2626 ( $self-> { min}, $self-> { max}) = ( $min, $max); 2627 $self-> value( $max) if $self-> {value} > $max; 2628 $self-> value( $min) if $self-> {value} < $min; 2629} 2630 2631sub value 2632{ 2633 return $_[0]-> {value} unless $#_; 2634 my $v = $_[1] < $_[0]-> {min} ? $_[0]-> {min} : ($_[1] > $_[0]-> {max} ? $_[0]-> {max} : $_[1]); 2635 return if $v == $_[0]->{value}; 2636 $_[0]-> {value} = $v; 2637 $_[0]-> repaint; 2638} 2639 2640sub min {($#_)?$_[0]-> set_bounds($_[1], $_[0]-> {'max'}) : return $_[0]-> {min};} 2641sub max {($#_)?$_[0]-> set_bounds($_[0]-> {'min'}, $_[1]) : return $_[0]-> {max};} 2642 2643sub Timer_Tick { shift-> next_tick } 2644 26451; 2646 2647=pod 2648 2649=head1 NAME 2650 2651Prima::Sliders - sliding bars, spin buttons and input lines, dial widget etc. 2652 2653=head1 DESCRIPTION 2654 2655The module is a set of widget classes, with one 2656common property; - all of these provide input and / or output of an integer value. 2657This property unites the following set of class hierarchies: 2658 2659 Prima::AbstractSpinButton 2660 Prima::SpinButton 2661 Prima::AltSpinButton 2662 2663 Prima::SpinEdit 2664 2665 Prima::Gauge 2666 Prima::PrigressBar 2667 2668 Prima::AbstractSlider 2669 Prima::Slider 2670 Prima::CircularSlider 2671 2672=head1 Prima::AbstractSpinButton 2673 2674Provides a generic interface to spin-button class functionality, which includes 2675range definition properties and events. Neither C<Prima::AbstractSpinButton>, nor 2676its descendants store the integer value. These provide a mere possibility for 2677the user to send incrementing or decrementing commands. 2678 2679The class is not usable directly. 2680 2681=head2 Properties 2682 2683=over 2684 2685=item state INTEGER 2686 2687Internal state, reflects widget modal state, for example, 2688is set to non-zero when the user performs a mouse drag action. The exact meaning of C<state> 2689is defined in the descendant classes. 2690 2691=back 2692 2693=head2 Events 2694 2695=over 2696 2697=item Increment DELTA 2698 2699Called when the user presses a part of a widget that is responsible for 2700incrementing or decrementing commands. DELTA is an integer value, 2701indicating how the associated value must be modified. 2702 2703=item TrackEnd 2704 2705Called when the user finished the mouse transaction. 2706 2707=back 2708 2709=head1 Prima::SpinButton 2710 2711=for podview <img src="spinbutton.gif"> 2712 2713=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/spinbutton.gif"> 2714 2715A rectangular spin button, consists of three parts, divided horizontally. 2716The upper and the lower parts are push-buttons associated with singular 2717increment and decrement commands. The middle part, when dragged by mouse, 2718fires C<Increment> events with delta value, based on a vertical position 2719of the mouse pointer. 2720 2721=head1 Prima::AltSpinButton 2722 2723=for podview <img src="altspinbutton.gif"> 2724 2725=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/altspinbutton.gif"> 2726 2727A rectangular spin button, consists of two push-buttons, associated 2728with singular increment and decrement command. Comparing to C<Prima::SpinButton>, 2729the class is less functional but has more stylish look. 2730 2731=head1 Prima::SpinEdit 2732 2733=for podview <img src="spinedit.gif"> 2734 2735=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/spinedit.gif"> 2736 2737The class is a numerical input line, paired with a spin button. 2738The input line value can be change three ways - either as a direct 2739traditional keyboard input, or as spin button actions, or as mouse 2740wheel response. The class provides value storage and range 2741selection properties. 2742 2743=head2 Properties 2744 2745=over 2746 2747=item circulate BOOLEAN 2748 2749Selects the value modification rule when the increment or decrement 2750action hits the range. If 1, the value is changed to the opposite limit 2751value ( for example, if value is 100 in range 2-100, and the user 2752clicks on 'increment' button, the value is changed to 2 ). 2753 2754If 0, the value does not change. 2755 2756Default value: 0 2757 2758=item editClass STRING 2759 2760Assigns an input line class. 2761 2762Create-only property. 2763 2764Default value: C<Prima::InputLine> 2765 2766=item editDelegations ARRAY 2767 2768Assigns the input line list of delegated notifications. 2769 2770Create-only property. 2771 2772=item editProfile HASH 2773 2774Assigns hash of properties, passed to the input line during the creation. 2775 2776Create-only property. 2777 2778=item max INTEGER 2779 2780Sets the upper limit for C<value>. 2781 2782Default value: 100. 2783 2784=item min INTEGER 2785 2786Sets the lower limit for C<value>. 2787 2788Default value: 0 2789 2790=item pageStep INTEGER 2791 2792Determines the multiplication factor for incrementing/decrementing 2793actions of the mouse wheel. 2794 2795Default value: 10 2796 2797=item spinClass STRING 2798 2799Assigns a spin-button class. 2800 2801Create-only property. 2802 2803Default value: C<Prima::AltSpinButton> 2804 2805=item spinProfile ARRAY 2806 2807Assigns the spin-button list of delegated notifications. 2808 2809Create-only property. 2810 2811=item spinDelegations HASH 2812 2813Assigns hash of properties, passed to the spin-button during the creation. 2814 2815Create-only property. 2816 2817=item step INTEGER 2818 2819Determines the multiplication factor for incrementing/decrementing 2820actions of the spin-button. 2821 2822Default value: 1 2823 2824=item value INTEGER 2825 2826Selects integer value in range from C<min> to C<max>, reflected in the input line. 2827 2828Default value: 0. 2829 2830=back 2831 2832=head2 Methods 2833 2834=over 2835 2836=item set_bounds MIN, MAX 2837 2838Simultaneously sets both C<min> and C<max> values. 2839 2840=back 2841 2842=head2 Events 2843 2844=over 2845 2846=item Change 2847 2848Called when C<value> is changed. 2849 2850=back 2851 2852=head1 Prima::Gauge 2853 2854=for podview <img src="gauge.gif"> 2855 2856=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/gauge.gif"> 2857 2858An output-only widget class, displays a progress bar and an eventual percentage string. 2859Useful as a progress indicator. 2860 2861=head2 Properties 2862 2863=over 2864 2865=item indent INTEGER 2866 2867Selects width of a border around the widget. 2868 2869Default value: 1 2870 2871=item max INTEGER 2872 2873Sets the upper limit for C<value>. 2874 2875Default value: 100. 2876 2877=item min INTEGER 2878 2879Sets the lower limit for C<value>. 2880 2881Default value: 0 2882 2883=item relief INTEGER 2884 2885Selects the style of a border around the widget. Can be one of the 2886following C<gr::XXX> constants: 2887 2888 gr::Sink - 3d sunken look 2889 gr::Border - uniform black border 2890 gr::Raise - 3d risen look 2891 2892Default value: C<gr::Sink>. 2893 2894=item threshold INTEGER 2895 2896Selects the threshold value used to determine if the changes to C<value> 2897are reflected immediately or deferred until the value is changed more 2898significantly. When 0, all calls to C<value> result in an immediate 2899repaint request. 2900 2901Default value: 0 2902 2903=item value INTEGER 2904 2905Selects integer value between C<min> and C<max>, reflected in the progress bar and 2906eventual text. 2907 2908Default value: 0. 2909 2910=item vertical BOOLEAN 2911 2912If 1, the widget is drawn vertically, and the progress bar moves from bottom to top. 2913If 0, the widget is drawn horizontally, and the progress bar moves from left to right. 2914 2915Default value: 0 2916 2917=back 2918 2919=head2 Methods 2920 2921=over 2922 2923=item set_bounds MIN, MAX 2924 2925Simultaneously sets both C<min> and C<max> values. 2926 2927=back 2928 2929=head2 Events 2930 2931=over 2932 2933=item Stringify VALUE, REF 2934 2935Converts integer VALUE into a string format and puts into REF scalar reference. 2936Default stringifying conversion is identical to C<sprintf("%2d%%")> one. 2937 2938=back 2939 2940=head1 Prima::ProgressBar 2941 2942=for podview <img src="progressbar.png"> 2943 2944=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/progressbar.png"> 2945 2946Displays a progress bar 2947 2948=head2 Properties 2949 2950=over 2951 2952=item max INTEGER 2953 2954Sets the upper limit for C<value>. 2955 2956Default value: 100. 2957 2958=item min INTEGER 2959 2960Sets the lower limit for C<value>. 2961 2962Default value: 0 2963 2964=item value INTEGER 2965 2966Selects integer value between C<min> and C<max>, reflected in the progress bar and 2967eventual text. 2968 2969Default value: 0. 2970 2971=back 2972 2973=head2 Methods 2974 2975=over 2976 2977=item set_bounds MIN, MAX 2978 2979Simultaneously sets both C<min> and C<max> values. 2980 2981=back 2982 2983=head1 Prima::AbstractSlider 2984 2985The class provides basic functionality of a sliding bar, equipped with 2986tick marks. Tick marks are supposed to be drawn alongside the main sliding axis or 2987circle and provide visual feedback for the user. 2988 2989The class is not usable directly. 2990 2991=head2 Properties 2992 2993=over 2994 2995=item autoTrack BOOLEAN 2996 2997A boolean flag, selects the way notifications execute when the user mouse-drags 2998the sliding bar. If 1, C<Change> notification is executed as soon as C<value> 2999is changed. If 0, C<Change> is deferred until the user finished the mouse drag; 3000instead, C<Track> notification is executed when the bar is moved. 3001 3002This property can be used when the action, called on C<Change> performs very 3003slow, so the eventual fast mouse interactions would not thrash down the program. 3004 3005Default value: 1 3006 3007=item increment INTEGER 3008 3009A step range value, used in C<scheme> for marking the key ticks. 3010See L<scheme> for details. 3011 3012Default value: 10 3013 3014=item max INTEGER 3015 3016Sets the upper limit for C<value>. 3017 3018Default value: 100. 3019 3020=item min INTEGER 3021 3022Sets the lower limit for C<value>. 3023 3024Default value: 0 3025 3026=item readOnly BOOLEAN 3027 3028If 1, the use cannot change the value by moving the bar or otherwise. 3029 3030Default value: 0 3031 3032=item ticks ARRAY 3033 3034Selects the tick marks representation along the sliding axis or circle. 3035ARRAY consists of hashes, each for one tick. The hash must contain 3036at least C<value> key, with integer value. The two additional keys, 3037C<height> and C<text>, select the height of a tick mark in pixels 3038and the text drawn near the mark, correspondingly. 3039 3040If ARRAY is C<undef>, no ticks are drawn. 3041 3042=item scheme INTEGER 3043 3044C<scheme> is a property, that creates a set of tick marks 3045using one of the predefined scale designs, selected by C<ss::XXX> constants. 3046Each constant produces different scale; some make use of C<increment> integer 3047property, which selects a step by which the additional 3048text marks are drawn. As an example, C<ss::Thermometer> design with 3049default C<min>, C<max>, and C<increment> values would look like that: 3050 3051 0 10 20 100 3052 | | | | 3053 |||||||||||||||....||| 3054 3055The module defines the following constants: 3056 3057 ss::Axis - 5 minor ticks per increment 3058 ss::Gauge - 1 tick per increment 3059 ss::StdMinMax - 2 ticks at the ends of the bar 3060 ss::Thermometer - 10 minor ticks per increment, longer text ticks 3061 3062When C<tick> property is set, C<scheme> is reset to C<undef>. 3063 3064=item snap BOOLEAN 3065 3066If 1, C<value> cannot accept values that are not on the tick scale. 3067When set such a value, it is rounded to the closest tick mark. 3068If 0, C<value> can accept any integer value in range from C<min> to C<max>. 3069 3070Default value: 0 3071 3072=item step INTEGER 3073 3074Integer delta for singular increment / decrement commands and 3075a threshold for C<value> when C<snap> value is 0. 3076 3077Default value: 1 3078 3079=item value INTEGER 3080 3081Selects integer value between C<min> and C<max> and the corresponding sliding bar 3082position. 3083 3084Default value: 0. 3085 3086=back 3087 3088=head2 Events 3089 3090=over 3091 3092=item Change 3093 3094Called when C<value> value is changed, with one exception: 3095if the user moves the sliding bar while C<autoTrack> is 0, C<Track> 3096notification is called instead. 3097 3098=item Track 3099 3100Called when the user moves the sliding bar while C<autoTrack> value is 0; 3101this notification is a substitute to C<Change>. 3102 3103=back 3104 3105=head1 Prima::Slider 3106 3107=for podview <img src="slider.gif"> 3108 3109=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/slider.gif"> 3110 3111Presents a linear sliding bar, movable along a linear shaft. 3112 3113=head2 Properties 3114 3115=over 3116 3117=item borderWidth INTEGER 3118 3119In horizontal mode, sets extra margin space between the slider line and 3120the widget boundaries. Can be used for fine tuning of displaying text 3121labels from <ticks()>, where the default spacing (0) or spacing procedure 3122(drop overlapping labels) is not enough. 3123 3124=item ribbonStrip BOOLEAN 3125 3126If 1, the parts of shaft are painted with different colors, to increase 3127visual feedback. If 0, the shaft is painted with single default background color. 3128 3129Default value: 0 3130 3131=item shaftBreadth INTEGER 3132 3133Breadth of the shaft in pixels. 3134 3135Default value: 6 3136 3137=item tickAlign INTEGER 3138 3139One of C<tka::XXX> constants, that correspond to the situation of tick marks: 3140 3141 tka::Normal - ticks are drawn on the left or on the top of the shaft 3142 tka::Alternative - ticks are drawn on the right or at the bottom of the shaft 3143 tka::Dual - ticks are drawn both ways 3144 3145The ticks orientation ( left or top, right or bottom ) is dependant on C<vertical> 3146property value. 3147 3148Default value: C<tka::Normal> 3149 3150=item vertical BOOLEAN 3151 3152If 1, the widget is drawn vertically, and the slider moves from bottom to top. 3153If 0, the widget is drawn horizontally, and the slider moves from left to right. 3154 3155Default value: 0 3156 3157=back 3158 3159=head2 Methods 3160 3161=over 3162 3163=item pos2info X, Y 3164 3165Translates integer coordinates pair ( X, Y ) into the value corresponding to the scale, 3166and returns three scalars: 3167 3168=over 3169 3170=item info INTEGER 3171 3172If C<undef>, the user-driven positioning is not possible ( C<min> equals to C<max> ). 3173 3174If 1, the point is located on the slider. 3175 3176If 0, the point is outside the slider. 3177 3178=item value INTEGER 3179 3180If C<info> is 0 or 1, contains the corresponding C<value>. 3181 3182=item aperture INTEGER 3183 3184Offset in pixels along the shaft axis. 3185 3186=back 3187 3188=back 3189 3190=head1 Prima::CircularSlider 3191 3192=for podview <img src="circularslider.gif"> 3193 3194=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/circularslider.gif"> 3195 3196Presents a slider widget with the dial and two increment / decrement buttons. 3197The tick marks are drawn around the perimeter of the dial; current value 3198is displayed in the center of the dial. 3199 3200=head2 Properties 3201 3202=over 3203 3204=item buttons BOOLEAN 3205 3206If 1, the increment / decrement buttons are shown at the bottom of the dial, 3207and the user can change the value either by the dial or by the buttons. 3208If 0, the buttons are not shown. 3209 3210Default values: 0 3211 3212=item stdPointer BOOLEAN 3213 3214Determines the style of a value indicator ( pointer ) on the dial. 3215If 1, it is drawn as a black triangular mark. 3216If 0, it is drawn as a small circular knob. 3217 3218Default value: 0 3219 3220=back 3221 3222=head2 Methods 3223 3224=over 3225 3226=item offset2data VALUE 3227 3228Converts integer value in range from C<min> to C<max> into 3229the corresponding angle, and return two real values: 3230cosine and sine of the angle. 3231 3232=item offset2pt X, Y, VALUE, RADIUS 3233 3234Converts integer value in range from C<min> to C<max> into the 3235point coordinates, with the RADIUS and dial center coordinates 3236X and Y. Return the calculated point coordinates 3237as two integers in (X,Y) format. 3238 3239=item xy2val X, Y 3240 3241Converts widget coordinates X and Y into value in range from C<min> 3242to C<max>, and return two scalars: the value and the boolean flag, 3243which is set to 1 if the (X,Y) point is inside the dial circle, 3244and 0 otherwise. 3245 3246=back 3247 3248=head2 Events 3249 3250=over 3251 3252=item Stringify VALUE, REF 3253 3254Converts integer VALUE into a string format and puts into REF scalar reference. 3255The resulting string is displayed in the center of the dial. 3256 3257Default conversion routine simply copies VALUE to REF as is. 3258 3259=back 3260 3261=head1 AUTHOR 3262 3263Dmitry Karasik, E<lt>dmitry@karasik.eu.orgE<gt>, 3264Anton Berezin E<lt>tobez@tobez.orgE<gt>. 3265 3266=head1 SEE ALSO 3267 3268L<Prima>, F<examples/fontdlg.pl> 3269 3270=cut 3271