Implemented get request + parsing for list.json

This commit is contained in:
OmniaX-Dev 2026-06-21 06:33:48 +02:00
parent 893ddf3a83
commit 45c22ffe99
26 changed files with 21536 additions and 189 deletions

View file

@ -12,11 +12,11 @@ set(CMAKE_C_COMPILER "clang")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
cmake_minimum_required(VERSION 3.18)
project(${PROJ_NAME} LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 23)
file(STRINGS "./other/build.nr" BUILD_NUMBER)
#-----------------------------------------------------------------------------------------
message(STATUS "Building ${PROJ_NAME} ${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
message("** Building ${PROJ_NAME} ${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Debug Configuration")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
@ -35,26 +35,74 @@ list(APPEND SOURCE_FILES
#Targets
#-----------------------------------------------------------------------------------------
set(APP_TARGET pianolib)
add_executable(${APP_TARGET} ${SOURCE_FILES})
target_include_directories(${APP_TARGET} PUBLIC ${INCLUDE_DIRS})
set(MAIN_TARGET pianolib)
add_executable(${MAIN_TARGET} ${SOURCE_FILES})
if(WIN32)
target_sources(${MAIN_TARGET} PUBLIC "${CMAKE_CURRENT_LIST_DIR}/other/appIcon.rc")
endif()
target_include_directories(${MAIN_TARGET} PUBLIC ${INCLUDE_DIRS})
target_compile_definitions(${MAIN_TARGET} PUBLIC BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
if (WIN32)
set(MSYS2_UCRT64 "C:/msys64/ucrt64")
target_include_directories(${MAIN_TARGET} PRIVATE
"${MSYS2_UCRT64}/include"
"${MSYS2_UCRT64}/include/c++/13.2.0"
"${MSYS2_UCRT64}/include/c++/13.2.0/x86_64-w64-mingw32"
)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-O3 -MMD -MP -Wall -ggdb -fsanitize=address)
target_compile_definitions(${APP_TARGET} PUBLIC PIANOLIB_DEBUG_BUILD)
target_compile_definitions(${MAIN_TARGET} PUBLIC BUILD_CONFIG_DEBUG OX_DEBUG_BUILD)
add_compile_options(-MMD -MP -Wall -ggdb)
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
add_compile_options(-MMD -MP -Wall)
target_compile_definitions(${APP_TARGET} PUBLIC PIANOLIB_RELEASE_BUILD)
target_compile_definitions(${MAIN_TARGET} PUBLIC BUILD_CONFIG_RELEASE OX_RELEASE_BUILD)
add_compile_options(-O3 -MMD -MP -Wall)
endif()
if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Release")
set_target_properties(${MAIN_TARGET} PROPERTIES
WIN32_EXECUTABLE TRUE
)
endif()
#-----------------------------------------------------------------------------------------
#Linking
#-----------------------------------------------------------------------------------------
target_link_libraries(${APP_TARGET} PUBLIC ostd ogfx)
if (WIN32)
target_link_libraries(${MAIN_TARGET} mingw32 SDL3 SDL3_ttf SDL3_image)
endif (WIN32)
if (APPLE)
cmake_policy(SET CMP0167 OLD)
find_package(Boost REQUIRED COMPONENTS regex)
target_link_libraries(${MAIN_TARGET} Boost::regex)
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL3 REQUIRED sdl3)
pkg_check_modules(SDL3_IMAGE REQUIRED sdl3-image)
pkg_check_modules(SDL3_TTF REQUIRED sdl3-ttf)
target_include_directories(${MAIN_TARGET} PUBLIC
${SDL3_INCLUDE_DIRS}
${SDL3_IMAGE_INCLUDE_DIRS}
${SDL3_TTF_INCLUDE_DIRS}
)
target_link_directories(${MAIN_TARGET} PUBLIC
${SDL3_LIBRARY_DIRS}
${SDL3_IMAGE_LIBRARY_DIRS}
${SDL3_TTF_LIBRARY_DIRS}
)
target_link_libraries(${MAIN_TARGET}
${SDL3_LIBRARIES}
${SDL3_IMAGE_LIBRARIES}
${SDL3_TTF_LIBRARIES}
)
elseif (UNIX)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,'$ORIGIN',-rpath,/usr/lib,-rpath,/usr/local/lib")
target_link_libraries(${MAIN_TARGET} xcb xcb-randr boost_regex SDL3 SDL3_image)
endif ()
target_compile_definitions(${MAIN_TARGET} PUBLIC CPPHTTPLIB_MBEDTLS_SUPPORT)
target_link_libraries(${MAIN_TARGET} mbedtls mbedx509 mbedcrypto)
target_link_libraries(${MAIN_TARGET} ostd ogfx)
#-----------------------------------------------------------------------------------------

42
build
View file

@ -22,18 +22,52 @@ if [[ "$1" == "debug" || "$1" == "release" ]]; then
cmake -B bin -S ./ -G "$MAKEFILE_TYPE" -DCMAKE_BUILD_TYPE=$BUILD_TYPE
$MAKE_COMMAND -C bin $NJOBS --no-print-directory
cp -r extra/* bin/
build_number=$(cat other/build.nr)
build_number=$((build_number + 1))
typeset -i build_number=$(cat other/build.nr)
((build_number++))
truncate -s 0 other/build.nr
echo $build_number >> other/build.nr
# elif [[ "$1" == "windows_release" ]]; then
# cd other
# chmod +x build_windows_release.sh
# ./build_windows_release.sh
# cd ../bin/DragonV2_w64
# cd ../..
# elif [[ "$1" == "linux_release" ]]; then
# cd other
# chmod +x build_linux_release.sh
# ./build_linux_release.sh
# cd ../bin/DragonV2_linux64
# cd ../..
# elif [[ "$1" == "dependencies" ]]; then
# cd other
# chmod +x build_dependencies.sh
# ./build_dependencies.sh
# cd ..
else
cp -r extra/* bin/
printf "\n\033[0;32mBuilding Changes...\n\n\033[0m"
$MAKE_COMMAND -C bin $NJOBS --no-print-directory
if [[ "$1" == "run" ]]; then
printf "\n\033[0;32mRunning PianoLibrary...\n\n\033[0m"
cd bin
printf "\n\033[0;32mRunning Application...\n\n\033[0m"
cd bin
./pianolib
cd ..
elif [[ "$1" == "gdb" ]]; then
printf "\n\033[0;32mDebugging Application...\n\n\033[0m"
cd bin
gdb -batch \
-ex 'set pagination off' \
-ex 'set print pretty on' \
-ex 'set logging file gdb-last.log' \
-ex 'set logging overwrite on' \
-ex 'set logging on' \
-ex 'handle SIGPIPE nostop noprint pass' \
-ex run \
-ex 'bt full' \
-ex 'info locals' \
-ex 'info args' \
-ex quit \
--args ./pianolib
cd ..
fi
fi

BIN
extra/res/icons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
extra/res/loading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,424 @@
% ================== DefaultTheme.oss ==================
% ==== Variables ====
const $accentColor = #2FB000FF
const $textColor = #CCEECCFF
const $borderColor = #111111FF
const $darkColor = #223322FF
const $accentColorLight = #87FF5CFF
const $accentColorDark = #3B7500FF
const $backgroundColor = rgba(30, 30, 30, 255)
const $crimsonAccent = rgba(220, 20, 60, 150)
const $blueAccent = #0075C7
% ===================
macro set_focus_border(color, width = 1, show = true) {
focusBorderColor = $color;
showFocusBorder = $show;
focusBorderWidth = $width;
}
macro set_common_values(_borderColor = $borderColor, _fontSize = 18, _showBorder = true) {
borderColor = $_borderColor;
fontSize = $_fontSize;
showBorder = $_showBorder;
}
% ====== Root Widget ======
(window) {
backgroundColor = $backgroundColor
(tooltip) {
backgroundColor = #FFF7D6FF
borderColor = rgb(30, 30, 30)
textColor = rgb(30, 30, 30)
borderWidth = 1
fontSize = 20
}
}
% =========================
% ====== Panel ======
(panel) {
set_common_values()
backgroundColor = $backgroundColor
titleColor = $accentColorDark
titlebarColor = $borderColor
titlebarBorderColor = $borderColor
borderRadius = 0
borderWidth = 2
titlebarBorderWidth = 2
showBackground = true
titlebarType = full
titlebarHeight = 35
titlebarFontSize = 18
titlebarTextAlign = text_left
scrollSpeed = 1.2
scrollSmoothFactor = 0.7
padding = rect(15, 15, 15, 15)
margin = rect(0, 0, 0, 0)
}
% ===================
% ====== TabPanel ======
(tabPanel) {
set_common_values()
backgroundColor = $backgroundColor
borderRadius = 0
borderWidth = 2
showBackground = true
margin = rect(0, 0, 0, 0)
textColor = $textColor
(tabBar) {
height = 35.0
backgroundColor = $darkColor
borderColor = $borderColor
borderWidth = 1
sidePadding = 20.0
}
}
(@panel_tab panel) {
}
% ======================
% ====== Scrollbar ======
(scrollbar) {
(thumb) {
color = $darkColor
borderRadius = 30.0
borderColor = $borderColor
showBorder = true
}
(track) {
color = #111111FF
}
width = 18.0
}
(scrollbar:hover) {
(thumb) {
color = $accentColorDark
}
}
(scrollbar:pressed) {
(thumb) {
color = #225500FF
}
(track) {
color = #001100FF
}
}
% =======================
% ====== CheckBox ======
(checkbox) {
set_common_values($borderColor, 18, false)
checkBorderColor = $accentColorDark
checkBoxColor = $accentColor
textColor = $textColor
borderRadius = 0
borderWidth = 0
checkBorderRadius = 6
checkBorderWidth = 2
showBackground = false
padding = rect(4, 4, 4, 4)
margin = rect(0, 0, 0, 0)
set_focus_border($blueAccent)
}
(checkbox:hover) {
checkBorderColor = $accentColorLight
}
(checkbox:pressed) {
checkBoxColor = $accentColor
textColor = $accentColorDark
checkBorderColor = $accentColor
}
(checkbox:active) {
checkBoxColor = $accentColor
checkBorderColor = $accentColor
textColor = $accentColorDark
}
% ======================
% ====== RadioButton ======
(radioButton) {
set_common_values($borderColor, 18, false)
innerCircleColor = $accentColorDark
outerCircleColor = $accentColor
outerCircleBorderWidth = 2
textColor = $textColor
showBackground = false
padding = rect(4, 4, 4, 4)
margin = rect(0, 0, 0, 0)
set_focus_border($blueAccent)
}
(radioButton:hover) {
outerCircleColor = $accentColorLight
}
(radioButton:pressed) {
outerCircleColor = $accentColor
textColor = $accentColorDark
innerCircleColor = $accentColor
}
(radioButton:active) {
innerCircleColor = $accentColor
outerCircleColor = $accentColor
textColor = $accentColorDark
}
% =========================
% ====== Label ======
(label) {
set_common_values($borderColor, 18, false)
textColor = $textColor
borderRadius = 0
borderWidth = 0
showBackground = false
padding = rect(15, 15, 15, 15)
margin = rect(0, 0, 0, 0)
}
% ===================
% ====== Button ======
(button) {
set_common_values($accentColorDark)
textColor = $textColor
backgroundGradient = gradientV($accentColor - $accentColorDark)
borderRadius = 0
borderWidth = 1
showBackground = false
useBackgroundGradient = true
padding = rect(5, 5, 5, 5)
margin = rect(0, 0, 0, 0)
showIcon = false
(icon) {
}
set_focus_border($blueAccent)
}
(button:hover) {
borderColor = $accentColorLight
}
(button:pressed) {
borderColor = $accentColorDark
textColor = #BBBBBBFF
backgroundGradient = gradientV($accentColorDark - $accentColor)
}
% ====================
% ====== Image ======
(image) {
}
% ===================
% ====== ProgressBar ======
(progressbar) {
set_common_values($accentColor)
borderWidth = 1
showDecimal = false
showText = true
useBackgroundGradient = true
backgroundGradient = gradientV($darkColor - $accentColorDark)
useProgressGradient = true
progressGradient = gradientV($accentColor - $accentColorDark)
}
% =========================
% ====== Slider ======
(slider) {
(handle) {
color = $accentColor
size = vec2(10, 18)
}
(track) {
color = $accentColorDark
progressColor = $accentColor
width = 6.0
}
showStepTicks = true
stepTickHeight = 6.0
stepTickColor = $accentColor
padding = rect(5, 5, 5, 5)
set_focus_border($blueAccent)
}
% ===================
% ====== Context Menu ======
(context) {
set_common_values()
padding = rect(16, 0, 16, 0)
itemSpacing = 6.0
backgroundColor = $darkColor
selectionColor = $accentColor
selectionTextColor = $accentColorLight
separatorLineColor = $borderColor
textColor = $textColor
submenuIndicatorColor = $accentColorDark
useSelectionGradient = true
selectionGradient = gradientV($accentColor - $accentColorDark)
animateOpen = true
animationDelay = 120
}
% ==========================
% ====== MenuBar ======
(menubar) {
set_common_values($borderColor, 16, false)
height = 24.0
itemPadding = rect(0, 0, 15, 0)
backgroundColor = $darkColor
textColor = $textColor
selectionColor = $accentColor
selectionTextColor = $accentColorLight
}
% =====================
% ====== ToolBar ======
(toolbar) {
set_common_values()
height = 30.0
backgroundColor = $darkColor
textColor = $textColor
selectionColor = $accentColor
selectionTextColor = $accentColorLight
showBackground = true
}
(@tool_button button) {
set_common_values(color_black, 18, false)
padding = rect(5, 0, 5, 0)
margin = rect(5, 0, 0, 0)
showBackground = true
backgroundColor = $darkColor
useBackgroundGradient = false
showIcon = true
(icon) {
tint = $accentColorLight
}
set_focus_border($blueAccent)
}
(@tool_button button:hover) {
borderColor = $textColor
backgroundColor = $accentColorDark
showBorder = true
(icon) {
tint = $textColor
}
}
(@tool_button button:pressed) {
borderColor = $accentColorDark
showBorder = true
(icon) {
tint = $accentColorLight
}
}
% =====================
% ====== ComboBox ======
(combo) {
set_common_values($accentColor)
backgroundColor = $darkColor
borderWidth = 1
showBackground = true
triangleIndicatorColor = $accentColor
triangleIndicatorPadding = 8.0
textColor = $textColor
}
(combo:hover) {
triangleIndicatorColor = $accentColorLight
borderColor = $accentColorLight
}
(combo:active) {
triangleIndicatorColor = $accentColorLight
borderColor = $accentColorLight
}
% ======================
% ====== TreeView ======
(tree) {
set_common_values($borderColor, 16, true)
linePadding = rect(5, 5, 0, 0)
selectionColor = $accentColor
selectionTextColor = color_white
iconSpacing = 0.0
iconInset = 4
showIcons = true
textColor = $textColor
backgroundColor = $darkColor
backgroundColor2 = $darkColor.lighten(0.06)
showAlternatingBackground = true
separatorLineColor = $borderColor
showSeparatorLine = false
iconTintColor = $accentColorLight
indentWidth = 16.0
chevronColor = $accentColorDark
chevronInset = 4.0
branchLineColor = $textColor
showBranchLines = true
branchLineThickness = 1.0
set_focus_border($blueAccent)
}
% ======================
% ====== TextEdit ======
(textEdit) {
set_common_values()
set_focus_border($blueAccent)
backgroundColor = $darkColor
textColor = $textColor
cursorBlink = true
cursorWidth = 2
cursorColor = $crimsonAccent
selectionColor = $blueAccent
cursorStyle = "line"
}
% ======================
% ====== ColorPicker ======
(colorPicker) {
set_common_values()
set_focus_border($blueAccent)
backgroundColor = $darkColor
padding = rect(8, 8, 8, 8)
stripWidth = 20
regionSpacing = 8
cursorRadius = 6
}
(colorButton) {
set_common_values()
set_focus_border($blueAccent)
}
% =========================

BIN
other/appIcon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

1
other/appIcon.rc Normal file
View file

@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "appIcon.ico"

View file

@ -1 +1 @@
2
4

1
other/deps.txt Normal file
View file

@ -0,0 +1 @@
mbedtls

100
src/Metadata.hpp Normal file
View file

@ -0,0 +1,100 @@
/*
PianoLibrary
Copyright (C) 2025 OmniaX-Dev
This file is part of PianoLibrary.
PianoLibrary is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PianoLibrary is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PianoLibrary. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "data.hpp"
#include <ostd/vendor/nlohmann/json.hpp>
namespace metadata
{
// Safe conversions — return fallback on any failure.
static float str_to_float(const std::string& s, float fallback = 0.0f)
{
try { return s.empty() ? fallback : std::stof(s); }
catch (...) { return fallback; }
}
static uint64_t str_to_u64(const std::string& s, uint64_t fallback = 0)
{
try { return s.empty() ? fallback : static_cast<uint64_t>(std::stoull(s)); }
catch (...) { return fallback; }
}
static uint32_t str_to_u32(const std::string& s, uint32_t fallback = 0)
{
try { return s.empty() ? fallback : static_cast<uint32_t>(std::stoul(s)); }
catch (...) { return fallback; }
}
// Parse the full list.json body into a PianoLibrary.
// Returns false on any structural error (missing "data", bad JSON, etc.).
static bool parsePianoLibrary(const std::string& jsonBody, data::PianoLibrary& out)
{
try
{
auto j = nlohmann::json::parse(jsonBody);
// Root-level metadata
out.generatedAt = j.value("generated-at", std::string(""));
if (j.contains("revision"))
{
if (j["revision"].is_number())
out.revision = j["revision"].get<uint64_t>();
else if (j["revision"].is_string())
out.revision = str_to_u64(j["revision"].get<std::string>());
}
if (!j.contains("data") || !j["data"].is_array())
return false;
out.performances.clear();
out.performances.reserve(j["data"].size());
for (const auto& e : j["data"])
{
data::Performance p;
p.date = e.value("date", std::string(""));
p.artist = e.value("artist", std::string(""));
p.title = e.value("title", std::string(""));
p.extraInfo = e.value("extra-info", std::string(""));
p.fileName = e.value("file-name", std::string(""));
p.midiFileName = e.value("midi-file-name", std::string(""));
p.id = e.value("ID", std::string(""));
p.performanceNumber = str_to_u32(e.value("performance-number", std::string("0")));
p.totalPerformances = str_to_u32(e.value("total-performances", std::string("0")));
p.fileSize = str_to_u64(e.value("file-size", std::string("0")));
p.duration = str_to_float(e.value("duration", std::string("0")));
p.soundStart = str_to_float(e.value("sound-start", std::string("0")));
out.performances.push_back(std::move(p));
}
return true;
}
catch (...)
{
return false;
}
}
}

51
src/data.hpp Normal file
View file

@ -0,0 +1,51 @@
/*
PianoLibrary
Copyright (C) 2025 OmniaX-Dev
This file is part of PianoLibrary.
PianoLibrary is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PianoLibrary is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PianoLibrary. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/data/Types.hpp>
#include <ostd/string/String.hpp>
namespace data
{
struct Performance
{
String date;
String artist;
String title;
String extraInfo;
String fileName;
String midiFileName;
String id;
uint32_t performanceNumber { 0 };
uint32_t totalPerformances { 0 };
uintmax_t fileSize { 0 };
float duration { 0.0f };
float soundStart { 0.0f };
};
struct PianoLibrary
{
String generatedAt;
uint64_t revision { 0 };
stdvec<Performance> performances;
};
}

View file

@ -1,52 +0,0 @@
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>
int main() {
cpr::Response r = cpr::Get(cpr::Url{"https://example.com/data.json"});
auto j = nlohmann::json::parse(r.text);
}
#include <ctime>
#include <cstring>
#include <string>
// Portable timegm: temporarily override TZ to UTC, call mktime, restore.
// Works on POSIX, MSVC, MinGW, MSYS2 — everywhere mktime exists.
static std::time_t portable_timegm(std::tm* tm)
{
#if defined(_WIN32) || defined(__MINGW32__)
// _mkgmtime is MSVC/UCRT's timegm equivalent, also available in MSYS2 UCRT64.
return _mkgmtime(tm);
#else
return timegm(tm);
#endif
}
std::string utcToLocal(const std::string& iso)
{
std::tm tm = {};
// Manual parse — avoids strptime, which MSVC lacks entirely.
// Expected format: "2026-06-20T14:00:00Z"
if (std::sscanf(iso.c_str(), "%d-%d-%dT%d:%d:%dZ",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
return iso; // Unparseable -> return as-is.
tm.tm_year -= 1900;
tm.tm_mon -= 1;
tm.tm_isdst = -1;
std::time_t utc = portable_timegm(&tm);
std::tm* local = std::localtime(&utc);
char buf[64];
std::strftime(buf, sizeof(buf), "%d %b %Y, %H:%M %Z", local);
return buf;
}

View file

@ -1,167 +1,258 @@
/*
PianoLibrary - A collection of useful functionality
Copyright (C) 2025 OmniaX-Dev
PianoLibrary
Copyright (C) 2025 OmniaX-Dev
This file is part of PianoLibrary.
This file is part of PianoLibrary.
PianoLibrary is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PianoLibrary is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PianoLibrary is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
PianoLibrary is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PianoLibrary. If not, see <https://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with PianoLibrary. If not, see <https://www.gnu.org/licenses/>.
*/
#include <ogfx/WindowBase.hpp>
#include <ogfx/BasicRenderer.hpp>
#include <ostd/io/IOHandlers.hpp>
#include <ostd/ostd.hpp>
#include <ostd/string/String.hpp>
#include <unordered_map>
#include <vector>
#include <ogfx/ogfx.hpp>
#include "vendor/httplib.h"
#include "Metadata.hpp"
#include "utc.hpp"
ostd::ConsoleOutputHandler out;
using namespace ogfx::gui;
struct PianoPerformance
{
ostd::String date { "" };
ostd::String artist { "" };
ostd::String name { "" };
ostd::String filePath { "" };
ostd::String extraInfo { "" };
uint32_t performanceNumber { 0 };
};
int date_to_int(const std::string& s)
{
int d, m, y;
sscanf(s.c_str(), "%d.%d.%d", &d, &m, &y);
return y * 10000 + m * 100 + d; // YYYYMMDD
}
class Window : public ogfx::WindowBase
class TestWindow : public Window
{
public:
inline Window(void) { }
inline TestWindow(void) { }
inline void onInitialize(void) override
{
enableSignals();
connectSignal(ostd::tBuiltinSignals::KeyReleased);
m_theme.loadFromFile("./themes/DefaultThemeDark.oss", true, getDefaultStylesheetVariableList());
setTheme(m_theme);
m_gfx.init(*this);
m_gfx.setFont("res/ttf/Courier Prime.ttf");
getToolBar().setHeight(30);
getToolBar().setLayout<BoxLayout>(BoxLayout::Orientation::Horizontal);
getToolBar().getLayout()->setSpacing(10);
getStatusBar().setHeight(24);
showMenuBar();
showToolBar();
showStatusBar();
std::unordered_map<ostd::String, std::vector<PianoPerformance>> pianoPerformances;
ogfx::AnimationData iconsAD;
iconsAD.frameCount = 1;
iconsAD.fps = 1;
iconsAD.columns = 8;
iconsAD.rows = 5;
iconsAD.frameWidth = 64;
iconsAD.frameHeight = 64;
iconsAD.spacingX = 50;
iconsAD.spacingY = 48;
iconsAD.still = true;
auto musicList = ostd::FileSystem::listFilesInDirectoryRecursive("/home/sylar/Desktop/Music/");
for (const auto& path : musicList)
auto l_randomIcon = [this](const ogfx::AnimationData& src) -> ogfx::Icon {
static ogfx::Image img("./res/icons.png", getGFX());
ogfx::AnimationData ad = src;
ad.columnOffset = ostd::Random::getui32(0, 7);
ad.rowOffset = ostd::Random::getui32(0, 4);
if (ad.rowOffset == 3)
ad.columnOffset = ostd::Random::getui32(0, 4);
return { img, ad };
};
for (i32 i = 0; i < 17; i++)
{
ostd::String p = path;
p.trim();
if (p.contains("/"))
p.substr(p.lastIndexOf("/") + 1);
PianoPerformance pp;
auto tokens = p.tokenize("-");
if (tokens.hasNext())
pp.date = tokens.next();
if (tokens.hasNext())
pp.artist = tokens.next();
if (tokens.hasNext())
pp.name = tokens.next();
if (tokens.hasNext())
{
ostd::String tmp = tokens.next();
if (!tmp.isNumeric())
{
int32_t i = 0;
for ( ; i < tmp.len(); i++)
{
char c = tmp[i];
if (c >= '0' && c <= '9')
continue;
break;
}
if (i == 0)
pp.performanceNumber = 0;
else
pp.performanceNumber = tmp.substr(0, i).toInt();
}
else
pp.performanceNumber = tmp.toInt();
}
while (tokens.hasNext())
pp.extraInfo += tokens.next() + " ";
pianoPerformances[pp.date].push_back(pp);
auto icon = l_randomIcon(iconsAD);
auto& btn = getToolBar().addButton("./res/icons.png");
btn.enableAnimated();
btn.setAnimationData(icon);
btn.setTabIndex(i + 100);
}
std::vector<std::pair<std::string, std::vector<PianoPerformance>>> sortedPerformances(pianoPerformances.begin(), pianoPerformances.end());
m_tabs.setSize(900, 700);
m_tabs.addThemeOverride("@.tabPanel.showBorder", false);
auto& t1 = m_tabs.addTab("Tab1");
auto& t2 = m_tabs.addTab("Tab2 Test");
auto& t3 = m_tabs.addTab("Long Tab Test");
std::sort(sortedPerformances.begin(), sortedPerformances.end(),
[](auto& a, auto& b) {
return date_to_int(a.first) < date_to_int(b.first);
}
);
addWidget(m_tabs, { 0, 0 });
getToolBar().reloadTheme(true);
for (auto[date, performances] : sortedPerformances)
{
out.fg(ostd::ConsoleColors::BrightBlue).p(date).nl();
for (const auto& perf : performances)
auto onActivate = [this](const ContextMenu::Entry& e) {
out().fg("cyan").p("[MenuBar] Activated: ").fg("yellow").p(e.text)
.fg("cyan").p(" (id=").fg("green").p(e.id).fg("cyan").p(")").reset().nl();
switch (e.id)
{
out.fg(ostd::ConsoleColors::BrightYellow).p(" ").p(perf.artist).p("\t");
out.fg(ostd::ConsoleColors::BrightGray).p(perf.name).p(" ");
out.fg(ostd::ConsoleColors::BrightRed).p("(").p(perf.performanceNumber).p(")").nl();
case TestMenuId::File_Exit:
close();
break;
// ... add real handlers as you build them out
default:
break;
}
out.nl().nl();
}
out.reset();
};
close();
}
fileMenu.onActivate = onActivate;
editMenu.onActivate = onActivate;
viewMenu.onActivate = onActivate;
helpMenu.onActivate = onActivate;
inline void handleSignal(ostd::tSignal& signal) override
getMenuBar().addMenu("File", fileMenu)
.addMenu("Edit", editMenu)
.addMenu("View", viewMenu)
.addMenu("Help", helpMenu);
}
inline void onSignal(ostd::Signal& signal) override
{
if (signal.ID == ostd::tBuiltinSignals::KeyReleased)
if (signal.ID == ostd::BuiltinSignals::KeyReleased)
{
auto& evtData = (ogfx::KeyEventData&)signal.userData;
if (evtData.keyCode == SDLK_ESCAPE)
if (evtData.keyCode == ogfx::KeyCode::Escape)
close();
}
else if (signal.ID == ostd::BuiltinSignals::WindowResized)
{
auto& wrd = cast<ogfx::WindowResizedData&>(signal.userData);
m_tabs.setSize(cast<f32>(getWindowWidth()), cast<f32>(getWindowHeight() - getMenuBar().geth() - getToolBar().geth() - getStatusBar().geth()));
m_tabs.setPosition(0, -1);
m_tabs.refreshCurrentTab();
}
}
inline void onRender(void) override
void onRedraw(ogfx::BasicRenderer2D& gfx) override
{
}
inline void onFixedUpdate(double frameTime_s) override
{
}
inline void onUpdate(void) override
void onFixedUpdate(void) override
{
}
private:
ogfx::BasicRenderer2D m_gfx;
TabPanel m_tabs { *this };
enum TestMenuId : i32
{
// File
File_New = 1,
File_Open,
File_OpenRecent_Project1,
File_OpenRecent_Project2,
File_OpenRecent_Project3,
File_OpenRecent_ClearList,
File_Save,
File_SaveAs,
File_Export_PNG,
File_Export_JPG,
File_Export_SVG,
File_Export_PDF,
File_Exit,
// Edit
Edit_Undo,
Edit_Redo,
Edit_Cut,
Edit_Copy,
Edit_Paste,
Edit_SelectAll,
// View
View_ZoomIn,
View_ZoomOut,
View_ZoomReset,
View_Theme_Dark,
View_Theme_Light,
View_Theme_Crimson,
// Help
Help_Documentation,
Help_About,
};
ContextMenu::Instance fileMenu { {
{ "New", TestMenuId::File_New },
{ "Open...", TestMenuId::File_Open },
{ "Open Recent", {
{ "Project1.proj", TestMenuId::File_OpenRecent_Project1 },
{ "Project2.proj", TestMenuId::File_OpenRecent_Project2 },
{ "Project3.proj", TestMenuId::File_OpenRecent_Project3 },
{ "Clear Recent List", TestMenuId::File_OpenRecent_ClearList },
}},
{ "Save", TestMenuId::File_Save },
{ "Save As...",TestMenuId::File_SaveAs },
{ "Export As", {
{ "PNG Image", TestMenuId::File_Export_PNG },
{ "JPG Image", TestMenuId::File_Export_JPG },
{ "SVG Vector",TestMenuId::File_Export_SVG },
{ "PDF Document", TestMenuId::File_Export_PDF },
}},
{ "Exit", TestMenuId::File_Exit } },
nullptr
};
ContextMenu::Instance editMenu { {
{ "Undo", TestMenuId::Edit_Undo },
{ "Redo", TestMenuId::Edit_Redo },
{ "Cut", TestMenuId::Edit_Cut },
{ "Copy", TestMenuId::Edit_Copy },
{ "Paste", TestMenuId::Edit_Paste },
{ "Select All", TestMenuId::Edit_SelectAll } },
nullptr
};
ContextMenu::Instance viewMenu { {
{ "Zoom In", TestMenuId::View_ZoomIn },
{ "Zoom Out", TestMenuId::View_ZoomOut },
{ "Reset Zoom", TestMenuId::View_ZoomReset },
{ "Theme", {
{ "Dark", TestMenuId::View_Theme_Dark },
{ "Light", TestMenuId::View_Theme_Light },
{ "Crimson", TestMenuId::View_Theme_Crimson },
}} },
nullptr
};
ContextMenu::Instance helpMenu { {
{ "Documentation", TestMenuId::Help_Documentation },
{ "About", TestMenuId::Help_About } },
nullptr
};
ostd::Stylesheet m_theme;
};
int main(int argc, char** argv)
{
Window window;
window.initialize(1280, 720, "PianoLibrary");
window.setClearColor({ 0, 2 , 15 });
while (window.isRunning())
void request() {
httplib::Client cli("https://timeline.keylightpiano.com");
auto res = cli.Get("/music/list.json");
if (res && res->status == 200)
{
window.update();
}
data::PianoLibrary library;
if (metadata::parsePianoLibrary(res->body, library))
{
std::string localTime = utc::toLocal(library.generatedAt);
printf("Revision %lu, generated %s\n", library.revision, localTime.c_str());
printf("%zu performances loaded\n", library.performances.size());
for (const auto& p : library.performances)
printf(" %s - %s (%.1fs)\n", p.artist.c_str(), p.title.c_str(), p.duration);
}
}
}
i32 main(i32 argc, char** argv)
{
request();
ostd::Random::autoSeed();
TestWindow window;
window.initialize(960, 540, "PianoLibrary");
window.setClearColor({ 0, 0, 0 });
window.setPosition({ 50, 50 });
// window.setWindowState(ogfx::gui::Window::eWindowState::Maximized);
window.mainLoop();
return 0;
}

