LibraryViewer
This commit is contained in:
parent
f6ba86aff8
commit
dd71eba0ec
24 changed files with 226111 additions and 36 deletions
17
LibraryViewer/CMakeLists.txt
Normal file
17
LibraryViewer/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
project(LibraryViewer)
|
||||
|
||||
### ---------------------- 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_link_libraries(${PROJECT_NAME} PUBLIC Graphics Connection)
|
||||
|
||||
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)
|
||||
46
LibraryViewer/applications/Entry.cpp
Normal file
46
LibraryViewer/applications/Entry.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
#include "NewPlacement.hpp"
|
||||
#include "LibraryGui.hpp"
|
||||
|
||||
// events
|
||||
// animations
|
||||
// monitor resource usage
|
||||
// sorting
|
||||
|
||||
void runApp() {
|
||||
|
||||
Library library;
|
||||
library.loadJson("library/Library.json");
|
||||
|
||||
auto gui = LibraryWidget<tp::Window::Events, tp::Graphics::Canvas>(&library);
|
||||
|
||||
auto window = tp::Window::createWindow(800, 600, "Window 1");
|
||||
|
||||
if (window) {
|
||||
while (!window->shouldClose()) {
|
||||
window->processEvents();
|
||||
|
||||
auto area = window->getCanvas().getAvaliableArea();
|
||||
|
||||
gui.proc(window->getEvents(), {}, { area.x, area.y, area.z, area.w });
|
||||
gui.draw(window->getCanvas(), {}, { area.x, area.y, area.z, area.w });
|
||||
|
||||
window->draw();
|
||||
}
|
||||
}
|
||||
|
||||
tp::Window::destroyWindow(window);
|
||||
}
|
||||
|
||||
int main() {
|
||||
tp::ModuleManifest* deps[] = { &tp::gModuleLibraryViewer, nullptr };
|
||||
tp::ModuleManifest binModule("LibViewEntry", nullptr, nullptr, deps);
|
||||
|
||||
if (!binModule.initialize()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
runApp();
|
||||
|
||||
binModule.deinitialize();
|
||||
}
|
||||
BIN
LibraryViewer/applications/Font.ttf
Normal file
BIN
LibraryViewer/applications/Font.ttf
Normal file
Binary file not shown.
102806
LibraryViewer/library/Library.json
Normal file
102806
LibraryViewer/library/Library.json
Normal file
File diff suppressed because it is too large
Load diff
121340
LibraryViewer/library/Library.xml
Normal file
121340
LibraryViewer/library/Library.xml
Normal file
File diff suppressed because it is too large
Load diff
96
LibraryViewer/library/convert.py
Normal file
96
LibraryViewer/library/convert.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import xmltodict
|
||||
import json
|
||||
|
||||
|
||||
def flatten_dict(d):
|
||||
|
||||
out = []
|
||||
for song in d:
|
||||
newSong = {}
|
||||
|
||||
strCount = 0
|
||||
intCount = 0
|
||||
dateCount = 0
|
||||
|
||||
def getStr(id):
|
||||
nonlocal strCount
|
||||
if id not in song["key"]:
|
||||
return "undef"
|
||||
if len(song["string"]) <= strCount:
|
||||
return "error"
|
||||
strCount += 1
|
||||
return song["string"][strCount - 1]
|
||||
|
||||
def getInt(id):
|
||||
nonlocal intCount
|
||||
if id not in song["key"]:
|
||||
return -1
|
||||
if len(song["integer"]) <= intCount:
|
||||
return -1
|
||||
intCount += 1
|
||||
return song["integer"][intCount - 1]
|
||||
|
||||
def getDate(id):
|
||||
nonlocal dateCount
|
||||
if id not in song["key"]:
|
||||
return "undef"
|
||||
if len(song["date"]) <= dateCount:
|
||||
return "error"
|
||||
dateCount += 1
|
||||
return song["date"][dateCount - 1]
|
||||
|
||||
newSong["Track ID"] = getInt("Track ID")
|
||||
newSong["Name"] = getStr("Name")
|
||||
newSong["Artist"] = getStr("Artist")
|
||||
newSong["Album Artist"] = getStr("Album Artist")
|
||||
newSong["Composer"] = getStr("Composer")
|
||||
newSong["Album"] = getStr("Album")
|
||||
newSong["Genre"] = getStr("Genre")
|
||||
newSong["Kind"] = getStr("Kind")
|
||||
newSong["Size"] = getInt("Size")
|
||||
newSong["Total Time"] = getInt("Total Time")
|
||||
newSong["Disc Number"] = getInt("Disc Number")
|
||||
newSong["Disc Count"] = getInt("Disc Count")
|
||||
newSong["Track Number"] = getInt("Track Number")
|
||||
newSong["Track Count"] = getInt("Track Count")
|
||||
newSong["Year"] = getInt("Year")
|
||||
newSong["Date Modified"] = getDate("Date Modified")
|
||||
newSong["Date Added"] = getDate("Date Added")
|
||||
newSong["Bit Rate"] = getInt("Bit Rate")
|
||||
newSong["Sample Rate"] = getInt("Sample Rate")
|
||||
newSong["Play Count"] = getInt("Play Count")
|
||||
newSong["Play Date"] = getInt("Play Date")
|
||||
newSong["Play Date UTC"] = getDate("Play Date UTC")
|
||||
newSong["Skip Count"] = getInt("Skip Count")
|
||||
newSong["Skip Date"] = getDate("Skip Date")
|
||||
newSong["Release Date"] = getDate("Release Date")
|
||||
newSong["Album Rating"] = getInt("Album Rating")
|
||||
newSong["Album Rating Computed"] = "Album Rating Computed" in song["key"]
|
||||
newSong["Loved"] = "Loved" in song["key"]
|
||||
newSong["Album Loved"] = "Album Loved" in song["key"]
|
||||
newSong["Explicit"] = "Explicit" in song["key"]
|
||||
newSong["Compilation"] = "Compilation" in song["key"]
|
||||
newSong["Artwork Count"] = getInt("Artwork Count")
|
||||
newSong["Sort Album"] = getStr("Sort Album")
|
||||
newSong["Sort Artist"] = getStr("Sort Artist")
|
||||
newSong["Sort Name"] = getStr("Sort Name")
|
||||
newSong["Persistent ID"] = getStr("Persistent ID")
|
||||
newSong["Track Type"] = getStr("Track Type")
|
||||
|
||||
out.append(newSong)
|
||||
|
||||
return out
|
||||
|
||||
with open('Library.xml', 'r', encoding='utf-8') as xml_file:
|
||||
data_dict = xmltodict.parse(xml_file.read())
|
||||
|
||||
# Extract the "Tracks" dictionary to be flattened
|
||||
tracks_dict = data_dict['plist']['dict']['dict']
|
||||
|
||||
flat_tracks_dict = flatten_dict(tracks_dict["dict"])
|
||||
|
||||
|
||||
json_data = json.dumps(flat_tracks_dict, indent=2)
|
||||
|
||||
with open('output.json', 'w', encoding='utf-8') as json_file:
|
||||
json_file.write(json_data)
|
||||
49
LibraryViewer/private/LibraryViewer.cpp
Normal file
49
LibraryViewer/private/LibraryViewer.cpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
#include "LibraryViewer.hpp"
|
||||
#include "LocalConnection.hpp"
|
||||
|
||||
#include "picojson.h"
|
||||
|
||||
namespace tp {
|
||||
static ModuleManifest* deps[] = { &gModuleGraphics, &gModuleConnection, nullptr };
|
||||
ModuleManifest gModuleLibraryViewer = ModuleManifest("LibraryViewer", nullptr, nullptr, deps);
|
||||
}
|
||||
|
||||
using namespace tp;
|
||||
|
||||
bool Library::loadJson(const String& path) {
|
||||
LocalConnection libraryFile;
|
||||
Buffer<int1> libraryFileMem;
|
||||
|
||||
if (!libraryFile.connect(LocalConnection::Location(path), LocalConnection::Type(true))) return false;
|
||||
|
||||
libraryFileMem.reserve(libraryFile.size());
|
||||
libraryFile.readBytes(libraryFileMem.getBuff(), libraryFile.size());
|
||||
|
||||
std::string json = libraryFileMem.getBuff();
|
||||
picojson::value jsonNode;
|
||||
std::string err = picojson::parse(jsonNode, json);
|
||||
|
||||
if (! err.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto & traks = jsonNode.get<picojson::array>();
|
||||
for (auto & trackNode : traks) {
|
||||
Track newTrack;
|
||||
|
||||
auto & track = trackNode.get<picojson::object>();
|
||||
for (auto & trackProperty : track) {
|
||||
if (trackProperty.first == "Name") {
|
||||
newTrack.mName = (int1*) trackProperty.second.to_str().c_str();
|
||||
}
|
||||
else if (trackProperty.first == "Artist") {
|
||||
newTrack.mArtist = (int1*) trackProperty.second.to_str().c_str();
|
||||
}
|
||||
}
|
||||
|
||||
mTraks.append(newTrack);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
192
LibraryViewer/public/LibraryGui.hpp
Normal file
192
LibraryViewer/public/LibraryGui.hpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
#pragma once
|
||||
|
||||
#include "LibraryViewer.hpp"
|
||||
#include "Rect.hpp"
|
||||
#include "Animations.hpp"
|
||||
|
||||
template <typename T>
|
||||
concept DrawableConcept = true;
|
||||
|
||||
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) return;
|
||||
|
||||
if (!areaParent.isOverlap(area)) {
|
||||
mIsScrolling = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (events.getScrollY() != 0) {
|
||||
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) : 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 (!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 (!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} );
|
||||
}
|
||||
|
||||
private:
|
||||
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) : mLibrary(lib) {
|
||||
for (auto track : mLibrary->mTraks) {
|
||||
mTraks.append(TrackWidget<Events, Canvas>(&track.data()));
|
||||
}
|
||||
}
|
||||
|
||||
void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) {
|
||||
|
||||
area = { aArea.x, aArea.y, aArea.z, aArea.w - controllPanelSize };
|
||||
areaCP = { aArea.x, aArea.y + aArea.w - controllPanelSize, aArea.z, controllPanelSize };
|
||||
|
||||
mScroll.mSizeFraction = area.w / (mLibrary->mTraks.size() * trackSize);
|
||||
mScroll.mScrollFactor = 1.f / mLibrary->mTraks.size();
|
||||
|
||||
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 } );
|
||||
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 * mLibrary->mTraks.size() * trackSize;
|
||||
auto idx = 0;
|
||||
|
||||
for (auto track : mTraks) {
|
||||
track->draw(canvas, area, { area.x + 10, area.y + 10 + idx * trackSize - offset, area.z - 20 - scrollSize, trackSize - 5 } );
|
||||
idx++;
|
||||
}
|
||||
|
||||
mScroll.draw(canvas, area, { area.x + area.z - scrollSize - 3, area.y + 10, scrollSize - 3.f, area.w - 20 } );
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private:
|
||||
tp::halnf scrollSize = 15;
|
||||
tp::halnf controllPanelSize = 70;
|
||||
tp::halnf trackSize = 60;
|
||||
tp::Vec2F debugPos;
|
||||
|
||||
tp::RectF area;
|
||||
tp::RectF areaCP;
|
||||
|
||||
tp::Buffer<TrackWidget<Events, Canvas>> mTraks;
|
||||
ScrollBarWidget<Events, Canvas> mScroll;
|
||||
Library* mLibrary;
|
||||
};
|
||||
31
LibraryViewer/public/LibraryViewer.hpp
Normal file
31
LibraryViewer/public/LibraryViewer.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include "Window.hpp"
|
||||
|
||||
namespace tp {
|
||||
extern ModuleManifest gModuleLibraryViewer;
|
||||
}
|
||||
|
||||
class Track {
|
||||
public:
|
||||
Track() = default;
|
||||
|
||||
public:
|
||||
tp::String mName = "undef";
|
||||
tp::String mArtist = "undef";
|
||||
};
|
||||
|
||||
class Library {
|
||||
public:
|
||||
Library() = default;
|
||||
|
||||
public:
|
||||
bool loadJson(const tp::String& path);
|
||||
|
||||
public:
|
||||
tp::Buffer<Track> mTraks;
|
||||
};
|
||||
|
||||
class LibraryViewverGui {};
|
||||
|
||||
void runApp();
|
||||
1200
LibraryViewer/public/picojson.h
Normal file
1200
LibraryViewer/public/picojson.h
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue