From a6877e038546eaf5c9796657fbbc5fc8623b20de Mon Sep 17 00:00:00 2001 From: Jaidyn Ann Date: Sat, 4 Jun 2022 15:58:29 -0500 Subject: [PATCH] Add a volume-slider replicant (VolumeView) --- Makefile | 3 +- src/CoverView.cpp | 2 +- src/LyricsView.cpp | 2 +- src/MainWindow.cpp | 10 +++ src/MediaPlayer.cpp | 55 +++++++++++----- src/MediaPlayer.h | 7 +- src/ReplicantView.cpp | 6 +- src/ReplicantView.h | 6 +- src/VolumeView.cpp | 148 ++++++++++++++++++++++++++++++++++++++++++ src/VolumeView.h | 36 ++++++++++ 10 files changed, 249 insertions(+), 26 deletions(-) create mode 100644 src/VolumeView.cpp create mode 100644 src/VolumeView.h diff --git a/Makefile b/Makefile index a7b965a..f18fe49 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,8 @@ SRCS = src/App.cpp \ src/MediaPlayer.cpp \ src/ReplicantView.cpp \ src/Song.cpp \ - src/TextFile.cpp + src/TextFile.cpp \ + src/VolumeView.cpp # Specify the resource definition files to use. Full or relative paths can be # used. diff --git a/src/CoverView.cpp b/src/CoverView.cpp index dbf8049..14b9f72 100644 --- a/src/CoverView.cpp +++ b/src/CoverView.cpp @@ -18,7 +18,7 @@ const uint32 COVER_MAKE_SQUARE = 'cvsq'; CoverView::CoverView(BRect frame) : - ReplicantView(frame, "Cover", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE, B_FOLLOW_RIGHT) + ReplicantView(frame, "Cover", B_FOLLOW_RIGHT, B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE) { SetViewColor(B_TRANSPARENT_COLOR); fCover = NULL; diff --git a/src/LyricsView.cpp b/src/LyricsView.cpp index 4c2fa09..6bb680d 100644 --- a/src/LyricsView.cpp +++ b/src/LyricsView.cpp @@ -63,7 +63,7 @@ LyricsTextView::MouseDown(BPoint where) LyricsView::LyricsView(BRect frame) : - ReplicantView(frame, "Lyrics", 0, B_FOLLOW_LEFT) + ReplicantView(frame, "Lyrics", B_FOLLOW_LEFT) { BRect textRect(0, 0, Bounds().Width(), Bounds().Height() - 10); fTextView = new LyricsTextView(textRect, "lyricsText", textRect, diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 1f20c91..8333733 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -6,10 +6,12 @@ #include "MainWindow.h" #include +#include #include #include "CoverView.h" #include "LyricsView.h" +#include "VolumeView.h" MainWindow::MainWindow() @@ -26,9 +28,17 @@ MainWindow::MainWindow() void MainWindow::_InitInterface() { + + BView* controlsView = new BView("Controls", B_WILL_DRAW); + BLayoutBuilder::Group<>(controlsView, B_VERTICAL) + .Add(new VolumeView(BRect(BPoint(0,0), BSize(200.0, 20.0)))) + .AddGlue() + .End(); + BTabView* tabView = new BTabView("appletTabView"); tabView->AddTab(new LyricsView(BRect(BPoint(0,0), BSize(300.0, 200.0)))); tabView->AddTab(new CoverView(BRect(BPoint(0,0), BSize(300.0, 200.0)))); + tabView->AddTab(controlsView); BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f) .Add(tabView) diff --git a/src/MediaPlayer.cpp b/src/MediaPlayer.cpp index d4a9fa7..a597d1b 100644 --- a/src/MediaPlayer.cpp +++ b/src/MediaPlayer.cpp @@ -48,8 +48,8 @@ MediaPlayer::Instantiate(BMessage* data) bool MediaPlayer::IsPlaying() { - BMessage reply; - _GetResponse("IsPlaying", &reply); + BMessage send, reply; + _ScriptingCall("IsPlaying", &send, &reply); return reply.GetBool("result", false); } @@ -57,12 +57,30 @@ MediaPlayer::IsPlaying() int64 MediaPlayer::Position() { - BMessage reply; - _GetResponse("Position", &reply); + BMessage send, reply; + _ScriptingCall("Position", &send, &reply); return reply.GetInt64("result", -1); } +float +MediaPlayer::Volume() +{ + BMessage send, reply; + _ScriptingCall("Volume", &send, &reply); + return reply.GetFloat("result", -1.0); +} + + +void +MediaPlayer::SetVolume(float volume) +{ + BMessage send(B_SET_PROPERTY), reply; + send.AddFloat("data", volume); + _ScriptingCall("Volume", &send, &reply); +} + + bool MediaPlayer::CurrentItem(Song* song, bool duration) { @@ -90,8 +108,8 @@ MediaPlayer::SetWindow(int32 index) int32 MediaPlayer::CountWindows() { - BMessage reply; - _GetResponse("Window", &reply, MP_NO_TRACK, B_COUNT_PROPERTIES); + BMessage send(B_COUNT_PROPERTIES), reply; + _ScriptingCall("Window", &send, &reply, MP_NO_TRACK); int32 count; if (reply.FindInt32("result", &count) == B_OK) @@ -103,15 +121,15 @@ MediaPlayer::CountWindows() bool MediaPlayer::_GetSong(Song* song, int32 trackIndex, bool durationRequired) { - BMessage reply; - _GetResponse("URI", &reply, trackIndex); + BMessage send, reply; + _ScriptingCall("URI", &send, &reply, trackIndex); BString uriString; if (reply.FindString("result", &uriString) != B_OK) return false; int64 duration = -1; if (durationRequired) { - _GetResponse("Duration", &reply, trackIndex); + _ScriptingCall("Duration", &send, &reply, trackIndex); if (reply.FindInt64("result", &duration) != B_OK) return false; } @@ -122,17 +140,20 @@ MediaPlayer::_GetSong(Song* song, int32 trackIndex, bool durationRequired) void -MediaPlayer::_GetResponse(const char* attribute, BMessage* reply, int32 trackIndex, int32 what) +MediaPlayer::_ScriptingCall(const char* attribute, BMessage* send, BMessage* reply, int32 trackIndex) { - BMessage message; - message.what = what; - message.AddSpecifier(attribute); + if (send->what == 0) + send->what = B_GET_PROPERTY; + + send->AddSpecifier(attribute); if (trackIndex > 0) - message.AddSpecifier("PlaylistTrack", trackIndex); + send->AddSpecifier("PlaylistTrack", trackIndex); else if (trackIndex == MP_CURRENT_TRACK) - message.AddSpecifier("CurrentTrack"); - message.AddSpecifier("Window", fWindowIndex); - BMessenger("application/x-vnd.Haiku-MediaPlayer").SendMessage(&message, reply); + send->AddSpecifier("CurrentTrack"); + + send->AddSpecifier("Window", fWindowIndex); + + BMessenger("application/x-vnd.Haiku-MediaPlayer").SendMessage(send, reply); } diff --git a/src/MediaPlayer.h b/src/MediaPlayer.h index 936cbf0..660b8b1 100644 --- a/src/MediaPlayer.h +++ b/src/MediaPlayer.h @@ -30,6 +30,9 @@ public: bool IsPlaying(); int64 Position(); + float Volume(); + void SetVolume(float volume); + bool CurrentItem(Song* song, bool duration = true); bool PlaylistItem(Song* song, int32 index, bool duration = true); @@ -38,8 +41,8 @@ public: private: bool _GetSong(Song* song, int32 trackIndex, bool durationRequired = true); - void _GetResponse(const char* attribute, BMessage* reply, - int32 trackIndex = MP_NO_TRACK, int32 what = B_GET_PROPERTY); + void _ScriptingCall(const char* attribute, BMessage* send, BMessage* reply, + int32 trackIndex = MP_NO_TRACK); const char* _UriToPath(BString URI); int32 fWindowIndex; diff --git a/src/ReplicantView.cpp b/src/ReplicantView.cpp index 396ec19..dc69183 100644 --- a/src/ReplicantView.cpp +++ b/src/ReplicantView.cpp @@ -13,9 +13,11 @@ #include "MediaPlayer.h" -ReplicantView::ReplicantView(BRect frame, const char* name, uint32 flags, uint32 draggerPlacement) +ReplicantView::ReplicantView(BRect frame, const char* name, uint32 draggerPlacement, uint32 resize, + uint32 flags) : - BView(frame, name, B_FOLLOW_ALL_SIDES, B_TRANSPARENT_BACKGROUND | B_PULSE_NEEDED | flags) + BView(frame, name, resize, + B_TRANSPARENT_BACKGROUND | B_DRAW_ON_CHILDREN | B_PULSE_NEEDED | flags) { BRect dragRect(frame.Width() - 10, 0, frame.Width(), frame.Height()); uint32 dragFollow = B_FOLLOW_RIGHT; diff --git a/src/ReplicantView.h b/src/ReplicantView.h index ff3fe09..cc3d885 100644 --- a/src/ReplicantView.h +++ b/src/ReplicantView.h @@ -32,8 +32,10 @@ enum { - MediaPlayer object */ class ReplicantView : public BView { public: - ReplicantView(BRect frame, const char* name, uint32 flags, - uint32 draggerPlacement); + ReplicantView(BRect frame, const char* name, + uint32 draggerPlacement = B_FOLLOW_RIGHT, + uint32 resize = B_FOLLOW_ALL_SIDES, + uint32 flags = 0); ReplicantView(BMessage* archive); virtual status_t Archive(BMessage* data, bool deep = true) const; diff --git a/src/VolumeView.cpp b/src/VolumeView.cpp new file mode 100644 index 0000000..7cc9c6b --- /dev/null +++ b/src/VolumeView.cpp @@ -0,0 +1,148 @@ +/* + * Copyright 2022, Jaidyn Levesque + * All rights reserved. Distributed under the terms of the MIT license. + */ + +#include "VolumeView.h" + +#include + +#include + +#include "MediaPlayer.h" + + +// The same color MediaPlayer uses, interface/VolumeSlider +static const rgb_color kVolumeGreen = (rgb_color){ 116, 224, 0, 255 }; + + +VolumeView::VolumeView(BRect frame) + : + ReplicantView(frame, "Volume", B_FOLLOW_RIGHT, B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW) +{ + SetViewColor(B_TRANSPARENT_COLOR); + + fSlider = new BSlider(frame, "volumeSlider", NULL, NULL, 0, 1000, B_HORIZONTAL, B_BLOCK_THUMB, + B_FOLLOW_LEFT_RIGHT); + fSlider->UseFillColor(true, &kVolumeGreen); + fSlider->SetViewColor(B_TRANSPARENT_COLOR); + AddChild(fSlider); + + _Init(); +} + + +VolumeView::VolumeView(BMessage* data) + : + ReplicantView(data) +{ + fSlider = dynamic_cast(FindView("volumeSlider")); + _Init(); +} + + +status_t +VolumeView::Archive(BMessage* data, bool deep) const +{ + status_t status = ReplicantView::Archive(data, deep); + + data->AddString("class", "VolumeView"); + data->AddString("add_on", APP_SIGNATURE); + return status; +} + + +VolumeView* +VolumeView::Instantiate(BMessage* data) +{ + if (!validate_instantiation(data, "VolumeView")) + return NULL; + return new VolumeView(data); +} + + +void +VolumeView::MessageReceived(BMessage* msg) +{ + if (msg->what != B_MOUSE_WHEEL_CHANGED || !fSlider->IsEnabled()) { + ReplicantView::MessageReceived(msg); + return; + } + + float scroll = 0.0f; + if ((msg->FindFloat("be:wheel_delta_x", &scroll) == B_OK && scroll != 0.0f) + || (msg->FindFloat("be:wheel_delta_y", &scroll) == B_OK && scroll != 0.0f)) { + fSlider->SetPosition(fSlider->Position() + scroll * -0.03); + } +} + + +void +VolumeView::Pulse() +{ + if (fSlider->Position() != fLastPos) + fMediaPlayer->SetVolume(_PositionToVolume(fSlider->Position())); + + float volume = fMediaPlayer->Volume(); + if (volume > 0) { + SetInactive(false); + fSlider->SetPosition(_VolumeToPosition(volume)); + } else + SetInactive(true); + + fLastPos = fSlider->Position(); +} + + +void +VolumeView::SetInactive(bool inactive) +{ + fSlider->SetEnabled(!inactive); + + if (inactive && fTransparentInactivity && !fSlider->IsHidden()) + fSlider->Hide(); + else if ((!inactive || !fTransparentInactivity) && fSlider->IsHidden()) + fSlider->Show(); + + ReplicantView::SetInactive(inactive); +} + + +void +VolumeView::_Init() +{ + float height = 20.0; + fSlider->GetPreferredSize(NULL, &height); + SetExplicitMaxSize(BSize(B_SIZE_UNSET, height + 16)); + + fLastPos = 0; + Pulse(); +} + + +// The VolumeSlider for MediaPlayer isn't quite linear… I've tried my best to replicate it +// without actually tearing the guts from MediaPlayer or expending too much effort. +// It's close enough, probably… +float +VolumeView::_VolumeToPosition(float volume) +{ + if (volume > 1) + return volume / 2; + else if (volume != 0) + return (-.91 * pow(volume, 2)) + (1.3 * volume) + .06; + return 0; +} + + +// … please don't hate me :w:" +float +VolumeView::_PositionToVolume(float position) +{ + if (position > .5) + return position * 2; + else if (position != 0) { + float vol = -130 + pow(-36400 * position + 19084, .5); + return abs(vol / 182); + } + return 0; +} diff --git a/src/VolumeView.h b/src/VolumeView.h new file mode 100644 index 0000000..85b4c72 --- /dev/null +++ b/src/VolumeView.h @@ -0,0 +1,36 @@ +/* + * Copyright 2022, Jaidyn Levesque + * All rights reserved. Distributed under the terms of the MIT license. + */ +#ifndef VOLUMEVIEW_H +#define VOLUMEVIEW_H + +#include "ReplicantView.h" + +class BSlider; + + +class VolumeView : public ReplicantView { +public: + VolumeView(BRect frame); + VolumeView(BMessage* archive); + + virtual status_t Archive(BMessage* data, bool deep = true) const; + static VolumeView* Instantiate(BMessage* data); + + virtual void MessageReceived(BMessage* msg); + virtual void Pulse(); + + virtual void SetInactive(bool inactive); + +private: + void _Init(); + + float _VolumeToPosition(float volume); + float _PositionToVolume(float position); + + BSlider* fSlider; + float fLastPos; +}; + +#endif // VOLUMEVIEW_H