diff --git a/Graphics/private/bindings/DebugGUI.cpp b/Graphics/private/bindings/DebugGUI.cpp index 62ad101..2435a84 100644 --- a/Graphics/private/bindings/DebugGUI.cpp +++ b/Graphics/private/bindings/DebugGUI.cpp @@ -4,6 +4,7 @@ // -------- Debug UI -------- // #include +#include #include #include @@ -28,6 +29,9 @@ void Graphics::GUI::init(Window* window) { // Initialize ImGui with GLFW and OpenGL ImGui_ImplGlfw_InitForOpenGL(window->getContext()->window, true); + + // ImGui_ImplGlfw_GetBackendData(); + ImGui_ImplOpenGL3_Init("#version 330"); } @@ -45,10 +49,6 @@ void Graphics::GUI::proc() { ImGui::NewFrame(); tp::HeapAllocGlobal::stopIgnore(); - - // ImGui code goes here - ImGui::Begin("Window"); - ImGui::End(); } void Graphics::GUI::draw() { diff --git a/Graphics/private/bindings/Window.cpp b/Graphics/private/bindings/Window.cpp index da58476..b2aa797 100644 --- a/Graphics/private/bindings/Window.cpp +++ b/Graphics/private/bindings/Window.cpp @@ -59,26 +59,26 @@ void Graphics::GL::draw() { void Graphics::init(Window* window) { mGl.init(); - //mGui.init(window); + mGui.init(window); mCanvas.init(); } void Graphics::deinit() { mCanvas.deinit(); - // mGui.deinit(); + mGui.deinit(); mGl.deinit(); } void Graphics::proc() { mGl.proc(); mCanvas.proc(); - // mGui.proc(); + mGui.proc(); } void Graphics::draw() { mGl.draw(); mCanvas.draw(); - // mGui.draw(); + mGui.draw(); } Window::Window(int width, int height, const char* title) { @@ -101,10 +101,9 @@ Window::Window(int width, int height, const char* title) { return; } - mGraphics.init(this); - mContext->inputManager.init(mContext->window); - glfwSetWindowUserPointer(mContext->window, &mContext->inputManager); + + mGraphics.init(this); mEvents.mContext = mContext; } diff --git a/Graphics/private/bindings/WindowContext.hpp b/Graphics/private/bindings/WindowContext.hpp index 9a6f016..ed8bcae 100644 --- a/Graphics/private/bindings/WindowContext.hpp +++ b/Graphics/private/bindings/WindowContext.hpp @@ -15,6 +15,8 @@ namespace tp { void init(GLFWwindow* aWindow) { window = aWindow; // Set up key and mouse button callbacks + + glfwSetWindowUserPointer(window, this); glfwSetKeyCallback(window, keyCallback); glfwSetMouseButtonCallback(window, mouseButtonCallback); glfwSetScrollCallback(window, scrollCallback); @@ -86,6 +88,7 @@ namespace tp { void resetScroll() { scrollX = 0.0; scrollY = 0.0; + mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] = GLFW_REPEAT; } double scrollX = 0.0; diff --git a/LibraryViewer/CMakeLists.txt b/LibraryViewer/CMakeLists.txt index ea28d80..55fc9de 100644 --- a/LibraryViewer/CMakeLists.txt +++ b/LibraryViewer/CMakeLists.txt @@ -1,17 +1,24 @@ project(LibraryViewer) +find_package(ALSA REQUIRED) + +### ---------------------- Externals --------------------- ### +set(BINDINGS_INCLUDE ../Externals/glfw/include ../Externals/glew/include) +set(BINDINGS_LIBS glfw glew_s Imgui Nanovg) + ### ---------------------- Static Library --------------------- ### file(GLOB SOURCES "./private/*.cpp" "./private/*/*.cpp") file(GLOB HEADERS "./public/*.hpp" "./public/*/*.hpp" "./applications/*.hpp") add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) -target_include_directories(${PROJECT_NAME} PUBLIC ./public/) +target_include_directories(${PROJECT_NAME} PUBLIC ./public/ ${BINDINGS_INCLUDE}) target_link_libraries(${PROJECT_NAME} PUBLIC Graphics Connection) +target_link_libraries(${PROJECT_NAME} PUBLIC ${BINDINGS_LIBS}) file(COPY "library" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") file(COPY "applications/Font.ttf" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") ### -------------------------- Applications -------------------------- ### add_executable(libView ./applications/Entry.cpp) -target_link_libraries(libView ${PROJECT_NAME} LibraryViewer) \ No newline at end of file +target_link_libraries(libView ${PROJECT_NAME} ${ALSA_LIBRARY}) \ No newline at end of file diff --git a/LibraryViewer/applications/Entry.cpp b/LibraryViewer/applications/Entry.cpp index 304f920..6c9046b 100644 --- a/LibraryViewer/applications/Entry.cpp +++ b/LibraryViewer/applications/Entry.cpp @@ -1,9 +1,8 @@ -#include "NewPlacement.hpp" +// #include "NewPlacement.hpp" +#include "WavPlayer.hpp" #include "LibraryGui.hpp" -// events -// animations // monitor resource usage // sorting @@ -33,6 +32,8 @@ void runApp() { } int main() { + // example(); + tp::ModuleManifest* deps[] = { &tp::gModuleLibraryViewer, nullptr }; tp::ModuleManifest binModule("LibViewEntry", nullptr, nullptr, deps); diff --git a/LibraryViewer/library/Example.wav b/LibraryViewer/library/Example.wav new file mode 100644 index 0000000..593c2dc Binary files /dev/null and b/LibraryViewer/library/Example.wav differ diff --git a/LibraryViewer/private/WavPlayer.cpp b/LibraryViewer/private/WavPlayer.cpp new file mode 100644 index 0000000..3e25ac1 --- /dev/null +++ b/LibraryViewer/private/WavPlayer.cpp @@ -0,0 +1,138 @@ + +#include "WavPlayer.hpp" +#include +#include +#include +#include + +WavPlayer::WavPlayer() : + handle(nullptr), + playbackThread(nullptr), + isPlaying(false), + progress(0.0f) { + if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) { + std::cerr << "Error: Couldn't open ALSA PCM device." << std::endl; + } +} + +WavPlayer::~WavPlayer() { + stopSong(); + if (handle) { + snd_pcm_close(handle); + } +} + +void WavPlayer::loadSong(const char* filePath) { + stopSong(); + + if (snd_pcm_prepare(handle) < 0) { + std::cerr << "Error: Couldn't prepare PCM." << std::endl; + return; + } + + // TODO: Load and parse the WAV file + // For simplicity, assume a 16-bit, mono WAV file with a 44.1 kHz sample rate + // You need to handle loading and parsing of the WAV file here + + // Example: + // std::ifstream file(filePath, std::ios::binary); + // if (!file) { + // std::cerr << "Error: Could not open WAV file." << std::endl; + // return; + // } + // ... (load and parse WAV file) + + snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 44100, 1, 500000); +} + +void WavPlayer::playSong() { + if (!isPlaying) { + isPlaying = true; + playbackThread = new std::thread(&WavPlayer::playbackThreadFunction, this); + } +} + +void WavPlayer::stopSong() { + if (isPlaying) { + isPlaying = false; + if (playbackThread && playbackThread->joinable()) { + playbackThread->join(); + } + delete playbackThread; + playbackThread = nullptr; + snd_pcm_drop(handle); + } +} + +void WavPlayer::setVolume(float volume) { + // TODO: Adjust volume using ALSA API + // You might need to use functions like snd_pcm_volume() or other ALSA functions +} + +float WavPlayer::getProgress() const { return progress; } + +float WavPlayer::getDurationSec() const { + // TODO: Return the duration of the loaded song + // You need information from the loaded WAV file for this + return 0.0f; +} + +void WavPlayer::setProgress(float newProgress) { + if (newProgress >= 0.0f && newProgress <= 1.0f) { + progress = newProgress; + + // TODO: Adjust playback position based on the progress using ALSA API + // You might need to use functions like snd_pcm_writei() or other ALSA functions + } +} + +void WavPlayer::playbackThreadFunction() { + const int buffer_size = 1024; // Adjust the buffer size according to your requirements + short buffer[buffer_size]; + + while (isPlaying) { + // TODO: Implement actual playback logic using ALSA API + // You might need to use functions like snd_pcm_writei() or other ALSA functions + + // For simplicity, we'll fill the buffer with dummy data + for (int i = 0; i < buffer_size; ++i) { + buffer[i] = static_cast(32767 * std::sin(2.0 * M_PI * 440.0 * progress)); + } + + // Write the buffer to the PCM device + int err = snd_pcm_writei(handle, buffer, buffer_size); + if (err < 0) { + std::cerr << "Error writing to PCM device: " << snd_strerror(err) << std::endl; + stopSong(); + return; + } + + // Update progress + progress += 0.1f; + if (progress >= 1.0f) { + stopSong(); + } + + // Sleep for a short duration (adjust as needed) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +void example() { + WavPlayer player; + + // Load a WAV file + player.loadSong("path/to/your/file.wav"); + + // Start playing the song asynchronously + player.playSong(); + + // Simulate a main application loop (replace this with your actual application logic) + for (int i = 0; i < 30; ++i) { + std::cout << "Progress: " << player.getProgress() << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + // Stop the song playback + player.stopSong(); +} \ No newline at end of file diff --git a/LibraryViewer/public/LibraryGui.hpp b/LibraryViewer/public/LibraryGui.hpp index 86c6ad9..9537db5 100644 --- a/LibraryViewer/public/LibraryGui.hpp +++ b/LibraryViewer/public/LibraryGui.hpp @@ -3,10 +3,143 @@ #include "LibraryViewer.hpp" #include "Rect.hpp" #include "Animations.hpp" +#include "imgui.h" template concept DrawableConcept = true; +template requires(DrawableConcept) +class ResizerWidget { +public: + ResizerWidget() {} + + void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& area) { + if (!areaParent.isOverlap(area)) { + resizing = false; + return; + } + + hover = area.isInside(events.getPos()); + + if (events.isPressed() && hover) { + resizing = true; + } else if (!events.isDown()) { + resizing = false; + } + + value = area.x; + + if (resizing) { + tp::halnf pos = events.getPos().x; + auto diff = (pos - value - area.z / 2.f); + value += diff; + } + + value = tp::clamp(value, min, max); + + if (min > max) { + value = min; + } + } + + void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { + if (!areaParent.isOverlap(area)) return; + + if (hover || resizing) { + canvas.rect(area, { 0.23f, 0.23f, 0.23f, 1.f}, 0.f); + } else { + canvas.rect(area, { 0.04f, 0.04f, 0.04f, 1.f }, 0.f); + } + } + +public: + bool hover = false; + bool resizing = false; + tp::halnf max = 0; + tp::halnf min = 0; + tp::halnf value = 0; +}; + +template requires(DrawableConcept) +class TrackInfoWidget { + struct SortType { + tp::String text; + bool dec = false; + bool inc = false; + }; + +public: + TrackInfoWidget(const Track* track = nullptr) : mTrack(track) { + items.append( { "Date Added" } ); + items.append( { "Date Last Played" } ); + } + + void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& area) { + if (!mTrack) return; + if (!areaParent.isOverlap(area)) return; + } + + void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { + if (!areaParent.isOverlap(area)) return; + + ImGui::SetNextWindowPos( { area.x, area.y } ); + ImGui::SetNextWindowSize( { area.z, area.w } ); + RenderUI(); + + canvas.rect(area, { 0.13f, 0.13f, 0.13f, 1.f}, 4.f); + + if (!mTrack) return; + } + + void RenderUI() { + ImGui::Begin("InfoWindow", 0, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoResize); + + ImGui::Text("Song Info:"); + if (mTrack) { + ImGui::Text("Name : %s", mTrack->mName.read()); + ImGui::Text("Author : %s", mTrack->mArtist.read()); + } else { + ImGui::Text("Not Selected"); + } + + ImGui::Separator(); + + ImGui::Checkbox("Loved only", &filterLoved); + songFilter.Draw("Song Filter"); + + sortFilter.Draw("Sorting Type"); + + for (int i = 0; i < items.size(); ++i) { + if (!sortFilter.PassFilter(items[i].text.read())) continue; + + ImGui::PushID(i); + + if (ImGui::Button("Inc")) { + items[i].inc = true; + } + ImGui::SameLine(); + if (ImGui::Button("Dec")) { + items[i].dec = true; + } + + ImGui::AlignTextToFramePadding(); + ImGui::SameLine(); + ImGui::Text("%s", items[i].text.read()); + ImGui::PopID(); + } + + ImGui::End(); + } + +public: + ImGuiTextFilter songFilter; + ImGuiTextFilter sortFilter; + tp::Buffer items; + const Track* mTrack; + bool filterLoved = false; +}; + + template requires(DrawableConcept) class ScrollBarWidget { public: @@ -20,7 +153,7 @@ public: return; } - if (events.getScrollY() != 0) { + if (events.getScrollY() != 0 && areaParent.isInside(events.getPos())) { auto offset = events.getScrollY() < 0 ? 1.0f : -1.0f; if (scrollInertia * offset > 0) { scrollInertia += offset; @@ -82,12 +215,13 @@ public: template requires(DrawableConcept) class TrackWidget { public: - TrackWidget(const Track* track) : mTrack(track) { + TrackWidget(const Track* track = nullptr) : mTrack(track) { col.mColor.setAnimTime(0); col.mColor.setNoTransition({ 0.15f, 0.15f, 0.15f, 0.f }); }; void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& area) { + if (!mTrack) return; if (!areaParent.isOverlap(area)) return; if (area.isInside(events.getPos())) { col.set( { 0.15f, 0.15f, 0.15f, 1.f } ); @@ -99,6 +233,7 @@ public: } void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { + if (!mTrack) return; if (!areaParent.isOverlap(area)) return; canvas.rect(area, col.get(), 4.f); @@ -116,7 +251,7 @@ public: canvas.text(mTrack->mArtist.read(), textAreaAuthor, 12.f, Canvas::LC, 4.f, { 0.8f, 0.8f, 0.8f, 1.f} ); } -private: +public: tp::halnf marging = 5.f; bool insideDebug = false; tp::AnimColor col; @@ -130,25 +265,39 @@ public: for (auto track : mLibrary->mTraks) { mTraks.append(TrackWidget(&track.data())); } + mResizeWidget.value = 1000; } void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) { + mResizeWidget.max = aArea.z - trackInfoSize; + mResizeWidget.min = 100; - area = { aArea.x, aArea.y, aArea.z, aArea.w - controllPanelSize }; areaCP = { aArea.x, aArea.y + aArea.w - controllPanelSize, aArea.z, controllPanelSize }; + area = { aArea.x, aArea.y, mResizeWidget.value, aArea.w - controllPanelSize }; + areaInfo = { mResizeWidget.value + resizeSize, aArea.y, aArea.z - mResizeWidget.value - resizeSize, aArea.w - controllPanelSize } ; + areaResize = { mResizeWidget.value, aArea.y, resizeSize, areaInfo.w }; mScroll.mSizeFraction = area.w / (mLibrary->mTraks.size() * trackSize); mScroll.mScrollFactor = 1.f / mLibrary->mTraks.size(); + mScroll.proc(events, area, { area.x + area.z - scrollSize - 3, area.y + 10, scrollSize - 3.f, area.w - 20 } ); + mResizeWidget.proc(events, area, areaResize ); + if (area.isInside(events.getPos())) { - - mScroll.proc(events, area, { area.x + area.z - scrollSize - 3, area.y + 10, scrollSize - 3.f, area.w - 20 } ); tp::halnf offset = mScroll.mPositionFraction * mLibrary->mTraks.size() * trackSize; auto idx = 0; for (auto track : mTraks) { - track->proc(events, area, { area.x + 10, area.y + 10 + idx * trackSize - offset, area.z - 20 - scrollSize, trackSize - 5 } ); + auto trackArea = tp::RectF{ area.x + 10, area.y + 10 + idx * trackSize - offset, area.z - 20 - scrollSize, trackSize - 5 }; + + track->proc(events, area, trackArea); + + if (trackArea.isInside(events.getPos()) && events.isPressed()) { + mCurrentTrack.mTrack = track->mTrack; + mCurrentTrackInfo.mTrack = track->mTrack; + } + idx++; } } @@ -157,6 +306,7 @@ public: } void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& aArea) { + canvas.rect(aArea, { 0.1f, 0.1f, 0.1f, 1.f }); tp::halnf offset = mScroll.mPositionFraction * mLibrary->mTraks.size() * trackSize; @@ -168,25 +318,39 @@ public: } mScroll.draw(canvas, area, { area.x + area.z - scrollSize - 3, area.y + 10, scrollSize - 3.f, area.w - 20 } ); + + // canvas.rect( areaInfo, { 0.04f, 0.04f, 0.04f, 1.f }); + mCurrentTrackInfo.draw(canvas, aArea, areaInfo ); + + mResizeWidget.draw(canvas, aArea, areaResize ); drawCP(canvas, aArea, areaCP); } void drawCP(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { canvas.rect( area, { 0.04f, 0.04f, 0.04f, 1.f }); - mTraks.first().draw(canvas, area, area); + mCurrentTrack.draw(canvas, area, area); } private: - tp::halnf scrollSize = 15; + tp::halnf scrollSize = 20; + tp::halnf trackInfoSize = 200; tp::halnf controllPanelSize = 70; tp::halnf trackSize = 60; tp::Vec2F debugPos; + tp::halnf resizeSize = 10; tp::RectF area; + tp::RectF areaInfo; tp::RectF areaCP; + tp::RectF areaResize; tp::Buffer> mTraks; + TrackWidget mCurrentTrack; + TrackInfoWidget mCurrentTrackInfo; + ScrollBarWidget mScroll; + ResizerWidget mResizeWidget; + Library* mLibrary; }; \ No newline at end of file diff --git a/LibraryViewer/public/WavPlayer.hpp b/LibraryViewer/public/WavPlayer.hpp new file mode 100644 index 0000000..579684c --- /dev/null +++ b/LibraryViewer/public/WavPlayer.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +class WavPlayer { +public: + WavPlayer(); + ~WavPlayer(); + + void loadSong(const char* filePath); + void playSong(); + void stopSong(); + void setVolume(float volume); + float getProgress() const; + float getDurationSec() const; + void setProgress(float newProgress); + +private: + void playbackThreadFunction(); + + snd_pcm_t* handle; + std::thread* playbackThread; + bool isPlaying; + float progress; +}; + +void example(); \ No newline at end of file