Files
ApfelTeeSaft eee7f21af5 add loop toggle
Added a Toggle to the Controls that allows the User to Loop the currently playing song
2024-12-22 16:08:37 +01:00

845 lines
27 KiB
C++

// The UI was 99% AI generated.
#include <wx/wx.h>
#include <wx/listbox.h>
#include <wx/scrolbar.h>
#include <wx/graphics.h>
#include <wx/dcbuffer.h>
#include <filesystem>
#include <vector>
#include <string>
#include <thread>
#include "PlayerFunctionality.h"
#include "resource.h"
#include "VersionNum.h"
#ifdef __WXMSW__
#include <wx/msw/private.h>
#include <windows.h>
#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")
#endif
class DarkPanel : public wxPanel {
public:
DarkPanel(wxWindow* parent, wxWindowID id = wxID_ANY)
: wxPanel(parent, id)
{
SetBackgroundColour(wxColour(35, 35, 35)); // Slightly lighter than main background
Bind(wxEVT_PAINT, &DarkPanel::OnPaint, this);
}
private:
void OnPaint(wxPaintEvent& evt) {
wxPaintDC dc(this);
wxSize size = GetSize();
// Draw the border
dc.SetPen(wxPen(wxColour(60, 60, 60), 1)); // Subtle border color
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRoundedRectangle(0, 0, size.GetWidth(), size.GetHeight(), 4);
}
};
class DarkListBox : public wxListBox {
public:
DarkListBox(wxWindow* parent, wxWindowID id)
: wxListBox(parent, id, wxDefaultPosition, wxDefaultSize,
0, NULL, wxLB_SINGLE | wxNO_BORDER | wxLB_NO_SB)
{
SetBackgroundColour(wxColour(40, 40, 40));
SetForegroundColour(wxColour(200, 200, 200));
Bind(wxEVT_MOUSEWHEEL, &DarkListBox::OnMouseWheel, this);
}
protected:
void OnMouseWheel(wxMouseEvent& event) {
int nScrollLines = 3;
if (event.GetWheelRotation() > 0) {
for (int i = 0; i < nScrollLines; i++) {
ScrollLines(-1);
}
}
else {
for (int i = 0; i < nScrollLines; i++) {
ScrollLines(1);
}
}
}
};
class MainFrame;
// Custom Button with dark theme
class DarkButton : public wxButton {
public:
DarkButton(wxWindow* parent, wxWindowID id, const wxString& label)
: wxButton(parent, id, label, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxNO_BORDER)
, m_isHovered(false)
, m_isPressed(false)
, m_cornerRadius(6)
{
SetBackgroundStyle(wxBG_STYLE_PAINT);
SetBackgroundColour(wxColour(60, 60, 60));
SetForegroundColour(wxColour(200, 200, 200));
// Bind all relevant events
Bind(wxEVT_PAINT, &DarkButton::OnPaint, this);
Bind(wxEVT_ENTER_WINDOW, &DarkButton::OnEnter, this);
Bind(wxEVT_LEAVE_WINDOW, &DarkButton::OnLeave, this);
Bind(wxEVT_LEFT_DOWN, &DarkButton::OnMouseDown, this);
Bind(wxEVT_LEFT_UP, &DarkButton::OnMouseUp, this);
Bind(wxEVT_ERASE_BACKGROUND, &DarkButton::OnEraseBackground, this);
Bind(wxEVT_BUTTON, &DarkButton::OnButtonClick, this);
SetMinSize(wxSize(60, 30));
}
void ForceRefresh() {
m_isPressed = false;
m_isHovered = false;
Refresh(false);
Update();
}
protected:
void OnEraseBackground(wxEraseEvent& event) {
// Do nothing to prevent flickering
}
void OnPaint(wxPaintEvent& event) {
wxBufferedPaintDC dc(this);
wxSize size = GetSize();
wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
if (gc) {
// Clear the background
gc->SetBrush(GetParent()->GetBackgroundColour());
gc->SetPen(*wxTRANSPARENT_PEN);
gc->DrawRectangle(0, 0, size.GetWidth(), size.GetHeight());
// Determine button color based on all states
wxColour bgColor;
if (GetBackgroundColour() == wxColour(147, 112, 219)) {
// Special color for selected speed buttons
if (m_isPressed) {
bgColor = wxColour(137, 102, 209); // Darker when pressed
}
else if (m_isHovered) {
bgColor = wxColour(157, 122, 229); // Lighter when hovered
}
else {
bgColor = wxColour(147, 112, 219); // Normal
}
}
else {
// Regular button colors
if (m_isPressed) {
bgColor = wxColour(50, 50, 50); // Darker when pressed
}
else if (m_isHovered) {
bgColor = wxColour(80, 80, 80); // Lighter when hovered
}
else {
bgColor = wxColour(60, 60, 60); // Normal
}
}
// Draw the rounded button
gc->SetBrush(wxBrush(bgColor));
gc->SetPen(*wxTRANSPARENT_PEN);
wxGraphicsPath path = gc->CreatePath();
path.AddRoundedRectangle(0, 0, size.GetWidth(), size.GetHeight(), m_cornerRadius);
gc->FillPath(path);
// Draw text
gc->SetFont(GetFont(), GetForegroundColour());
wxString label = GetLabel();
double textWidth, textHeight;
gc->GetTextExtent(label, &textWidth, &textHeight);
double x = (size.GetWidth() - textWidth) / 2;
double y = (size.GetHeight() - textHeight) / 2;
// Slight offset when pressed for a "pressed" effect
if (m_isPressed) {
x += 1;
y += 1;
}
gc->DrawText(label, x, y);
delete gc;
}
}
void OnMouseDown(wxMouseEvent& event) {
m_isPressed = true;
Refresh();
event.Skip();
}
void OnMouseUp(wxMouseEvent& event) {
m_isPressed = false;
Refresh();
event.Skip();
}
void OnButtonClick(wxCommandEvent& event) {
RefreshAllButtonsInWindow(GetParent());
event.Skip();
}
void OnEnter(wxMouseEvent& event) {
m_isHovered = true;
Refresh();
event.Skip();
}
void OnLeave(wxMouseEvent& event) {
m_isHovered = false;
m_isPressed = false; // Reset pressed state when mouse leaves
Refresh();
event.Skip();
}
private:
void RefreshAllButtonsInWindow(wxWindow* window) {
// Refresh buttons in the current window
wxWindowList& children = window->GetChildren();
for (wxWindow* child : children) {
DarkButton* button = dynamic_cast<DarkButton*>(child);
if (button) {
button->ForceRefresh();
}
// Recursively check children windows
RefreshAllButtonsInWindow(child);
}
}
bool m_isHovered;
bool m_isPressed;
int m_cornerRadius;
};
class CustomProgressBar;
wxDEFINE_EVENT(wxEVT_PROGRESS_SEEK, wxCommandEvent);
// Custom progress bar class
class CustomProgressBar : public wxControl {
public:
CustomProgressBar(wxWindow* parent, wxWindowID id, int range = 100)
: wxControl(parent, id, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE)
, m_range(range)
, m_value(0)
, m_isHovering(false)
{
SetBackgroundColour(wxColour(40, 40, 40));
SetMinSize(wxSize(-1, 12));
Bind(wxEVT_PAINT, &CustomProgressBar::OnPaint, this);
Bind(wxEVT_LEFT_DOWN, &CustomProgressBar::OnMouseClick, this);
Bind(wxEVT_MOTION, &CustomProgressBar::OnMouseMove, this);
Bind(wxEVT_ENTER_WINDOW, &CustomProgressBar::OnMouseEnter, this);
Bind(wxEVT_LEAVE_WINDOW, &CustomProgressBar::OnMouseLeave, this);
// Create cursor
SetCursor(wxCursor(wxCURSOR_HAND));
}
void SetValue(int value) {
m_value = value;
Refresh();
}
void SetRange(int range) {
m_range = range;
Refresh();
}
wxDECLARE_EVENT_TABLE();
wxDECLARE_DYNAMIC_CLASS(CustomProgressBar);
private:
void OnPaint(wxPaintEvent& evt) {
wxPaintDC dc(this);
wxSize size = GetSize();
// Draw background
dc.SetBrush(wxBrush(GetBackgroundColour()));
dc.SetPen(*wxTRANSPARENT_PEN);
dc.DrawRectangle(0, 0, size.GetWidth(), size.GetHeight());
if (m_range > 0) {
// Calculate progress width
int progressWidth = (size.GetWidth() * m_value) / m_range;
// Create gradient colors
wxColour startColor(147, 112, 219);
wxColour endColor(122, 93, 182);
if (m_isHovering) {
// Brighten colors when hovering
startColor = startColor.ChangeLightness(110);
endColor = endColor.ChangeLightness(110);
}
// Draw progress with gradient
if (progressWidth > 0) {
wxRect progressRect(0, 0, progressWidth, size.GetHeight());
dc.GradientFillLinear(progressRect, startColor, endColor, wxEAST);
}
// Draw hover position indicator
if (m_isHovering) {
dc.SetPen(wxPen(wxColour(255, 255, 255, 180), 2));
dc.DrawLine(m_hoverX, 0, m_hoverX, size.GetHeight());
}
}
}
void OnMouseClick(wxMouseEvent& event) {
if (m_range > 0) {
int newValue = (event.GetX() * m_range) / GetSize().GetWidth();
newValue = wxMin(wxMax(newValue, 0), m_range);
// Create and send a custom seek event
wxCommandEvent seekEvent(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
seekEvent.SetInt(newValue);
seekEvent.SetEventObject(this);
ProcessEvent(seekEvent);
}
}
void OnMouseMove(wxMouseEvent& event) {
if (m_isHovering) {
m_hoverX = event.GetX();
Refresh();
}
}
void OnMouseEnter(wxMouseEvent& event) {
m_isHovering = true;
m_hoverX = event.GetX();
Refresh();
}
void OnMouseLeave(wxMouseEvent& event) {
m_isHovering = false;
Refresh();
}
int m_range;
int m_value;
bool m_isHovering;
int m_hoverX;
};
IMPLEMENT_CLASS(CustomProgressBar, wxControl)
wxBEGIN_EVENT_TABLE(CustomProgressBar, wxControl)
EVT_PAINT(CustomProgressBar::OnPaint)
EVT_LEFT_DOWN(CustomProgressBar::OnMouseClick)
EVT_MOTION(CustomProgressBar::OnMouseMove)
EVT_ENTER_WINDOW(CustomProgressBar::OnMouseEnter)
EVT_LEAVE_WINDOW(CustomProgressBar::OnMouseLeave)
wxEND_EVENT_TABLE()
class DarkSearchBox : public wxTextCtrl {
public:
DarkSearchBox(wxWindow* parent, wxWindowID id)
: wxTextCtrl(parent, id, wxEmptyString, wxDefaultPosition, wxDefaultSize,
wxTE_PROCESS_ENTER | wxBORDER_NONE)
{
SetBackgroundColour(wxColour(50, 50, 50));
SetForegroundColour(wxColour(200, 200, 200));
// Set placeholder text color
SetHint("Search songs...");
// Custom font
wxFont customFont(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
wxFONTWEIGHT_NORMAL, false, "Segoe UI");
SetFont(customFont);
}
};
class MainFrame : public wxFrame {
public:
MainFrame() : wxFrame(nullptr, wxID_ANY, "Guitar Player v" + std::string(VERSIONSTR),
wxDefaultPosition, wxSize(300, 500),
wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN),
isPlayingSong(false), isPaused(false), currentProgress(0)
{
// Enable dark title bar for Windows
#ifdef __WXMSW__
HWND hwnd = (HWND)GetHandle();
if (hwnd) { // Enable dark mode for title bar
BOOL value = TRUE;
DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
HICON hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1));
if (hIcon)
{
// Set the small icon (16x16)
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
// Set the big icon (32x32)
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
}
}
#endif
SetBackgroundColour(wxColour(30, 30, 30));
// Create main sizer
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
DarkPanel* groupPanelSongs = new DarkPanel(this);
wxBoxSizer* groupSizerSongs = new wxBoxSizer(wxVERTICAL);
wxBoxSizer* searchSizer = new wxBoxSizer(wxHORIZONTAL);
searchBox = new DarkSearchBox(groupPanelSongs, wxID_ANY);
refreshButton = new DarkButton(groupPanelSongs, wxID_ANY, "Refresh");
// Add search box and refresh button to the horizontal sizer
searchSizer->Add(searchBox, 1, wxEXPAND | wxRIGHT, 5);
searchSizer->Add(refreshButton, 0, wxEXPAND);
listBox = new DarkListBox(groupPanelSongs, wxID_ANY);
LoadSongs("songs");
groupSizerSongs->Add(searchSizer, 0, wxEXPAND | wxALL, 10);
groupSizerSongs->Add(listBox, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
groupPanelSongs->SetSizer(groupSizerSongs);
mainSizer->Add(groupPanelSongs, 1, wxEXPAND | wxALL, 10);
DarkPanel* playbackPanel = new DarkPanel(this);
wxBoxSizer* playbackSizer = new wxBoxSizer(wxVERTICAL);
currentSongLabel = new wxStaticText(playbackPanel, wxID_ANY, "No song selected");
currentSongLabel->SetForegroundColour(wxColour(200, 200, 200));
//currentSongLabel->SetWindowStyle(wxALIGN_CENTER_HORIZONTAL);
progressBar = new CustomProgressBar(playbackPanel, wxID_ANY);
timeLabel = new wxStaticText(playbackPanel, wxID_ANY, "0:00 / 0:00");
timeLabel->SetForegroundColour(wxColour(200, 200, 200));
wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
playButton = new DarkButton(playbackPanel, wxID_ANY, "Play");
pauseButton = new DarkButton(playbackPanel, wxID_ANY, "Pause");
stopButton = new DarkButton(playbackPanel, wxID_ANY, "Stop");
loopCheckbox = new wxCheckBox(playbackPanel, wxID_ANY, "Loop");
buttonSizer->Add(loopCheckbox, 1, wxLEFT | wxRIGHT, 5);
buttonSizer->Add(playButton, 1, wxRIGHT, 5);
buttonSizer->Add(pauseButton, 1, wxLEFT | wxRIGHT, 5);
buttonSizer->Add(stopButton, 1, wxLEFT, 5);
// Create speed controls
CreateSpeedControls(playbackPanel); // Modified to take parent parameter
playbackSizer->Add(currentSongLabel, 0, wxEXPAND | wxALL, 10);
playbackSizer->Add(progressBar, 0, wxEXPAND | wxLEFT | wxRIGHT, 10);
playbackSizer->Add(timeLabel, 0, wxALIGN_CENTER | wxALL, 5);
playbackSizer->Add(buttonSizer, 0, wxEXPAND | wxALL, 10);
playbackSizer->Add(speedSizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
playbackPanel->SetSizer(playbackSizer);
mainSizer->Add(playbackPanel, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
SetSizer(mainSizer);
// Bind events
searchBox->Bind(wxEVT_TEXT, &MainFrame::OnSearchText, this);
searchBox->Bind(wxEVT_TEXT_ENTER, &MainFrame::OnSearchEnter, this);
playButton->Bind(wxEVT_BUTTON, &MainFrame::OnPlay, this);
pauseButton->Bind(wxEVT_BUTTON, &MainFrame::OnPause, this);
stopButton->Bind(wxEVT_BUTTON, &MainFrame::OnStop, this);
loopCheckbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) {
isLooping = loopCheckbox->GetValue();
});
listBox->Bind(wxEVT_LISTBOX, &MainFrame::OnSongSelect, this);
progressBar->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MainFrame::OnSeek, this);
refreshButton->Bind(wxEVT_BUTTON, &MainFrame::OnRefresh, this);
// Create timer for progress updates
progressTimer = new wxTimer(this);
Bind(wxEVT_TIMER, &MainFrame::OnProgressTimer, this);
// Set custom font
wxFont customFont(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
wxFONTWEIGHT_NORMAL, false, "Segoe UI");
listBox->SetFont(customFont);
playButton->SetFont(customFont);
pauseButton->SetFont(customFont);
stopButton->SetFont(customFont);
timeLabel->SetFont(customFont);
currentSongLabel->SetFont(customFont);
refreshButton->SetFont(customFont);
// Bind the close event
Bind(wxEVT_CLOSE_WINDOW, &MainFrame::OnClose, this);
// Initialize button states
UpdateButtonStates();
}
~MainFrame() {
StopPlayback();
delete progressTimer;
}
void RefreshAllButtons() {
// Refresh all buttons in the frame and its children
RefreshButtonsInWindow(this);
}
void OnRefresh(wxCommandEvent& event) {
wxString currentSearch = searchBox->GetValue();
LoadSongs("songs");
if (!currentSearch.IsEmpty()) {
FilterSongs(currentSearch);
}
RefreshAllButtons();
}
private:
void RefreshButtonsInWindow(wxWindow* window) {
// Refresh buttons in the current window
wxWindowList& children = window->GetChildren();
for (wxWindow* child : children) {
// Check if the child is a DarkButton
DarkButton* button = dynamic_cast<DarkButton*>(child);
if (button) {
button->ForceRefresh();
}
// Recursively check children windows
RefreshButtonsInWindow(child);
}
}
void CreateSpeedControls(wxWindow* parent) {
speedSizer = new wxBoxSizer(wxHORIZONTAL);
std::vector<double> speeds = { 0.5, 0.75, 1.0, 1.25, 1.5 };
for (size_t i = 0; i < speeds.size(); i++) {
double speed = speeds[i];
wxString label = wxString::Format("%.2fx", speed);
DarkButton* speedBtn = new DarkButton(parent, wxID_ANY, label);
speedBtn->SetMinSize(wxSize(41, 25));
if (speed == 1.0) {
speedBtn->SetBackgroundColour(wxColour(147, 112, 219));
}
speedBtn->Bind(wxEVT_BUTTON, [this, speed, speedBtn](wxCommandEvent& event) {
OnSpeedChange(speed, speedBtn);
});
// Add first button
if (i == 0) {
speedSizer->Add(speedBtn, 0, wxRIGHT, 5);
}
// Add middle buttons
else if (i < speeds.size() - 1) {
speedSizer->Add(speedBtn, 0, wxLEFT | wxRIGHT, 5);
}
// Add last button
else {
speedSizer->Add(speedBtn, 0, wxLEFT, 5);
}
speedButtons.push_back(speedBtn);
}
}
void OnSpeedChange(double newSpeed, DarkButton* clickedButton) {
playbackSpeed = newSpeed;
// Update button appearances
for (DarkButton* btn : speedButtons) {
if (btn == clickedButton) {
btn->SetBackgroundColour(wxColour(147, 112, 219));
}
else {
btn->SetBackgroundColour(wxColour(60, 60, 60));
}
btn->Refresh();
}
RefreshAllButtons();
}
void OnSeek(wxCommandEvent& event) {
if (isPlayingSong && totalDuration > 0) {
int seekPercentage = event.GetInt();
int seekPosition = (seekPercentage * totalDuration) / 100;
// Set seeking flag and update progress
isSeeking = true;
currentProgress = seekPosition;
// Update UI immediately
progressBar->SetValue(seekPercentage);
int currentSeconds = currentProgress / 1000;
int totalSeconds = totalDuration / 1000;
wxString timeStr = wxString::Format("%d:%02d / %d:%02d",
currentSeconds / 60, currentSeconds % 60,
totalSeconds / 60, totalSeconds % 60);
timeLabel->SetLabel(timeStr);
}
}
void FilterSongs(const wxString& searchTerm) {
listBox->Clear();
wxString lowerSearchTerm = searchTerm.Lower();
for (const wxString& song : allSongs) {
if (song.Lower().Contains(lowerSearchTerm)) {
listBox->Append(song);
}
}
}
void OnSearchText(wxCommandEvent& event) {
FilterSongs(searchBox->GetValue());
}
void OnSearchEnter(wxCommandEvent& event) {
FilterSongs(searchBox->GetValue());
// If there's exactly one match, select it
if (listBox->GetCount() == 1) {
listBox->SetSelection(0);
wxCommandEvent selEvent(wxEVT_LISTBOX);
selEvent.SetInt(0);
OnSongSelect(selEvent);
}
}
void LoadSongs(const std::string& folder) {
listBox->Clear();
allSongs.clear();
try {
const std::filesystem::path songPath(folder);
for (const auto& entry : std::filesystem::directory_iterator(songPath)) {
if (entry.is_regular_file()) {
// Convert to wstring then to wxString to handle Unicode properly
wxString filename(entry.path().filename().wstring());
listBox->Append(filename);
allSongs.push_back(filename);
}
}
}
catch (const std::filesystem::filesystem_error& e) {
wxMessageBox("Error loading songs", "Error", wxOK | wxICON_ERROR);
}
}
void OnSongSelect(wxCommandEvent& event) {
// Update selected song filename
selectedSongFileName = listBox->GetString(listBox->GetSelection()).ToStdString();
event.Skip();
}
std::string GetFullPath(const std::string& filename) {
return std::filesystem::path("songs").append(filename).string();
}
void OnPlay(wxCommandEvent& event) {
if (listBox->GetSelection() != wxNOT_FOUND) {
if (isPaused) {
isPaused = false;
isPlayingSong = true;
UpdateButtonStates();
ResumePlayback();
}
else if (!isPlayingSong) {
wxString filename = listBox->GetString(listBox->GetSelection());
std::filesystem::path songPath("songs");
songPath /= filename.ToStdWstring();
//selectedSongFileName = "songs/" + filename.utf8_string();
//std::string filePath = songPath.string();
if (!std::filesystem::exists(songPath)) {
wxMessageBox("Cannot find file: " + filename, "Error", wxOK | wxICON_ERROR);
return;
}
currentSongLabel->SetLabel(filename);
StopPlayback();
isPlayingSong = true;
isPaused = false;
currentProgress = 0;
progressTimer->Start(100);
if (songPlayerThread.joinable()) {
songPlayerThread.join();
}
songPlayerThread = std::thread(PlaySong, songPath,
std::ref(isPlayingSong), std::ref(isPaused),
std::ref(currentProgress), std::ref(totalDuration),
std::ref(playbackSpeed));
UpdateButtonStates();
}
}
RefreshAllButtons();
}
void OnPause(wxCommandEvent& event) {
if (isPlayingSong && !isPaused) {
isPaused = true;
UpdateButtonStates();
}
RefreshAllButtons();
}
void OnStop(wxCommandEvent& event) {
StopPlayback();
currentProgress = 0;
progressBar->SetValue(0);
timeLabel->SetLabel("0:00 / 0:00");
currentSongLabel->SetLabel("No song selected");
UpdateButtonStates();
RefreshAllButtons();
}
void StopPlayback() {
if (isPlayingSong) {
isPlayingSong = false;
isPaused = false;
if (songPlayerThread.joinable()) {
songPlayerThread.join();
}
progressTimer->Stop();
}
}
void ResumePlayback() {
// Implementation for resuming playback
}
// Update the OnProgressTimer function
void OnProgressTimer(wxTimerEvent& event) {
if (isPlayingSong && !isPaused && totalDuration > 0) {
int progressPercent = (currentProgress * 100) / totalDuration;
progressBar->SetValue(progressPercent);
// Update time label
int currentSeconds = currentProgress / 1000;
int totalSeconds = totalDuration / 1000;
wxString timeStr = wxString::Format("%d:%02d / %d:%02d",
currentSeconds / 60, currentSeconds % 60,
totalSeconds / 60, totalSeconds % 60);
timeLabel->SetLabel(timeStr);
}
}
void UpdateButtonStates() {
// Update enabled states
playButton->Enable(!isPlayingSong || isPaused);
pauseButton->Enable(isPlayingSong && !isPaused);
stopButton->Enable(isPlayingSong || isPaused);
loopCheckbox->SetValue(isLooping); // Sync checkbox state with current loop status
// Update button colors based on current state
wxColour activeColor(147, 112, 219); // Purple highlight color
wxColour inactiveColor(60, 60, 60); // Regular button color
if (isPlayingSong && !isPaused) {
// Playing state
playButton->SetBackgroundColour(activeColor);
pauseButton->SetBackgroundColour(inactiveColor);
}
else if (isPlayingSong && isPaused) {
// Paused state
pauseButton->SetBackgroundColour(activeColor);
playButton->SetBackgroundColour(inactiveColor);
}
else {
// Stopped state
playButton->SetBackgroundColour(inactiveColor);
pauseButton->SetBackgroundColour(inactiveColor);
}
stopButton->SetBackgroundColour(inactiveColor);
// Force refresh all buttons to ensure proper appearance
playButton->ForceRefresh();
pauseButton->ForceRefresh();
stopButton->ForceRefresh();
}
void OnClose(wxCloseEvent& event) {
// Ensure clean shutdown
StopPlayback();
event.Skip();
}
DarkListBox* listBox;
DarkButton* playButton;
DarkButton* pauseButton;
DarkButton* stopButton;
wxCheckBox* loopCheckbox;
DarkButton* refreshButton;
CustomProgressBar* progressBar;
wxStaticText* timeLabel;
wxTimer* progressTimer;
wxStaticText* currentSongLabel;
wxBoxSizer* speedSizer;
std::vector<DarkButton*> speedButtons;
DarkSearchBox* searchBox;
std::vector<wxString> allSongs;
std::thread songPlayerThread;
bool isPlayingSong;
bool isPaused;
bool isSeeking;
std::string selectedSongFileName;
int currentProgress;
int totalDuration;
double playbackSpeed = 1.0;
};
class MusicPlayerApp : public wxApp {
public:
bool OnInit() {
// Enable dark mode for the entire application (Windows)
#ifdef __WXMSW__
typedef BOOL(WINAPI* SetPreferredAppMode)(INT);
HMODULE hUxtheme = LoadLibraryA("uxtheme.dll");
if (hUxtheme) {
SetPreferredAppMode SetAppMode =
(SetPreferredAppMode)GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135));
if (SetAppMode) {
SetAppMode(2); // 2 = Force Dark Mode
}
FreeLibrary(hUxtheme);
}
#endif
MainFrame* frame = new MainFrame();
frame->Show(true);
return true;
}
};
wxIMPLEMENT_APP(MusicPlayerApp);