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