1 /* 2 * Copyright 2006-2010 Stephan Aßmus <superstippi@gmx.de> 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 7 // NOTE: Based on my code in the BeOS interface for the VLC media player 8 // that I did during the VLC 0.4.3 - 0.4.6 times. Code not written by me 9 // removed. -Stephan Aßmus 10 11 12 #include "TransportControlGroup.h" 13 14 #include <stdio.h> 15 #include <string.h> 16 17 #include <Shape.h> 18 #include <SpaceLayoutItem.h> 19 #include <String.h> 20 21 #include "DurationView.h" 22 #include "PeakView.h" 23 #include "PlaybackState.h" 24 #include "PlayPauseButton.h" 25 #include "PositionToolTip.h" 26 #include "SeekSlider.h" 27 #include "SymbolButton.h" 28 #include "VolumeSlider.h" 29 30 enum { 31 MSG_SEEK = 'seek', 32 MSG_PLAY = 'play', 33 MSG_STOP = 'stop', 34 MSG_REWIND = 'rwnd', 35 MSG_FORWARD = 'frwd', 36 MSG_SKIP_BACKWARDS = 'skpb', 37 MSG_SKIP_FORWARD = 'skpf', 38 MSG_SET_VOLUME = 'stvl', 39 MSG_SET_MUTE = 'stmt', 40 }; 41 42 // the range of the volume sliders (in dB) 43 #define kVolumeDbMax 6.0 44 #define kVolumeDbMin -60.0 45 // a power function for non linear sliders 46 #define kVolumeDbExpPositive 1.4 // for dB values > 0 47 #define kVolumeDbExpNegative 1.9 // for dB values < 0 48 49 #define kVolumeFactor 100 50 #define kPositionFactor 3000 51 52 53 TransportControlGroup::TransportControlGroup(BRect frame, bool useSkipButtons, 54 bool usePeakView, bool useWindButtons) 55 : 56 BGroupView(B_VERTICAL, 0), 57 fSeekSlider(NULL), 58 fDurationView(NULL), 59 fPositionToolTip(NULL), 60 fPeakView(NULL), 61 fVolumeSlider(NULL), 62 fSkipBack(NULL), 63 fSkipForward(NULL), 64 fRewind(NULL), 65 fForward(NULL), 66 fPlayPause(NULL), 67 fStop(NULL), 68 fMute(NULL), 69 fSymbolScale(1.0f) 70 { 71 // Pick a symbol size based on the current system font size, but make 72 // sure the size is uneven, so the pointy shapes have their middle on 73 // a pixel instead of between two pixels. 74 float symbolHeight = int(be_plain_font->Size() / 1.33) | 1; 75 76 BGroupView* seekGroup = new BGroupView(B_HORIZONTAL, 0); 77 fSeekLayout = seekGroup->GroupLayout(); 78 GroupLayout()->AddView(seekGroup); 79 80 // Seek slider 81 fSeekSlider = new SeekSlider("seek slider", new BMessage(MSG_SEEK), 82 0, kPositionFactor); 83 fSeekLayout->AddView(fSeekSlider); 84 85 fPositionToolTip = new PositionToolTip(); 86 fSeekSlider->SetToolTip(fPositionToolTip); 87 88 // Duration view 89 fDurationView = new DurationView("duration view"); 90 fSeekLayout->AddView(fDurationView); 91 92 // Buttons 93 94 uint32 topBottomBorder = BControlLook::B_TOP_BORDER 95 | BControlLook::B_BOTTOM_BORDER; 96 97 if (useSkipButtons) { 98 // Skip Back 99 fSkipBack = new SymbolButton(B_EMPTY_STRING, 100 _CreateSkipBackwardsShape(symbolHeight), 101 new BMessage(MSG_SKIP_BACKWARDS), 102 BControlLook::B_LEFT_BORDER | topBottomBorder); 103 // Skip Foward 104 fSkipForward = new SymbolButton(B_EMPTY_STRING, 105 _CreateSkipForwardShape(symbolHeight), 106 new BMessage(MSG_SKIP_FORWARD), 107 BControlLook::B_RIGHT_BORDER | topBottomBorder); 108 } 109 110 if (useWindButtons) { 111 // Rewind 112 fRewind = new SymbolButton(B_EMPTY_STRING, 113 _CreateRewindShape(symbolHeight), new BMessage(MSG_REWIND), 114 useSkipButtons ? topBottomBorder 115 : BControlLook::B_LEFT_BORDER | topBottomBorder); 116 // Forward 117 fForward = new SymbolButton(B_EMPTY_STRING, 118 _CreateForwardShape(symbolHeight), new BMessage(MSG_FORWARD), 119 useSkipButtons ? topBottomBorder 120 : BControlLook::B_RIGHT_BORDER | topBottomBorder); 121 } 122 123 // Play Pause 124 fPlayPause = new PlayPauseButton(B_EMPTY_STRING, 125 _CreatePlayShape(symbolHeight), _CreatePauseShape(symbolHeight), 126 new BMessage(MSG_PLAY), useWindButtons || useSkipButtons 127 ? topBottomBorder 128 : topBottomBorder | BControlLook::B_LEFT_BORDER); 129 130 // Stop 131 fStop = new SymbolButton(B_EMPTY_STRING, 132 _CreateStopShape(symbolHeight), new BMessage(MSG_STOP), 133 useWindButtons || useSkipButtons ? topBottomBorder 134 : topBottomBorder | BControlLook::B_RIGHT_BORDER); 135 136 // Mute 137 fMute = new SymbolButton(B_EMPTY_STRING, 138 _CreateSpeakerShape(floorf(symbolHeight * 0.9)), 139 new BMessage(MSG_SET_MUTE), 0); 140 141 // Volume Slider 142 fVolumeSlider = new VolumeSlider("volume slider", 143 _DbToGain(_ExponentialToLinear(kVolumeDbMin)) * kVolumeFactor, 144 _DbToGain(_ExponentialToLinear(kVolumeDbMax)) * kVolumeFactor, 145 kVolumeFactor, new BMessage(MSG_SET_VOLUME)); 146 fVolumeSlider->SetValue(_DbToGain(_ExponentialToLinear(0.0)) 147 * kVolumeFactor); 148 149 // Peak view 150 if (usePeakView) 151 fPeakView = new PeakView("peak view", false, false); 152 153 // Layout the controls 154 155 BGroupView* buttonGroup = new BGroupView(B_HORIZONTAL, 0); 156 BGroupLayout* buttonLayout = buttonGroup->GroupLayout(); 157 158 if (fSkipBack != NULL) 159 buttonLayout->AddView(fSkipBack); 160 if (fRewind != NULL) 161 buttonLayout->AddView(fRewind); 162 buttonLayout->AddView(fPlayPause); 163 buttonLayout->AddView(fStop); 164 if (fForward != NULL) 165 buttonLayout->AddView(fForward); 166 if (fSkipForward != NULL) 167 buttonLayout->AddView(fSkipForward); 168 169 BGroupView* controlGroup = new BGroupView(B_HORIZONTAL, 0); 170 GroupLayout()->AddView(controlGroup); 171 fControlLayout = controlGroup->GroupLayout(); 172 fControlLayout->AddView(buttonGroup, 0.6f); 173 fControlLayout->AddItem(BSpaceLayoutItem::CreateHorizontalStrut(5)); 174 fControlLayout->AddView(fMute); 175 fControlLayout->AddView(fVolumeSlider); 176 if (fPeakView != NULL) 177 fControlLayout->AddView(fPeakView, 0.6f); 178 179 // Figure out the visual insets of the slider bounds towards the slider 180 // bar, and use that as insets for the rest of the layout. 181 float inset = fSeekSlider->BarFrame().left; 182 float hInset = inset - fSeekSlider->BarFrame().top; 183 if (hInset < 0.0f) 184 hInset = 0.0f; 185 186 fSeekLayout->SetInsets(0, hInset, 5, 0); 187 fControlLayout->SetInsets(inset, hInset, inset, inset); 188 189 BSize size = fControlLayout->MinSize(); 190 size.width *= 3; 191 size.height = B_SIZE_UNSET; 192 fControlLayout->SetExplicitMaxSize(size); 193 fControlLayout->SetExplicitAlignment(BAlignment(B_ALIGN_CENTER, 194 B_ALIGN_TOP)); 195 } 196 197 198 TransportControlGroup::~TransportControlGroup() 199 { 200 if (!fSeekSlider->IsEnabled()) 201 fPositionToolTip->ReleaseReference(); 202 } 203 204 205 void 206 TransportControlGroup::AttachedToWindow() 207 { 208 SetEnabled(EnabledButtons()); 209 210 // we are now a valid BHandler 211 fSeekSlider->SetTarget(this); 212 fVolumeSlider->SetTarget(this); 213 if (fSkipBack) 214 fSkipBack->SetTarget(this); 215 if (fSkipForward) 216 fSkipForward->SetTarget(this); 217 if (fRewind) 218 fRewind->SetTarget(this); 219 if (fForward) 220 fForward->SetTarget(this); 221 fPlayPause->SetTarget(this); 222 fStop->SetTarget(this); 223 fMute->SetTarget(this); 224 } 225 226 227 void 228 TransportControlGroup::GetPreferredSize(float* _width, float* _height) 229 { 230 BSize size = GroupLayout()->MinSize(); 231 if (_width != NULL) 232 *_width = size.width; 233 if (_height != NULL) 234 *_height = size.height; 235 } 236 237 238 void 239 TransportControlGroup::MessageReceived(BMessage* message) 240 { 241 switch (message->what) { 242 case MSG_PLAY: 243 _TogglePlaying(); 244 break; 245 case MSG_STOP: 246 _Stop(); 247 break; 248 249 case MSG_REWIND: 250 _Rewind(); 251 break; 252 case MSG_FORWARD: 253 _Forward(); 254 break; 255 256 case MSG_SKIP_BACKWARDS: 257 _SkipBackward(); 258 break; 259 case MSG_SKIP_FORWARD: 260 _SkipForward(); 261 break; 262 263 case MSG_SET_VOLUME: 264 _UpdateVolume(); 265 break; 266 case MSG_SET_MUTE: 267 _ToggleMute(); 268 break; 269 270 case MSG_SEEK: 271 _UpdatePosition(); 272 break; 273 274 default: 275 BView::MessageReceived(message); 276 break; 277 } 278 } 279 280 281 // #pragma mark - default implementation for the virtuals 282 283 284 uint32 285 TransportControlGroup::EnabledButtons() 286 { 287 return 0; 288 } 289 290 291 void TransportControlGroup::TogglePlaying() {} 292 void TransportControlGroup::Stop() {} 293 void TransportControlGroup::Rewind() {} 294 void TransportControlGroup::Forward() {} 295 void TransportControlGroup::SkipBackward() {} 296 void TransportControlGroup::SkipForward() {} 297 void TransportControlGroup::VolumeChanged(float value) {} 298 void TransportControlGroup::ToggleMute() {} 299 void TransportControlGroup::PositionChanged(float value) {} 300 301 302 // #pragma mark - 303 304 305 float 306 TransportControlGroup::_LinearToExponential(float dbIn) 307 { 308 float db = dbIn; 309 if (db >= 0) { 310 db = db * (pow(fabs(kVolumeDbMax), (1.0 / kVolumeDbExpPositive)) 311 / fabs(kVolumeDbMax)); 312 db = pow(db, kVolumeDbExpPositive); 313 } else { 314 db = -db; 315 db = db * (pow(fabs(kVolumeDbMin), (1.0 / kVolumeDbExpNegative)) 316 / fabs(kVolumeDbMin)); 317 db = pow(db, kVolumeDbExpNegative); 318 db = -db; 319 } 320 return db; 321 } 322 323 324 float 325 TransportControlGroup::_ExponentialToLinear(float dbIn) 326 { 327 float db = dbIn; 328 if (db >= 0) { 329 db = pow(db, (1.0 / kVolumeDbExpPositive)); 330 db = db * (fabs(kVolumeDbMax) / pow(fabs(kVolumeDbMax), 331 (1.0 / kVolumeDbExpPositive))); 332 } else { 333 db = -db; 334 db = pow(db, (1.0 / kVolumeDbExpNegative)); 335 db = db * (fabs(kVolumeDbMin) / pow(fabs(kVolumeDbMin), 336 (1.0 / kVolumeDbExpNegative))); 337 db = -db; 338 } 339 return db; 340 } 341 342 343 float 344 TransportControlGroup::_DbToGain(float db) 345 { 346 return pow(10.0, db / 20.0); 347 } 348 349 350 float 351 TransportControlGroup::_GainToDb(float gain) 352 { 353 return 20.0 * log10(gain); 354 } 355 356 357 // #pragma mark - 358 359 360 void 361 TransportControlGroup::SetEnabled(uint32 buttons) 362 { 363 if (!LockLooper()) 364 return; 365 366 fSeekSlider->SetEnabled(buttons & SEEK_ENABLED); 367 fSeekSlider->SetToolTip((buttons & SEEK_ENABLED) != 0 368 ? fPositionToolTip : NULL); 369 370 fVolumeSlider->SetEnabled(buttons & VOLUME_ENABLED); 371 fMute->SetEnabled(buttons & VOLUME_ENABLED); 372 373 if (fSkipBack) 374 fSkipBack->SetEnabled(buttons & SKIP_BACK_ENABLED); 375 if (fSkipForward) 376 fSkipForward->SetEnabled(buttons & SKIP_FORWARD_ENABLED); 377 if (fRewind) 378 fRewind->SetEnabled(buttons & SEEK_BACK_ENABLED); 379 if (fForward) 380 fForward->SetEnabled(buttons & SEEK_FORWARD_ENABLED); 381 382 fPlayPause->SetEnabled(buttons & PLAYBACK_ENABLED); 383 fStop->SetEnabled(buttons & PLAYBACK_ENABLED); 384 385 UnlockLooper(); 386 } 387 388 389 // #pragma mark - 390 391 392 void 393 TransportControlGroup::SetPlaybackState(uint32 state) 394 { 395 if (!LockLooper()) 396 return; 397 398 switch (state) { 399 case PLAYBACK_STATE_PLAYING: 400 fPlayPause->SetPlaying(); 401 break; 402 case PLAYBACK_STATE_PAUSED: 403 fPlayPause->SetPaused(); 404 break; 405 case PLAYBACK_STATE_STOPPED: 406 fPlayPause->SetStopped(); 407 break; 408 } 409 410 UnlockLooper(); 411 } 412 413 414 void 415 TransportControlGroup::SetSkippable(bool backward, bool forward) 416 { 417 if (!LockLooper()) 418 return; 419 420 if (fSkipBack) 421 fSkipBack->SetEnabled(backward); 422 if (fSkipForward) 423 fSkipForward->SetEnabled(forward); 424 425 UnlockLooper(); 426 } 427 428 429 // #pragma mark - 430 431 432 void 433 TransportControlGroup::SetAudioEnabled(bool enabled) 434 { 435 if (!LockLooper()) 436 return; 437 438 fMute->SetEnabled(enabled); 439 fVolumeSlider->SetEnabled(enabled); 440 441 UnlockLooper(); 442 } 443 444 445 void 446 TransportControlGroup::SetMuted(bool mute) 447 { 448 if (!LockLooper()) 449 return; 450 451 fVolumeSlider->SetMuted(mute); 452 453 UnlockLooper(); 454 } 455 456 457 void 458 TransportControlGroup::SetVolume(float value) 459 { 460 float db = _GainToDb(value); 461 float exponential = _LinearToExponential(db); 462 float gain = _DbToGain(exponential); 463 int32 pos = (int32)(floorf(gain * kVolumeFactor + 0.5)); 464 465 fVolumeSlider->SetValue(pos); 466 } 467 468 469 void 470 TransportControlGroup::SetAudioChannelCount(int32 count) 471 { 472 fPeakView->SetChannelCount(count); 473 } 474 475 476 void 477 TransportControlGroup::SetPosition(float value, bigtime_t position, 478 bigtime_t duration) 479 { 480 fPositionToolTip->Update(position, duration); 481 fDurationView->Update(position, duration); 482 483 if (fSeekSlider->IsTracking()) 484 return; 485 486 fSeekSlider->SetPosition(value); 487 } 488 489 490 float 491 TransportControlGroup::Position() const 492 { 493 return fSeekSlider->Position(); 494 } 495 496 497 void 498 TransportControlGroup::SetDisabledString(const char* string) 499 { 500 fSeekSlider->SetDisabledString(string); 501 } 502 503 504 void 505 TransportControlGroup::SetSymbolScale(float scale) 506 { 507 if (scale == fSymbolScale) 508 return; 509 510 fSymbolScale = scale; 511 512 if (fSeekSlider != NULL) 513 fSeekSlider->SetSymbolScale(scale); 514 if (fVolumeSlider != NULL) { 515 fVolumeSlider->SetBarThickness(fVolumeSlider->PreferredBarThickness() 516 * scale); 517 } 518 if (fDurationView != NULL) 519 fDurationView->SetSymbolScale(scale); 520 521 float symbolHeight = int(scale * be_plain_font->Size() / 1.33) | 1; 522 523 if (fSkipBack != NULL) 524 fSkipBack->SetSymbol(_CreateSkipBackwardsShape(symbolHeight)); 525 if (fSkipForward != NULL) 526 fSkipForward->SetSymbol(_CreateSkipForwardShape(symbolHeight)); 527 if (fRewind != NULL) 528 fRewind->SetSymbol(_CreateRewindShape(symbolHeight)); 529 if (fForward != NULL) 530 fForward->SetSymbol(_CreateForwardShape(symbolHeight)); 531 if (fPlayPause != NULL) { 532 fPlayPause->SetSymbols(_CreatePlayShape(symbolHeight), 533 _CreatePauseShape(symbolHeight)); 534 } 535 if (fStop != NULL) 536 fStop->SetSymbol(_CreateStopShape(symbolHeight)); 537 if (fMute != NULL) 538 fMute->SetSymbol(_CreateSpeakerShape(floorf(symbolHeight * 0.9))); 539 540 // Figure out the visual insets of the slider bounds towards the slider 541 // bar, and use that as insets for the rest of the layout. 542 float barInset = fSeekSlider->BarFrame().left; 543 float inset = barInset * scale; 544 float hInset = inset - fSeekSlider->BarFrame().top; 545 if (hInset < 0.0f) 546 hInset = 0.0f; 547 548 fSeekLayout->SetInsets(inset - barInset, hInset, inset, 0); 549 fSeekLayout->SetSpacing(inset - barInset); 550 fControlLayout->SetInsets(inset, hInset, inset, inset); 551 fControlLayout->SetSpacing(inset - barInset); 552 553 ResizeTo(Bounds().Width(), GroupLayout()->MinSize().height); 554 } 555 556 // #pragma mark - 557 558 559 void 560 TransportControlGroup::_TogglePlaying() 561 { 562 TogglePlaying(); 563 } 564 565 566 void 567 TransportControlGroup::_Stop() 568 { 569 fPlayPause->SetStopped(); 570 Stop(); 571 } 572 573 574 void 575 TransportControlGroup::_Rewind() 576 { 577 Rewind(); 578 } 579 580 581 void 582 TransportControlGroup::_Forward() 583 { 584 Forward(); 585 } 586 587 588 void 589 TransportControlGroup::_SkipBackward() 590 { 591 SkipBackward(); 592 } 593 594 595 void 596 TransportControlGroup::_SkipForward() 597 { 598 SkipForward(); 599 } 600 601 602 void 603 TransportControlGroup::_UpdateVolume() 604 { 605 float pos = fVolumeSlider->Value() / (float)kVolumeFactor; 606 float db = _ExponentialToLinear(_GainToDb(pos)); 607 float gain = _DbToGain(db); 608 VolumeChanged(gain); 609 } 610 611 612 void 613 TransportControlGroup::_ToggleMute() 614 { 615 fVolumeSlider->SetMuted(!fVolumeSlider->IsMuted()); 616 ToggleMute(); 617 } 618 619 620 void 621 TransportControlGroup::_UpdatePosition() 622 { 623 PositionChanged(fSeekSlider->Value() / (float)kPositionFactor); 624 } 625 626 627 // #pragma mark - 628 629 630 BShape* 631 TransportControlGroup::_CreateSkipBackwardsShape(float height) const 632 { 633 BShape* shape = new BShape(); 634 635 float stopWidth = ceilf(height / 6); 636 637 shape->MoveTo(BPoint(-stopWidth, height)); 638 shape->LineTo(BPoint(0, height)); 639 shape->LineTo(BPoint(0, 0)); 640 shape->LineTo(BPoint(-stopWidth, 0)); 641 shape->Close(); 642 643 shape->MoveTo(BPoint(0, height / 2)); 644 shape->LineTo(BPoint(height, height)); 645 shape->LineTo(BPoint(height, 0)); 646 shape->Close(); 647 648 shape->MoveTo(BPoint(height, height / 2)); 649 shape->LineTo(BPoint(height * 2, height)); 650 shape->LineTo(BPoint(height * 2, 0)); 651 shape->Close(); 652 653 return shape; 654 } 655 656 657 BShape* 658 TransportControlGroup::_CreateSkipForwardShape(float height) const 659 { 660 BShape* shape = new BShape(); 661 662 shape->MoveTo(BPoint(height, height / 2)); 663 shape->LineTo(BPoint(0, height)); 664 shape->LineTo(BPoint(0, 0)); 665 shape->Close(); 666 667 shape->MoveTo(BPoint(height * 2, height / 2)); 668 shape->LineTo(BPoint(height, height)); 669 shape->LineTo(BPoint(height, 0)); 670 shape->Close(); 671 672 float stopWidth = ceilf(height / 6); 673 674 shape->MoveTo(BPoint(height * 2, height)); 675 shape->LineTo(BPoint(height * 2 + stopWidth, height)); 676 shape->LineTo(BPoint(height * 2 + stopWidth, 0)); 677 shape->LineTo(BPoint(height * 2, 0)); 678 shape->Close(); 679 680 return shape; 681 } 682 683 684 BShape* 685 TransportControlGroup::_CreateRewindShape(float height) const 686 { 687 BShape* shape = new BShape(); 688 689 shape->MoveTo(BPoint(0, height / 2)); 690 shape->LineTo(BPoint(height, height)); 691 shape->LineTo(BPoint(height, 0)); 692 shape->Close(); 693 694 shape->MoveTo(BPoint(height, height / 2)); 695 shape->LineTo(BPoint(height * 2, height)); 696 shape->LineTo(BPoint(height * 2, 0)); 697 shape->Close(); 698 699 return shape; 700 } 701 702 703 BShape* 704 TransportControlGroup::_CreateForwardShape(float height) const 705 { 706 BShape* shape = new BShape(); 707 708 shape->MoveTo(BPoint(height, height / 2)); 709 shape->LineTo(BPoint(0, height)); 710 shape->LineTo(BPoint(0, 0)); 711 shape->Close(); 712 713 shape->MoveTo(BPoint(height * 2, height / 2)); 714 shape->LineTo(BPoint(height, height)); 715 shape->LineTo(BPoint(height, 0)); 716 shape->Close(); 717 718 return shape; 719 } 720 721 722 BShape* 723 TransportControlGroup::_CreatePlayShape(float height) const 724 { 725 BShape* shape = new BShape(); 726 727 float step = floorf(height / 8); 728 729 shape->MoveTo(BPoint(height + step, height / 2)); 730 shape->LineTo(BPoint(-step, height + step)); 731 shape->LineTo(BPoint(-step, 0 - step)); 732 shape->Close(); 733 734 return shape; 735 } 736 737 738 BShape* 739 TransportControlGroup::_CreatePauseShape(float height) const 740 { 741 BShape* shape = new BShape(); 742 743 float stemWidth = floorf(height / 3); 744 745 shape->MoveTo(BPoint(0, height)); 746 shape->LineTo(BPoint(stemWidth, height)); 747 shape->LineTo(BPoint(stemWidth, 0)); 748 shape->LineTo(BPoint(0, 0)); 749 shape->Close(); 750 751 shape->MoveTo(BPoint(height - stemWidth, height)); 752 shape->LineTo(BPoint(height, height)); 753 shape->LineTo(BPoint(height, 0)); 754 shape->LineTo(BPoint(height - stemWidth, 0)); 755 shape->Close(); 756 757 return shape; 758 } 759 760 761 BShape* 762 TransportControlGroup::_CreateStopShape(float height) const 763 { 764 BShape* shape = new BShape(); 765 766 shape->MoveTo(BPoint(0, height)); 767 shape->LineTo(BPoint(height, height)); 768 shape->LineTo(BPoint(height, 0)); 769 shape->LineTo(BPoint(0, 0)); 770 shape->Close(); 771 772 return shape; 773 } 774 775 776 static void 777 add_bow(BShape* shape, float offset, float size, float height, float step) 778 { 779 float width = floorf(size * 2 / 3); 780 float outerControlHeight = size * 2 / 3; 781 float outerControlWidth = size / 4; 782 float innerControlHeight = size / 2; 783 float innerControlWidth = size / 5; 784 // left/bottom 785 shape->MoveTo(BPoint(offset, height / 2 + size)); 786 // outer bow, to middle 787 shape->BezierTo( 788 BPoint(offset + outerControlWidth, height / 2 + size), 789 BPoint(offset + width, height / 2 + outerControlHeight), 790 BPoint(offset + width, height / 2) 791 ); 792 // outer bow, to left/top 793 shape->BezierTo( 794 BPoint(offset + width, height / 2 - outerControlHeight), 795 BPoint(offset + outerControlWidth, height / 2 - size), 796 BPoint(offset, height / 2 - size) 797 ); 798 // inner bow, to middle 799 shape->BezierTo( 800 BPoint(offset + innerControlWidth, height / 2 - size), 801 BPoint(offset + width - step, height / 2 - innerControlHeight), 802 BPoint(offset + width - step, height / 2) 803 ); 804 // inner bow, back to left/bottom 805 shape->BezierTo( 806 BPoint(offset + width - step, height / 2 + innerControlHeight), 807 BPoint(offset + innerControlWidth, height / 2 + size), 808 BPoint(offset, height / 2 + size) 809 ); 810 shape->Close(); 811 } 812 813 814 BShape* 815 TransportControlGroup::_CreateSpeakerShape(float height) const 816 { 817 BShape* shape = new BShape(); 818 819 float step = floorf(height / 8); 820 float magnetWidth = floorf(height / 5); 821 float chassieWidth = floorf(height / 1.5); 822 float chassieHeight = floorf(height / 4); 823 824 shape->MoveTo(BPoint(0, height - step)); 825 shape->LineTo(BPoint(magnetWidth, height - step)); 826 shape->LineTo(BPoint(magnetWidth, height / 2 + chassieHeight)); 827 shape->LineTo(BPoint(magnetWidth + chassieWidth - step, height + step)); 828 shape->LineTo(BPoint(magnetWidth + chassieWidth, height + step)); 829 shape->LineTo(BPoint(magnetWidth + chassieWidth, -step)); 830 shape->LineTo(BPoint(magnetWidth + chassieWidth - step, -step)); 831 shape->LineTo(BPoint(magnetWidth, height / 2 - chassieHeight)); 832 shape->LineTo(BPoint(magnetWidth, step)); 833 shape->LineTo(BPoint(0, step)); 834 shape->Close(); 835 836 float offset = magnetWidth + chassieWidth + step * 2; 837 add_bow(shape, offset, 3 * step, height, step * 2); 838 offset += step * 2; 839 add_bow(shape, offset, 5 * step, height, step * 2); 840 offset += step * 2; 841 add_bow(shape, offset, 7 * step, height, step * 2); 842 843 return shape; 844 } 845 846