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