61
src/utc.hpp Normal file
View file

@ -0,0 +1,61 @@
/*
PianoLibrary
Copyright (C) 2025 OmniaX-Dev
This file is part of PianoLibrary.
PianoLibrary is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PianoLibrary is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PianoLibrary. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/string/String.hpp>
namespace utc
{
// Portable timegm: temporarily override TZ to UTC, call mktime, restore.
// Works on POSIX, MSVC, MinGW, MSYS2 — everywhere mktime exists.
static std::time_t portable_timegm(std::tm* tm)
{
#if defined(_WIN32) || defined(__MINGW32__)
// _mkgmtime is MSVC/UCRT's timegm equivalent, also available in MSYS2 UCRT64.
return _mkgmtime(tm);
#else
return timegm(tm);
#endif
}
static String toLocal(const String& iso)
{
std::tm tm = {};
// Manual parse — avoids strptime, which MSVC lacks entirely.
// Expected format: "2026-06-20T14:00:00Z"
if (std::sscanf(iso.c_str(), "%d-%d-%dT%d:%d:%dZ",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
return iso; // Unparseable -> return as-is.
tm.tm_year -= 1900;
tm.tm_mon -= 1;
tm.tm_isdst = -1;
std::time_t utc = portable_timegm(&tm);
std::tm* local = std::localtime(&utc);
char buf[64];
std::strftime(buf, sizeof(buf), "%d %b %Y, %H:%M %Z", local);
return buf;
}
}

20588
src/vendor/httplib.h vendored Normal file

File diff suppressed because it is too large Load diff