looper/backends/ui/haiku/main_window.cpp

398 lines
14 KiB
C++
Raw Normal View History

2024-11-12 14:53:44 -08:00
#include "main_window.h"
#include <Button.h>
#include <Slider.h>
#include <Message.h>
#include <GroupView.h>
#include <Messenger.h>
#include <MessageRunner.h>
#include <MenuBar.h>
#include <Path.h>
#include <Menu.h>
#include <MessageFilter.h>
#include <functional>
#include <util.hpp>
#include <MenuItem.h>
#include <thread>
#include <log.hpp>
#include <cmath>
#include <fmt/core.h>
#include <fmt/format.h>
2024-12-09 15:28:12 -08:00
#include <LayoutItem.h>
2024-11-12 14:53:44 -08:00
#include <SDL.h>
#include "icons.h"
#include "utils.h"
#include "aboutwindow.h"
#define CMD_MOUSE_DOWN_KEY "catmeow:pressed"
#define CMD_PAUSE_RESUME 0x10
#define CMD_RESTART 0x11
#define CMD_STOP 0x12
#define CMD_OPEN 0x13
#define CMD_ABOUT 0x14
#define CMD_PREFS 0x15
#define CMD_SEEK 0x20
#define CMD_VOLUME 0x21
#define CMD_SPEED 0x22
#define CMD_PITCH 0x23
#define CMD_TEMPO 0x24
#define CMD_UPDATE 0x80
#define SLIDER_SCALE 1000
#define D(x) ((double)(x))
std::vector<Subwindow*> Subwindow::windows;
2024-11-21 10:22:34 -08:00
static BMessage *make_slider_msg(uint32_t what, bool down) {
2024-11-12 14:53:44 -08:00
BMessage *msg = new BMessage(what);
msg->SetBool(CMD_MOUSE_DOWN_KEY, down);
2024-11-20 08:15:45 -08:00
return msg;
2024-11-12 14:53:44 -08:00
}
2024-11-21 10:22:34 -08:00
static bool is_slider_down_msg(BMessage *msg) {
2024-11-12 14:53:44 -08:00
return msg->HasBool(CMD_MOUSE_DOWN_KEY) && msg->GetBool(CMD_MOUSE_DOWN_KEY);
}
2024-11-21 10:22:34 -08:00
void HaikuLooperWindow::UpdateViewFlags(BView *view) {
2024-11-12 14:53:44 -08:00
if (view == NULL) return;
view->SetFlags(view->Flags()|B_SUPPORTS_LAYOUT|B_FRAME_EVENTS);
view->SetResizingMode(B_FOLLOW_ALL_SIDES);
}
2024-11-21 10:22:34 -08:00
void HaikuLooperWindow::UpdateViewFlags(BLayout *layout) {
2024-11-12 14:53:44 -08:00
BView *owner = layout->Owner();
if (owner == NULL) {
owner = layout->View();
if (owner == NULL) {
return;
}
}
UpdateViewFlags(owner);
}
2024-11-21 10:22:34 -08:00
HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 100, 500, 100), "Looper", B_TITLED_WINDOW, 0) {
2024-11-12 14:53:44 -08:00
pause_bitmap = load_icon(ICON_PAUSE);
resume_bitmap = load_icon(ICON_PLAY);
stop_bitmap = load_icon(ICON_STOP);
refresh_bitmap = load_icon(ICON_REFRESH);
2024-11-21 10:22:34 -08:00
ref_handler = new HaikuLooperRefHandler(this);
2024-11-12 14:53:44 -08:00
this->playback = playback;
float minW, minH, maxW, maxH;
GetSizeLimits(&minW, &maxW, &minH, &maxH);
minW = 600;
minH = 200;
SetSizeLimits(minW, maxW, minH, maxH);
ResizeTo(minW, minH);
DisableUpdates();
2024-11-21 10:43:50 -08:00
auto about_window = new HaikuAboutWindow();
2024-11-12 14:53:44 -08:00
layout = new BGroupLayout(B_VERTICAL);
SetLayout(layout);
layout->SetSpacing(0.0);
layout->SetInsets(0, 0, 0, 0);
BMenuBar *menu_bar = new BMenuBar("main_menu");
file_menu = new BMenu("File");
open_item = new BMenuItem("Open...", new BMessage(CMD_OPEN));
prefs_item = new BMenuItem("Preferences...", new BMessage(CMD_PREFS));
quit_item = new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED));
help_menu = new BMenu("Help");
about_item = new BMenuItem("About...", new BMessage(CMD_ABOUT));
file_menu->AddItem(open_item);
file_menu->AddItem(prefs_item);
file_menu->AddItem(quit_item);
help_menu->AddItem(about_item);
menu_bar->AddItem(file_menu);
menu_bar->AddItem(help_menu);
layout->AddView(menu_bar);
BView *spacer = new BView("spacer", B_SUPPORTS_LAYOUT|B_FRAME_EVENTS);
spacer->SetExplicitPreferredSize(BSize(0, 0));
2024-11-12 14:53:44 -08:00
spacer->SetExplicitMinSize(BSize(0, 0));
layout->AddView(spacer);
BGroupView *top_row = new BGroupView(B_HORIZONTAL);
BGroupView *bottom_row = new BGroupView(B_HORIZONTAL);
BLayoutItem *top_item = layout->AddView(top_row);
BLayoutItem *bottom_item = layout->AddView(bottom_row);
pause_resume_btn = new BButton(NULL, new BMessage(CMD_PAUSE_RESUME));
pause_resume_btn->SetTarget(this);
top_row->AddChild(pause_resume_btn);
restart_btn = new BButton(NULL, new BMessage(CMD_RESTART));
restart_btn->SetTarget(this);
top_row->AddChild(restart_btn);
2024-12-09 15:28:12 -08:00
BView *slider_parent = new BView("main:slider_parent", B_SUPPORTS_LAYOUT);
BGroupLayout *slider_parent_layout = new BGroupLayout(B_HORIZONTAL, 0);
slider_parent_layout->SetInsets(0);
slider_parent->SetLayout(slider_parent_layout);
2024-11-12 14:53:44 -08:00
slider = new BSlider("seek_slider", "Seek", make_slider_msg(CMD_SEEK, false), 0, INT32_MAX, B_HORIZONTAL);
slider->SetModificationMessage(make_slider_msg(CMD_SEEK, true));
slider->SetTarget(this);
2024-12-09 15:28:12 -08:00
auto *slider_item = slider_parent_layout->AddView(slider);
slider_item->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT));
position_label = new BStringView("main:position_label", "Stopped");
auto *pos_label_item = slider_parent_layout->AddView(position_label);
pos_label_item->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_VERTICAL_CENTER));
position_label->Show();
auto *slider_parent_item = top_row->GroupLayout()->AddView(slider_parent);
slider_parent_item->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT));
slider->Hide();
2024-11-12 14:53:44 -08:00
stop_btn = new BButton(NULL, new BMessage(CMD_STOP));
stop_btn->SetTarget(this);
top_row->AddChild(stop_btn);
2024-11-21 10:22:34 -08:00
volume_slider = new HaikuLooperSlider("volume", "Volume", new BMessage(CMD_VOLUME), 0, 0, 100, 1, false);
2024-11-12 14:53:44 -08:00
volume_slider->SetLimitLabels("Muted", "Full Volume");
volume_slider->SetTarget(this);
top_row->AddChild(volume_slider);
2024-11-21 10:22:34 -08:00
speed_slider = new HaikuLooperSlider("speed", "Speed", new BMessage(CMD_SPEED), 0, 0.25, 4.0, 0.01, true);
2024-11-12 14:53:44 -08:00
speed_slider->SetLimitLabels("0.25x", "4.00x");
speed_slider->SetTarget(this);
bottom_row->AddChild(speed_slider);
2024-11-21 10:22:34 -08:00
tempo_slider = new HaikuLooperSlider("tempo", "Tempo", new BMessage(CMD_TEMPO), 0, 0.25, 4.0, 0.01, true);
2024-11-12 14:53:44 -08:00
tempo_slider->SetLimitLabels("0.25x", "4.00x");
tempo_slider->SetTarget(this);
bottom_row->AddChild(tempo_slider);
2024-11-21 10:22:34 -08:00
pitch_slider = new HaikuLooperSlider("pitch", "Pitch", new BMessage(CMD_PITCH), 0, 0.25, 4.0, 0.01, true);
2024-11-12 14:53:44 -08:00
pitch_slider->SetLimitLabels("0.25x", "4.00x");
pitch_slider->SetTarget(this);
bottom_row->AddChild(pitch_slider);
file_panel = new BFilePanel(B_OPEN_PANEL, new BMessenger(this));
2024-11-21 10:22:34 -08:00
auto prefs_window = new HaikuPrefsWindow(this);
2024-11-12 14:53:44 -08:00
prefs_subwindow = Subwindow::Add(prefs_window);
about_subwindow = Subwindow::Add(about_window);
EnableUpdates();
InvalidateLayout(true);
BRect top_rect = top_item->Frame();
top_item->SetExplicitMinSize(top_rect.Size());
BRect bottom_rect = bottom_item->Frame();
bottom_item->SetExplicitMinSize(bottom_rect.Size());
Pulse();
2024-11-21 10:43:50 -08:00
this->update_thread = new std::thread(std::mem_fn(&HaikuLooperWindow::ThreadFunc), this);
2024-11-12 14:53:44 -08:00
UpdateIfNeeded();
StartWatching(prefs_window, CMD_UPDATE_LABEL_SETTING);
auto *msg = new BMessage(CMD_UPDATE_LABEL_SETTING);
MessageReceived(msg);
delete msg;
}
2024-11-21 10:22:34 -08:00
HaikuLooperWindow::~HaikuLooperWindow() {
2024-11-12 14:53:44 -08:00
delete ref_handler;
delete pause_bitmap;
delete resume_bitmap;
delete stop_bitmap;
delete refresh_bitmap;
playback->Stop();
done = true;
update_thread->join();
delete update_thread;
}
2024-11-21 10:22:34 -08:00
void HaikuLooperWindow::MessageReceived(BMessage *msg) {
2024-11-12 14:53:44 -08:00
SDL_Event ev;
while (SDL_PollEvent(&ev)) {
if (ev.type == SDL_DROPFILE) {
playback->Start(ev.drop.file);
} else if (ev.type == SDL_QUIT) {
Hide();
return;
}
}
if (msg->what == B_REFS_RECEIVED) {
entry_ref ref;
if (msg->FindRef("refs", &ref) != B_OK) { // Don't crash when failing to get ref - This is probably an error in the system or another application
return;
}
BEntry entry(&ref, true);
BPath path;
if (entry.GetPath(&path) != B_OK) { // Don't know how this could cause an error, but don't crash if it does.
return;
}
playback->Start(path.Path());
return;
}
if (msg->IsSystem()) {
switch (msg->what) {
default: {
msg->PrintToStream();
} break;
2024-11-20 08:15:45 -08:00
}
2024-11-12 14:53:44 -08:00
return;
}
switch (msg->what) {
case CMD_PAUSE_RESUME: {
playback->Pause();
} break;
case CMD_RESTART: {
playback->Seek(0.0);
} break;
case CMD_VOLUME: {
playback->SetVolume(msg->GetDouble("be:value", 100));
volume_clicked = is_slider_down_msg(msg);
} break;
case CMD_SPEED: {
playback->SetSpeed(msg->GetDouble("be:value", 1.0));
speed_clicked = is_slider_down_msg(msg);
} break;
case CMD_TEMPO: {
playback->SetTempo(msg->GetDouble("be:value", 1.0));
tempo_clicked = is_slider_down_msg(msg);
} break;
case CMD_PITCH: {
playback->SetPitch(msg->GetDouble("be:value", 1.0));
pitch_clicked = is_slider_down_msg(msg);
} break;
case CMD_SEEK: {
playback->Seek((((double)msg->GetInt32("be:value", 0)) / INT32_MAX) * playback->GetLength());
seek_clicked = is_slider_down_msg(msg);
} break;
case CMD_UPDATE: {
Pulse();
} break;
case CMD_STOP: {
playback->Stop();
} break;
case CMD_OPEN: {
file_panel->Show();
} break;
case CMD_ABOUT: {
about_subwindow->Show();
} break;
case CMD_PREFS: {
prefs_subwindow->Show();
} break;
case CMD_UPDATE_LABEL_SETTING: {
volume_slider->MessageReceived(msg);
speed_slider->MessageReceived(msg);
tempo_slider->MessageReceived(msg);
pitch_slider->MessageReceived(msg);
if (show_labels) {
stop_btn->SetLabel("Stop");
restart_btn->SetLabel("Restart");
} else {
stop_btn->SetLabel("");
restart_btn->SetLabel("");
}
if (show_icons) {
stop_btn->SetIcon(stop_bitmap);
restart_btn->SetIcon(refresh_bitmap);
} else {
stop_btn->SetIcon(get_empty_icon());
restart_btn->SetIcon(get_empty_icon());
}
} break;
case 'DATA': {
status_t err = B_OK;
entry_ref ref;
if ((err = msg->FindRef("refs", &ref)) == B_OK) {
BEntry entry(&ref);
BPath path;
if ((err = entry.GetPath(&path)) == B_OK) {
playback->Start(path.Path());
} else {
WARNING.writefln("entry.GetPath(&path) == %d", (int32)(err));
msg->PrintToStream();
}
2024-11-20 08:15:45 -08:00
} else {
2024-11-12 14:53:44 -08:00
WARNING.writefln("msg->FindRef(\"refs\", &ref) == %d", (int32)(err));
msg->PrintToStream();
}
} break;
default: {
if (msg->HasPointer("catmeow:target")) {
BHandler *ptr = (BHandler*)msg->GetPointer("catmeow:target");
ptr->MessageReceived(msg);
} else {
WARNING.writeln("Unrecognized message type");
msg->PrintToStream();
}
} break;
};
}
2024-11-21 10:22:34 -08:00
void HaikuLooperWindow::Pulse() {
2024-11-12 14:53:44 -08:00
auto len = playback->GetLength();
auto pos = playback->GetPosition();
auto pos_milliseconds = pos * 1000.0;
int32_t pos_int32 = (int32_t)((((int64_t)pos_milliseconds) * INT32_MAX) / 1000 / len);
if (!seek_clicked) slider->SetValue(pos_int32);
auto component_count = TimeToComponentCount(len);
bool enable_ui = !playback->IsStopped();
if (enable_ui) {
2024-12-09 15:28:12 -08:00
if (len <= 0.0) {
position_label->SetText(fmt::format("Position: {} units", (int)pos).c_str());
slider->Hide();
position_label->Show();
} else {
slider->SetLabel(fmt::format("Position: {}", TimeToString(pos, component_count)).c_str());
slider->SetLimitLabels(TimeToString(0, component_count).c_str(), TimeToString(len, component_count).c_str());
position_label->Hide();
slider->Show();
}
2024-11-12 14:53:44 -08:00
if (show_icons) pause_resume_btn->SetIcon(playback->IsPaused() ? resume_bitmap : pause_bitmap);
if (show_labels) pause_resume_btn->SetLabel(playback->IsPaused() ? "Resume" : "Pause");
} else {
2024-12-09 15:28:12 -08:00
position_label->SetText("Stopped.");
slider->Hide();
position_label->Show();
2024-11-12 14:53:44 -08:00
if (show_icons) pause_resume_btn->SetIcon(pause_bitmap);
if (show_labels) pause_resume_btn->SetLabel("Pause");
}
if (!show_icons) pause_resume_btn->SetIcon(get_empty_icon());
if (!show_labels) pause_resume_btn->SetLabel("");
slider->SetEnabled(enable_ui);
pause_resume_btn->SetEnabled(enable_ui);
auto volume = playback->GetVolume();
auto pitch = playback->GetPitch();
auto speed = playback->GetSpeed();
auto tempo = playback->GetTempo();
if (!volume_clicked) volume_slider->SetValue(volume);
2024-11-20 08:15:45 -08:00
volume_slider->SetLabel(fmt::format("Volume: {}%", (int)volume).c_str());
2024-11-12 14:53:44 -08:00
if (!pitch_clicked) pitch_slider->SetValueDouble(pitch);
pitch_slider->SetLabel(fmt::format("Pitch {:.02f}x", pitch).c_str());
if (!speed_clicked) speed_slider->SetValueDouble(speed);
speed_slider->SetLabel(fmt::format("Speed: {:.02f}x", speed).c_str());
if (!tempo_clicked) tempo_slider->SetValueDouble(tempo);
tempo_slider->SetLabel(fmt::format("Tempo: {:.02f}x", tempo).c_str());
UpdateIfNeeded();
auto title = playback->get_current_title();
if (title.has_value()) {
SetTitle(fmt::format("{} - Looper", title.value()).c_str());
} else {
SetTitle("Looper");
}
2024-11-12 14:53:44 -08:00
}
2024-11-21 10:22:34 -08:00
void HaikuLooperWindow::FrameResized(float newWidth, float newHeight) {
2024-11-12 14:53:44 -08:00
InvalidateLayout(true);
Layout(true);
layout->SetExplicitSize(BSize(newWidth, newHeight));
}
2024-11-21 10:22:34 -08:00
void HaikuLooperWindow::ThreadFunc() {
2024-11-12 14:53:44 -08:00
while (true) {
if (done) return;
if (Lock()) {
if (!IsHidden()) {
Unlock();
break;
}
Unlock();
} else {
break;
}
}
while (true) {
if (done) return;
if (Lock()) {
if (IsHidden()) {
Unlock();
break;
}
PostMessage(CMD_UPDATE);
Unlock();
} else {
break;
}
std::this_thread::sleep_for(1s / 60.0);
}
}
2024-11-21 10:43:50 -08:00
HaikuLooperRefHandler::HaikuLooperRefHandler(HaikuLooperWindow *win) {
2024-11-12 14:53:44 -08:00
this->win = win;
be_app->Lock();
this->next_handler = be_app->PreferredHandler();
be_app->AddHandler(this);
be_app->SetPreferredHandler(this);
if (next_handler != NULL) {
SetNextHandler(next_handler);
}
AddFilter(new BMessageFilter(B_REFS_RECEIVED));
be_app->Unlock();
}
2024-11-21 10:22:34 -08:00
void HaikuLooperRefHandler::MessageReceived(BMessage *msg) {
2024-11-12 14:53:44 -08:00
if (msg->what == B_REFS_RECEIVED) {
win->PostMessage(msg);
}
}