451 lines
No EOL
12 KiB
C++
451 lines
No EOL
12 KiB
C++
#pragma once
|
|
|
|
#include "Library.hpp"
|
|
#include "Player.hpp"
|
|
#include "Rect.hpp"
|
|
#include "Animations.hpp"
|
|
#include "imgui.h"
|
|
|
|
template <typename T>
|
|
concept DrawableConcept = true;
|
|
|
|
template <typename Events, typename Canvas> requires(DrawableConcept<Canvas>)
|
|
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 <typename Events, typename Canvas> requires(DrawableConcept<Canvas>)
|
|
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);
|
|
|
|
if (mTrack) {
|
|
|
|
ImGui::Text(" Load Status : %s", mLoadStatus ? "Loaded" : "Not Loaded");
|
|
|
|
if (ImGui::Button("Play")) {
|
|
if (mPlayer->getPlayingId() != mTrack->mId) {
|
|
mLoadStatus = mPlayer->startStreamTrack(mTrack->mId);
|
|
}
|
|
mPlayer->continuePlayback();
|
|
}
|
|
|
|
if (mLoadStatus) {
|
|
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Stop")) {
|
|
mPlayer->freezePlayback();
|
|
}
|
|
|
|
auto songProgress = mPlayer->getPlaybackProgress();
|
|
auto vol = mPlayer->getVolume();
|
|
ImGui::Text("Load Progress : %f", mPlayer->getLoadProgress());
|
|
if (ImGui::SliderFloat("Progress", &songProgress, 0.f, 1.f)) {
|
|
mPlayer->setPlaybackProgress(songProgress);
|
|
}
|
|
if (ImGui::SliderFloat("Volume", &vol, 0.f, 1.f)) {
|
|
mPlayer->setVolume(vol);
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::Text("Song Info:");
|
|
if (mTrack) {
|
|
ImGui::Text("Name : %s", mTrack->mName.read());
|
|
ImGui::Text("Author : %s", mTrack->mArtist.read());
|
|
ImGui::Text("Love : %s", mTrack->mLoved ? "true" : "false");
|
|
ImGui::Text("Id : %lli", mTrack->mId);
|
|
ImGui::Text("Total Time : %lli", mTrack->mTotalTime);
|
|
ImGui::Text("Play Count : %lli", mTrack->mPlayCount);
|
|
ImGui::Text("Skip Count : %lli", mTrack->mSkipCount);
|
|
ImGui::Text("Date Added : %s", mTrack->mDateAdded.read());
|
|
ImGui::Text("Albom : %s", mTrack->mAlbum.read());
|
|
} else {
|
|
ImGui::Text("Not Selected");
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::Checkbox("Loved only", &filterLoved)) {
|
|
isSongFilterChanged |= true;
|
|
}
|
|
|
|
if (ImGui::SliderInt("Existing only", &filterExisting, 0, 3)) {
|
|
isSongFilterChanged |= true;
|
|
}
|
|
|
|
isSongFilterChanged |= 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<SortType> items;
|
|
const Track* mTrack;
|
|
bool filterLoved = false;
|
|
Player* mPlayer = nullptr;
|
|
bool mLoadStatus = false;
|
|
bool isSongFilterChanged = true;
|
|
int filterExisting = 0; // all existing no-existing
|
|
};
|
|
|
|
|
|
template <typename Events, typename Canvas> requires(DrawableConcept<Canvas>)
|
|
class ScrollBarWidget {
|
|
public:
|
|
ScrollBarWidget() = default;
|
|
|
|
void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& area) {
|
|
if (mSizeFraction > 1.f) {
|
|
mPositionFraction = 0;
|
|
return;
|
|
}
|
|
|
|
if (!areaParent.isOverlap(area)) {
|
|
mIsScrolling = false;
|
|
return;
|
|
}
|
|
|
|
if (events.getScrollY() != 0 && areaParent.isInside(events.getPos())) {
|
|
auto offset = events.getScrollY() < 0 ? 1.0f : -1.0f;
|
|
if (scrollInertia * offset > 0) {
|
|
scrollInertia += offset;
|
|
} else {
|
|
scrollInertia = -scrollInertia + offset;
|
|
}
|
|
}
|
|
|
|
if (tp::abs(scrollInertia) > 0.1f) {
|
|
auto offset = scrollInertia * mScrollFactor;
|
|
mPositionFraction += offset;
|
|
mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction);
|
|
scrollInertia *= 0.f;
|
|
return;
|
|
}
|
|
|
|
if (events.isPressed() && area.isInside(events.getPos())) {
|
|
mIsScrolling = true;
|
|
} else if (!events.isDown()) {
|
|
mIsScrolling = false;
|
|
}
|
|
|
|
if (mIsScrolling) {
|
|
tp::halnf pos = events.getPos().y;
|
|
pos = (pos - area.y - mSizeFraction * area.w / 2.f) / area.w;
|
|
mPositionFraction = tp::clamp(pos, 0.f, 1.f - mSizeFraction);
|
|
}
|
|
|
|
mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction);
|
|
}
|
|
|
|
void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) {
|
|
if (mSizeFraction > 1.f) return;
|
|
if (!areaParent.isOverlap(area)) return;
|
|
|
|
auto minSize = 10.f;
|
|
tp::RGBA col = tp::RGBA{ 0.25f, 0.25f, 0.25f, 1.f };
|
|
|
|
if (mIsScrolling) {
|
|
col = tp::RGBA{ 0.45f, 0.45f, 0.45f, 1.f };
|
|
minSize = 20.f;
|
|
}
|
|
|
|
canvas.rect(area, { 0.15f, 0.15f, 0.15f, 1.f }, 4.f);
|
|
auto sliderSize = tp::clamp(area.w * mSizeFraction, minSize, area.w);
|
|
auto diffSize = sliderSize - area.w * mSizeFraction;
|
|
canvas.rect({ area.x, area.y + (area.w - diffSize) * mPositionFraction, area.z, sliderSize }, col, 4.f);
|
|
}
|
|
|
|
public:
|
|
tp::halnf mScrollFactor = 0.f;
|
|
tp::halnf scrollInertia = 0.f;
|
|
bool mIsScrolling = false;
|
|
tp::halnf mSizeFraction = 1.f;
|
|
tp::halnf mPositionFraction = 0.f;
|
|
};
|
|
|
|
|
|
template <typename Events, typename Canvas> requires(DrawableConcept<Canvas>)
|
|
class TrackWidget {
|
|
public:
|
|
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 } );
|
|
insideDebug = true;
|
|
} else {
|
|
col.set( { 0.15f, 0.15f, 0.15f, 0.f } );
|
|
insideDebug = false;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
const tp::RectF imageArea = { area.x + marging, area.y + marging, area.w - marging * 2, area.w - marging * 2 };
|
|
canvas.rect(imageArea, { 0.25f, 0.25f, 0.25f, 1.f }, 4.f);
|
|
|
|
const tp::RectF textArea = { area.x + area.w + marging, area.y + marging, area.z - area.w - marging * 2, area.w - marging * 2 };
|
|
// canvas.rect(textArea, { 0.25f, 0.25f, 0.25f, 1.f }, 4.f);
|
|
|
|
const tp::RectF textAreaName = { textArea.x, textArea.y, textArea.z, textArea.w * 0.5f };
|
|
const tp::RectF textAreaAuthor = { textArea.x, textArea.y + textArea.w * 0.5f, textArea.z, textArea.w * 0.5f };
|
|
|
|
canvas.text(mTrack->mName.read(), textAreaName, 15.f, Canvas::LC, 4.f, { 0.9f, 0.9f, 0.9f, 1.f} );
|
|
canvas.text(mTrack->mArtist.read(), textAreaAuthor, 12.f, Canvas::LC, 4.f, { 0.8f, 0.8f, 0.8f, 1.f} );
|
|
}
|
|
|
|
public:
|
|
tp::halnf marging = 5.f;
|
|
bool insideDebug = false;
|
|
tp::AnimColor col;
|
|
const Track* mTrack;
|
|
};
|
|
|
|
template <typename Events, typename Canvas> requires(DrawableConcept<Canvas>)
|
|
class LibraryWidget {
|
|
public:
|
|
LibraryWidget(Library* lib, Player* player) : mLibrary(lib), mPlayer(player) {
|
|
for (auto track : mLibrary->mTraks) {
|
|
mTraks.append(TrackWidget<Events, Canvas>(&track.data()));
|
|
}
|
|
mResizeWidget.value = 1000;
|
|
mCurrentTrackInfo.mPlayer = mPlayer;
|
|
}
|
|
|
|
void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) {
|
|
mNeedRedraw = events.isEvent() || mCurrentTrackInfo.songFilter.IsActive();
|
|
if (!mNeedRedraw) return;
|
|
|
|
filter();
|
|
|
|
mResizeWidget.max = aArea.z - trackInfoSize;
|
|
mResizeWidget.min = 100;
|
|
|
|
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 - 10) / (mTraksFiltered.size() * trackSize);
|
|
mScroll.mScrollFactor = 1.f / mTraksFiltered.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())) {
|
|
|
|
tp::halnf offset = mScroll.mPositionFraction * mTraksFiltered.size() * trackSize;
|
|
auto idx = 0;
|
|
|
|
for (auto track : mTraksFiltered) {
|
|
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++;
|
|
}
|
|
}
|
|
|
|
debugPos = events.getPos();
|
|
}
|
|
|
|
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 * mTraksFiltered.size() * trackSize;
|
|
auto idx = 0;
|
|
|
|
for (auto track : mTraksFiltered) {
|
|
auto const trackArea = tp::RectF{ area.x + 10, area.y + 10 + idx * trackSize - offset, area.z - 20 - scrollSize, trackSize - 5 };
|
|
if (track->mTrack == mCurrentTrack.mTrack) canvas.rect(trackArea, { 0.2f, 0.2f, 0.2f, 0.5f }, 5);
|
|
track->draw(canvas, area, trackArea);
|
|
idx++;
|
|
}
|
|
|
|
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 });
|
|
mCurrentTrack.draw(canvas, area, area);
|
|
}
|
|
|
|
void filter() {
|
|
if (!mCurrentTrackInfo.isSongFilterChanged) return;
|
|
mTraksFiltered.clear();
|
|
|
|
for (auto track : mTraks) {
|
|
if (!mCurrentTrackInfo.songFilter.PassFilter(track->mTrack->mName.read()) && !mCurrentTrackInfo.songFilter.PassFilter(track->mTrack->mArtist.read())) {
|
|
continue;
|
|
}
|
|
|
|
if (mCurrentTrackInfo.filterLoved && !track->mTrack->mLoved) {
|
|
continue;
|
|
}
|
|
|
|
switch (mCurrentTrackInfo.filterExisting) {
|
|
case 1:
|
|
if (!track->mTrack->mExists) continue;
|
|
break;
|
|
|
|
case 2:
|
|
if (track->mTrack->mExists) continue;
|
|
break;
|
|
}
|
|
|
|
mTraksFiltered.append( &track.data() );
|
|
}
|
|
|
|
mCurrentTrackInfo.isSongFilterChanged = false;
|
|
}
|
|
|
|
private:
|
|
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<TrackWidget<Events, Canvas>> mTraks;
|
|
tp::Buffer<TrackWidget<Events, Canvas>*> mTraksFiltered;
|
|
|
|
TrackWidget<Events, Canvas> mCurrentTrack;
|
|
TrackInfoWidget<Events, Canvas> mCurrentTrackInfo;
|
|
|
|
ScrollBarWidget<Events, Canvas> mScroll;
|
|
ResizerWidget<Events, Canvas> mResizeWidget;
|
|
|
|
Library* mLibrary;
|
|
Player* mPlayer;
|
|
|
|
bool mNeedRedraw = false;
|
|
}; |