Add android support, and other changes.
This commit is contained in:
parent
0d236857b8
commit
6159f52e8a
40 changed files with 815 additions and 260 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -25,3 +25,6 @@
|
|||
[submodule "subprojects/libintl-lite"]
|
||||
path = subprojects/libintl-lite
|
||||
url = https://github.com/hathlife/libintl-lite.git
|
||||
[submodule "subprojects/oboe"]
|
||||
path = subprojects/oboe
|
||||
url = https://github.com/google/oboe.git
|
||||
|
|
|
@ -249,6 +249,11 @@ else()
|
|||
set(LIBINTL_LIBRARY Intl::Intl CACHE INTERNAL "")
|
||||
set(LIBINTL_INCDIRS ${Intl_INCLUDE_DIRS} CACHE INTERNAL "")
|
||||
endif()
|
||||
|
||||
if (DEFINED ANDROID_NDK)
|
||||
add_subdirectory(subprojects/oboe)
|
||||
target_link_libraries(liblooper PUBLIC oboe)
|
||||
endif()
|
||||
target_include_directories(liblooper PUBLIC ${LIBINTL_INCDIRS})
|
||||
target_link_libraries(liblooper PUBLIC ${LIBINTL_LIBRARY})
|
||||
if(DEFINED EMSCRIPTEN)
|
||||
|
@ -264,7 +269,7 @@ else()
|
|||
pkg_check_modules(jsoncpp IMPORTED_TARGET jsoncpp)
|
||||
endif()
|
||||
if (NOT BUILD_SDL)
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(SDL2 REQUIRED)
|
||||
endif()
|
||||
if (ENABLE_DBUS)
|
||||
find_package(sdbus-c++)
|
||||
|
@ -273,7 +278,11 @@ else()
|
|||
message("Warning: Dbus support not found - Not enabling DBus")
|
||||
endif()
|
||||
endif()
|
||||
target_link_libraries(liblooper PUBLIC SDL2::SDL2 SDL2-static SDL2main ${SDL_MIXER_X_TARGET} ${SOUNDTOUCH_TARGET} libvgmstream libvgmstream_shared ${JSONCPP_TARGET})
|
||||
set(SDL2_TARGET SDL2::SDL2)
|
||||
if (TARGET SDL2-static)
|
||||
set(SDL2_TARGET SDL2-static)
|
||||
endif()
|
||||
target_link_libraries(liblooper PUBLIC ${SDL2_TARGET} SDL2main ${SDL_MIXER_X_TARGET} ${SOUNDTOUCH_TARGET} libvgmstream libvgmstream_shared ${JSONCPP_TARGET})
|
||||
endif()
|
||||
if (${ENABLE_DBUS})
|
||||
target_link_libraries(liblooper PUBLIC SDBusCpp::sdbus-c++)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<arg type='a{sv}' name='platform_data' direction='in'/>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="com.complecwaft.Looper">
|
||||
<interface name="com.complecwaft.looper">
|
||||
<method name="CreateHandle">
|
||||
<arg type='s' name='new_handle' direction='out' />
|
||||
</method>
|
||||
|
@ -81,7 +81,7 @@
|
|||
<property name="IsDaemon" type="b" access="read" />
|
||||
<property name="StreamIdx" type="u" access="read" />
|
||||
</interface>
|
||||
<interface name="com.complecwaft.Looper.Errors" >
|
||||
<interface name="com.complecwaft.looper.Errors" >
|
||||
<method name="PopFront">
|
||||
<arg name="handle" direction="in" type="s" />
|
||||
<arg name="error" direction="out" type="s" />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.complecwaft.Looper</id>
|
||||
<id>com.complecwaft.looper</id>
|
||||
<developer_name>Catmeow72</developer_name>
|
||||
|
||||
<name>Looper</name>
|
||||
|
|
|
@ -48,13 +48,13 @@ private:
|
|||
namespace com {
|
||||
namespace complecwaft {
|
||||
|
||||
class Looper_adaptor
|
||||
class looper_adaptor
|
||||
{
|
||||
public:
|
||||
static constexpr const char* INTERFACE_NAME = "com.complecwaft.Looper";
|
||||
static constexpr const char* INTERFACE_NAME = "com.complecwaft.looper";
|
||||
|
||||
protected:
|
||||
Looper_adaptor(sdbus::IObject& object)
|
||||
looper_adaptor(sdbus::IObject& object)
|
||||
: object_(&object)
|
||||
{
|
||||
object_->registerMethod("CreateHandle").onInterface(INTERFACE_NAME).withOutputParamNames("new_handle").implementedAs([this](){ return this->CreateHandle(); });
|
||||
|
@ -90,12 +90,12 @@ protected:
|
|||
object_->registerProperty("StreamIdx").onInterface(INTERFACE_NAME).withGetter([this](){ return this->StreamIdx(); });
|
||||
}
|
||||
|
||||
Looper_adaptor(const Looper_adaptor&) = delete;
|
||||
Looper_adaptor& operator=(const Looper_adaptor&) = delete;
|
||||
Looper_adaptor(Looper_adaptor&&) = default;
|
||||
Looper_adaptor& operator=(Looper_adaptor&&) = default;
|
||||
looper_adaptor(const looper_adaptor&) = delete;
|
||||
looper_adaptor& operator=(const looper_adaptor&) = delete;
|
||||
looper_adaptor(looper_adaptor&&) = default;
|
||||
looper_adaptor& operator=(looper_adaptor&&) = default;
|
||||
|
||||
~Looper_adaptor() = default;
|
||||
~looper_adaptor() = default;
|
||||
|
||||
public:
|
||||
void emitPlaybackEngineStarted()
|
||||
|
@ -183,12 +183,12 @@ private:
|
|||
|
||||
namespace com {
|
||||
namespace complecwaft {
|
||||
namespace Looper {
|
||||
namespace looper {
|
||||
|
||||
class Errors_adaptor
|
||||
{
|
||||
public:
|
||||
static constexpr const char* INTERFACE_NAME = "com.complecwaft.Looper.Errors";
|
||||
static constexpr const char* INTERFACE_NAME = "com.complecwaft.looper.Errors";
|
||||
|
||||
protected:
|
||||
Errors_adaptor(sdbus::IObject& object)
|
||||
|
|
|
@ -56,13 +56,13 @@ private:
|
|||
namespace com {
|
||||
namespace complecwaft {
|
||||
|
||||
class Looper_proxy
|
||||
class looper_proxy
|
||||
{
|
||||
public:
|
||||
static constexpr const char* INTERFACE_NAME = "com.complecwaft.Looper";
|
||||
static constexpr const char* INTERFACE_NAME = "com.complecwaft.looper";
|
||||
|
||||
protected:
|
||||
Looper_proxy(sdbus::IProxy& proxy)
|
||||
looper_proxy(sdbus::IProxy& proxy)
|
||||
: proxy_(&proxy)
|
||||
{
|
||||
proxy_->uponSignal("PlaybackEngineStarted").onInterface(INTERFACE_NAME).call([this](){ this->onPlaybackEngineStarted(); });
|
||||
|
@ -76,12 +76,12 @@ protected:
|
|||
proxy_->uponSignal("FileChanged").onInterface(INTERFACE_NAME).call([this](const std::string& path, const std::string& title){ this->onFileChanged(path, title); });
|
||||
}
|
||||
|
||||
Looper_proxy(const Looper_proxy&) = delete;
|
||||
Looper_proxy& operator=(const Looper_proxy&) = delete;
|
||||
Looper_proxy(Looper_proxy&&) = default;
|
||||
Looper_proxy& operator=(Looper_proxy&&) = default;
|
||||
looper_proxy(const looper_proxy&) = delete;
|
||||
looper_proxy& operator=(const looper_proxy&) = delete;
|
||||
looper_proxy(looper_proxy&&) = default;
|
||||
looper_proxy& operator=(looper_proxy&&) = default;
|
||||
|
||||
~Looper_proxy() = default;
|
||||
~looper_proxy() = default;
|
||||
|
||||
virtual void onPlaybackEngineStarted() = 0;
|
||||
virtual void onSpeedChanged(const double& new_speed) = 0;
|
||||
|
@ -247,12 +247,12 @@ private:
|
|||
|
||||
namespace com {
|
||||
namespace complecwaft {
|
||||
namespace Looper {
|
||||
namespace looper {
|
||||
|
||||
class Errors_proxy
|
||||
{
|
||||
public:
|
||||
static constexpr const char* INTERFACE_NAME = "com.complecwaft.Looper.Errors";
|
||||
static constexpr const char* INTERFACE_NAME = "com.complecwaft.looper.Errors";
|
||||
|
||||
protected:
|
||||
Errors_proxy(sdbus::IProxy& proxy)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "backend.hpp"
|
||||
#include "backends/ui/imgui/ui_backend.hpp"
|
||||
#include "backends/ui/gtk/main.h"
|
||||
|
||||
void init_backends() {
|
||||
UIBackend::register_backend<ImGuiUIBackend>();
|
||||
UIBackend::register_backend<GtkBackend>();
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@ else()
|
|||
set(GLES_NORMALLY_REQUIRED_FOR_ARCHITECTURE OFF)
|
||||
endif()
|
||||
option(USE_GLES "Enable using OpenGL ES" ${GLES_NORMALLY_REQUIRED_FOR_ARCHITECTURE})
|
||||
option(GLES_VERSION "Version of OpenGL ES" 3)
|
||||
set(IMGUI_SRC imgui_demo.cpp imgui_draw.cpp imgui_tables.cpp imgui_widgets.cpp imgui.cpp misc/cpp/imgui_stdlib.cpp)
|
||||
set(IMGUI_BACKEND_SRC imgui_impl_opengl3.cpp imgui_impl_sdl2.cpp)
|
||||
set(IMGUI_BACKEND_SRC imgui_impl_sdlrenderer2.cpp imgui_impl_sdl2.cpp)
|
||||
set(BACKEND_IMGUI_SRC_BASE main.cpp base85.cpp file_browser.cpp main.cpp RendererBackend.cpp theme.cpp)
|
||||
foreach(SRC IN ITEMS ${IMGUI_BACKEND_SRC})
|
||||
list(APPEND IMGUI_SRC backends/${SRC})
|
||||
|
@ -26,8 +27,8 @@ foreach(INCDIR IN ITEMS ${BACKEND_IMGUI_INC_BASE})
|
|||
set(BACKEND_IMGUI_INC ${BACKEND_IMGUI_INC} ${CMAKE_CURRENT_SOURCE_DIR}/${INCDIR})
|
||||
endforeach()
|
||||
if(USE_GLES)
|
||||
set(GLComponents GLES2)
|
||||
set(GLTarget GLES2)
|
||||
set(GLComponents GLES${GLES_VERSION})
|
||||
set(GLTarget GLES${GLES_VERSION})
|
||||
else()
|
||||
set(GLComponents OpenGL)
|
||||
set(GLTarget GL)
|
||||
|
@ -36,9 +37,9 @@ find_package(OpenGL COMPONENTS ${GLComponents})
|
|||
if (NOT ${OpenGL_FOUND})
|
||||
if (DEFINED CMAKE_ANDROID_NDK)
|
||||
if (USE_GLES)
|
||||
find_path(GLES2_INCLUDE_DIR GLES2/gl2.h HINTS ${CMAKE_ANDROID_NDK})
|
||||
find_library(GLES2_LIBRARY libGLESv2.so HINTS ${GLES2_INCLUDE_DIR}/../lib)
|
||||
find_library(GLES3_LIBRARY libGLESv3.so HINTS ${GLES2_INCLUDE_DIR}/../lib)
|
||||
find_path(GLES_INCLUDE_DIR GLES${GLES_VERSION}/gl${GLES_VERSION}.h HINTS ${CMAKE_ANDROID_NDK})
|
||||
find_library(GLES2_LIBRARY libGLESv2.so HINTS ${GLES_INCLUDE_DIR}/../lib)
|
||||
find_library(GLES3_LIBRARY libGLESv3.so HINTS ${GLES_INCLUDE_DIR}/../lib)
|
||||
add_library(OpenGL::${GLTarget} INTERFACE IMPORTED)
|
||||
target_include_directories(OpenGL::${GLTarget} INTERFACE ${GLES2_INCLUDE_DIR})
|
||||
target_link_libraries(OpenGL::${GLTarget} INTERFACE ${GLES2_LIBRARY} ${GLES3_LIBRARY})
|
||||
|
@ -47,7 +48,7 @@ if (NOT ${OpenGL_FOUND})
|
|||
endif()
|
||||
add_ui_backend(imgui_ui ${BACKEND_IMGUI_SRC})
|
||||
if(USE_GLES)
|
||||
target_compile_definitions(imgui_ui PRIVATE IMGUI_IMPL_OPENGL_ES2=1)
|
||||
target_compile_definitions(imgui_ui PRIVATE IMGUI_IMPL_OPENGL_ES${GLES_VERSION})
|
||||
endif()
|
||||
if(DEFINED EMSCRIPTEN)
|
||||
target_compile_options(imgui_ui PRIVATE "-sUSE_SDL_IMAGE=2")
|
||||
|
|
|
@ -5,18 +5,23 @@
|
|||
#include "config.h"
|
||||
#include <SDL_image.h>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <initializer_list>
|
||||
#include "theme.h"
|
||||
#include "imgui_stdlib.h"
|
||||
#include "imgui_impl_sdl2.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include "imgui_impl_sdlrenderer2.h"
|
||||
#include "base85.h"
|
||||
#include <thread>
|
||||
#include <translation.hpp>
|
||||
#include <log.hpp>
|
||||
#include <options.hpp>
|
||||
using std::vector;
|
||||
using namespace Looper::Options;
|
||||
#ifdef __EMSCRIPTEN__
|
||||
extern "C" {
|
||||
extern void get_size(int32_t *x, int32_t *y);
|
||||
extern double get_dpi();
|
||||
}
|
||||
#endif
|
||||
void RendererBackend::on_resize() {
|
||||
|
@ -24,6 +29,7 @@ void RendererBackend::on_resize() {
|
|||
int32_t x, y;
|
||||
get_size(&x, &y);
|
||||
SDL_SetWindowSize(window, (int)x, (int)y);
|
||||
UpdateScale();
|
||||
#endif
|
||||
}
|
||||
static RendererBackend *renderer_backend;
|
||||
|
@ -40,14 +46,41 @@ void main_loop() {
|
|||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
struct FontData {
|
||||
const ImWchar *ranges;
|
||||
std::map<std::string, std::pair<const char *, double>> data;
|
||||
void Init(const ImWchar *ranges_in, std::map<std::string, std::pair<const char *, double>> data_in) {
|
||||
ranges = ranges_in;
|
||||
data = data_in;
|
||||
}
|
||||
FontData(const ImWchar *ranges, std::initializer_list<std::pair<std::string, std::pair<const char*, double>>> data) {
|
||||
std::map<std::string, std::pair<const char*, double>> out_data;
|
||||
for (auto pair : data) {
|
||||
out_data[pair.first] = pair.second;
|
||||
}
|
||||
Init(ranges, out_data);
|
||||
}
|
||||
FontData(const ImWchar *ranges, std::initializer_list<std::tuple<std::string, const char*, double>> data) {
|
||||
std::map<std::string, std::pair<const char*, double>> out_data;
|
||||
for (auto tuple : data) {
|
||||
std::pair<const char*, double> outPair = {std::get<1>(tuple), std::get<2>(tuple)};
|
||||
out_data[std::get<0>(tuple)] = outPair;
|
||||
}
|
||||
Init(ranges, out_data);
|
||||
}
|
||||
FontData(const ImWchar *ranges, std::map<std::string, std::pair<const char*, double>> data) {
|
||||
Init(ranges, data);
|
||||
}
|
||||
};
|
||||
void RendererBackend::BackendDeinit() {
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
// Cleanup
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplSDLRenderer2_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
SDL_GL_DeleteContext(gl_context);
|
||||
SDL_DestroyRenderer(rend);
|
||||
SDL_DestroyWindow(window);
|
||||
IMG_Quit();
|
||||
SDL_Quit();
|
||||
|
@ -55,7 +88,11 @@ void RendererBackend::BackendDeinit() {
|
|||
Deinit();
|
||||
renderer_backend = nullptr;
|
||||
}
|
||||
bool RendererBackend::isTouchScreenMode() {
|
||||
return touchScreenModeOverride.value_or(SDL_GetNumTouchDevices() > 0);
|
||||
}
|
||||
void RendererBackend::LoopFunction() {
|
||||
SDL_RenderSetVSync(rend, vsync ? 1 : 0);
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
if (resize_needed) {
|
||||
on_resize();
|
||||
|
@ -92,78 +129,71 @@ void RendererBackend::LoopFunction() {
|
|||
}
|
||||
}
|
||||
}
|
||||
bool touchScreenMode = isTouchScreenMode();
|
||||
if (touchScreenMode) {
|
||||
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
||||
} else {
|
||||
io.ConfigFlags &= ~ImGuiConfigFlags_IsTouchScreen;
|
||||
}
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplSDLRenderer2_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
// Run the GUI
|
||||
GuiFunction();
|
||||
// Rendering
|
||||
ImGui::Render();
|
||||
// Update the window size.
|
||||
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
|
||||
// Clear the screen.
|
||||
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
SDL_SetRenderDrawColor(rend, (Uint8)(clear_color.x * clear_color.w * 255), (Uint8)(clear_color.y * clear_color.w * 255), (Uint8)(clear_color.z * clear_color.w * 255), (Uint8)(clear_color.w * 255));
|
||||
SDL_RenderClear(rend);
|
||||
// Tell ImGui to render.
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
// Update and Render additional Platform Windows
|
||||
// (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere.
|
||||
// For this specific demo app we could also call SDL_GL_MakeCurrent(window, gl_context) directly)
|
||||
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
||||
{
|
||||
SDL_Window* backup_current_window = SDL_GL_GetCurrentWindow();
|
||||
SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext();
|
||||
ImGui::UpdatePlatformWindows();
|
||||
ImGui::RenderPlatformWindowsDefault();
|
||||
SDL_GL_MakeCurrent(backup_current_window, backup_current_context);
|
||||
}
|
||||
|
||||
// Swap the buffers, and do VSync if enabled.
|
||||
SDL_GL_SwapWindow(window);
|
||||
SDL_RenderPresent(rend);
|
||||
// If not doing VSync, wait until the next frame needs to be rendered.
|
||||
if (!vsync) {
|
||||
std::this_thread::sleep_until(next_frame);
|
||||
}
|
||||
}
|
||||
struct FontData {
|
||||
const char* data;
|
||||
const ImWchar *ranges;
|
||||
};
|
||||
|
||||
ImFont *add_font(vector<FontData> data_vec, int size = 13) {
|
||||
ImFont* font = nullptr;
|
||||
std::map<std::string, ImFont *> add_font(FontData data_vec, double scale) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
for (auto data : data_vec) {
|
||||
ImFontConfig font_cfg = ImFontConfig();
|
||||
font_cfg.SizePixels = size;
|
||||
font_cfg.OversampleH = font_cfg.OversampleV = 1;
|
||||
font_cfg.PixelSnapH = true;
|
||||
if (font_cfg.SizePixels <= 0.0f)
|
||||
font_cfg.SizePixels = 13.0f * 1.0f;
|
||||
if (font != nullptr) {
|
||||
font_cfg.DstFont = font;
|
||||
font_cfg.MergeMode = true;
|
||||
}
|
||||
//font_cfg.EllipsisChar = (ImWchar)0x0085;
|
||||
//font_cfg.GlyphOffset.y = 1.0f * IM_FLOOR(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units
|
||||
std::map<std::string, ImFont*> output;
|
||||
for (auto value : data_vec.data) {
|
||||
ImFont* font = nullptr;
|
||||
std::string id = value.first;
|
||||
const char *data = value.second.first;
|
||||
double size = value.second.second * scale;
|
||||
{
|
||||
ImFontConfig font_cfg = ImFontConfig();
|
||||
font_cfg.SizePixels = size;
|
||||
font_cfg.OversampleH = font_cfg.OversampleV = 1;
|
||||
font_cfg.PixelSnapH = true;
|
||||
if (font_cfg.SizePixels <= 0.0f)
|
||||
font_cfg.SizePixels = 13.0f * 1.0f;
|
||||
//font_cfg.EllipsisChar = (ImWchar)0x0085;
|
||||
//font_cfg.GlyphOffset.y = 1.0f * IM_FLOOR(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units
|
||||
|
||||
const char* ttf_compressed_base85 = data.data;
|
||||
const ImWchar* glyph_ranges = data.ranges;
|
||||
auto new_font = io.Fonts->AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges);
|
||||
if (font == nullptr) font = new_font;
|
||||
const char *ttf_compressed_base85 = data;
|
||||
const ImWchar *glyph_ranges = data_vec.ranges;
|
||||
font = io.Fonts->AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85,
|
||||
font_cfg.SizePixels,
|
||||
&font_cfg, glyph_ranges);
|
||||
}
|
||||
{
|
||||
ImFontConfig config;
|
||||
config.MergeMode = true;
|
||||
config.GlyphMinAdvanceX = size;
|
||||
config.SizePixels = size;
|
||||
config.DstFont = font;
|
||||
static const ImWchar icon_ranges[] = {ICON_MIN_FK, ICON_MAX_FK, 0};
|
||||
io.Fonts->AddFontFromMemoryCompressedBase85TTF(forkawesome_compressed_data_base85,
|
||||
float(size), &config, icon_ranges);
|
||||
}
|
||||
output[id] = font;
|
||||
}
|
||||
{
|
||||
ImFontConfig config;
|
||||
config.MergeMode = true;
|
||||
config.GlyphMinAdvanceX = size;
|
||||
config.SizePixels = size;
|
||||
config.DstFont = font;
|
||||
static const ImWchar icon_ranges[] = { ICON_MIN_FK, ICON_MAX_FK, 0 };
|
||||
io.Fonts->AddFontFromMemoryCompressedBase85TTF(forkawesome_compressed_data_base85, float(size), &config, icon_ranges);
|
||||
}
|
||||
return font;
|
||||
return output;
|
||||
}
|
||||
RendererBackend::RendererBackend() {
|
||||
}
|
||||
|
@ -184,6 +214,9 @@ void RendererBackend::UpdateScale() {
|
|||
#else
|
||||
96.0;
|
||||
#endif
|
||||
#ifdef __EMSCRIPTEN__
|
||||
scale = get_dpi() / defaultDPI;
|
||||
#else
|
||||
float dpi = defaultDPI;
|
||||
if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(window), NULL, &dpi, NULL) == 0){
|
||||
scale = dpi / defaultDPI;
|
||||
|
@ -191,13 +224,18 @@ void RendererBackend::UpdateScale() {
|
|||
WARNING.writeln("DPI couldn't be determined!");
|
||||
scale = 1.0;
|
||||
}
|
||||
#ifndef __ANDROID__
|
||||
SDL_SetWindowSize(window, window_width * scale, window_height * scale);
|
||||
#endif
|
||||
#endif
|
||||
AddFonts();
|
||||
}
|
||||
void RendererBackend::SetWindowSize(int w, int h) {
|
||||
#if !(defined(__ANDROID__) || defined(__EMSCRIPTEN__))
|
||||
window_width = w;
|
||||
window_height = h;
|
||||
SDL_SetWindowSize(window, w * scale, h * scale);
|
||||
#endif
|
||||
}
|
||||
void RendererBackend::GetWindowsize(int *w, int *h) {
|
||||
int ww, wh;
|
||||
|
@ -208,12 +246,25 @@ void RendererBackend::GetWindowsize(int *w, int *h) {
|
|||
if (h) *h = wh;
|
||||
}
|
||||
void RendererBackend::AddFonts() {
|
||||
ImGui_ImplOpenGL3_DestroyFontsTexture();
|
||||
ImGui_ImplSDLRenderer2_DestroyFontsTexture();
|
||||
auto& io = ImGui::GetIO(); (void)io;
|
||||
io.Fonts->Clear();
|
||||
add_font(vector<FontData>({FontData {notosans_regular_compressed_data_base85, io.Fonts->GetGlyphRangesDefault()}, FontData {notosansjp_regular_compressed_data_base85, io.Fonts->GetGlyphRangesJapanese()}}), 13 * scale);
|
||||
title = add_font(vector<FontData>({FontData {notosans_thin_compressed_data_base85, io.Fonts->GetGlyphRangesDefault()}, FontData {notosansjp_thin_compressed_data_base85, io.Fonts->GetGlyphRangesJapanese()}}), 48 * scale);
|
||||
ImGui_ImplOpenGL3_CreateFontsTexture();
|
||||
std::string font_type = get_option<std::string>("font_type", "default");
|
||||
std::string default_font = "default";
|
||||
std::string jp_font = "japanese";
|
||||
auto glyph_ranges_default = io.Fonts->GetGlyphRangesDefault();
|
||||
auto glyph_ranges_jp = io.Fonts->GetGlyphRangesJapanese();
|
||||
FontData latin(glyph_ranges_default, {{"normal", notosans_regular_compressed_data_base85, 13}, {"title", notosans_thin_compressed_data_base85, 32}});
|
||||
FontData jp(glyph_ranges_jp, {{"normal", notosansjp_regular_compressed_data_base85, 13}, {"title", notosansjp_thin_compressed_data_base85, 32}});
|
||||
std::map<std::string, ImFont*> font_map;
|
||||
if (font_type == jp_font) {
|
||||
font_map = add_font(jp, scale);
|
||||
} else {
|
||||
font_map = add_font(latin, scale);
|
||||
}
|
||||
title = font_map["title"];
|
||||
io.FontDefault = font_map["normal"];
|
||||
ImGui_ImplSDLRenderer2_CreateFontsTexture();
|
||||
}
|
||||
#ifdef __EMSCRIPTEN__
|
||||
static EM_BOOL resize_callback(int event_type, const EmscriptenUiEvent *event, void *userdata) {
|
||||
|
@ -244,56 +295,24 @@ int RendererBackend::Run() {
|
|||
#endif
|
||||
Theme::prefPath = prefPath;
|
||||
|
||||
// Decide GL+GLSL versions
|
||||
#if defined(IMGUI_IMPL_OPENGL_ES2)
|
||||
// GL ES 2.0 + GLSL 100
|
||||
const char* glsl_version = "#version 100";
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_EGL, 2);
|
||||
#elif defined(__APPLE__)
|
||||
// GL 3.2 Core + GLSL 150
|
||||
const char* glsl_version = "#version 150";
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
#else
|
||||
// GL 3.0 + GLSL 130
|
||||
const char* glsl_version = "#version 130";
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
#endif
|
||||
|
||||
// From 2.0.18: Enable native IME.
|
||||
#ifdef SDL_HINT_IME_SHOW_UI
|
||||
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
|
||||
#endif
|
||||
|
||||
// Create window with graphics context
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
|
||||
window = SDL_CreateWindow(NAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, window_width, window_height, window_flags);
|
||||
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
|
||||
SDL_CreateWindowAndRenderer(window_width, window_height, window_flags, &window, &rend);
|
||||
|
||||
#ifndef __ANDROID__
|
||||
SDL_SetWindowMinimumSize(window, window_width, window_height);
|
||||
if (enable_kms) {
|
||||
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
}
|
||||
#endif
|
||||
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
|
||||
const vector<unsigned char> icon_data = DecodeBase85(icon_compressed_data_base85);
|
||||
SDL_Surface* icon = IMG_Load_RW(SDL_RWFromConstMem(icon_data.data(), icon_data.size()), 1);
|
||||
SDL_SetWindowIcon(window, icon);
|
||||
gl_context = SDL_GL_CreateContext(window);
|
||||
SDL_GL_MakeCurrent(window, gl_context);
|
||||
#ifdef __ANDROID__
|
||||
vsync = true;
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
#endif
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
|
@ -302,6 +321,9 @@ int RendererBackend::Run() {
|
|||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
if (SDL_GetNumTouchDevices() > 0) {
|
||||
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
||||
}
|
||||
//io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
|
||||
io.IniFilename = strdup((std::string(prefPath) + "imgui.ini").c_str());
|
||||
if (enable_kms) {
|
||||
|
@ -313,8 +335,8 @@ int RendererBackend::Run() {
|
|||
//ImGui::StyleColorsLight();
|
||||
|
||||
// Setup Platform/Renderer backends
|
||||
ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
ImGui_ImplSDL2_InitForSDLRenderer(window, rend);
|
||||
ImGui_ImplSDLRenderer2_Init(rend);
|
||||
// Load Fonts
|
||||
// - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
|
||||
// - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
|
||||
|
@ -345,9 +367,7 @@ int RendererBackend::Run() {
|
|||
#endif
|
||||
);
|
||||
#endif
|
||||
#ifndef __EMSCRIPTEN__
|
||||
SDL_GL_SetSwapInterval(vsync ? 1 : 0);
|
||||
#endif
|
||||
SDL_RenderSetVSync(rend, vsync ? 1 : 0);
|
||||
theme->Apply(accent_color);
|
||||
Init();
|
||||
SDL_ShowWindow(window);
|
||||
|
|
|
@ -19,13 +19,16 @@ static const char* NAME = "Looper";
|
|||
class RendererBackend {
|
||||
void BackendDeinit();
|
||||
void LoopFunction();
|
||||
SDL_GLContext gl_context;
|
||||
//SDL_GLContext gl_context;
|
||||
bool resize_needed = true;
|
||||
void on_resize();
|
||||
public:
|
||||
std::optional<bool> touchScreenModeOverride;
|
||||
bool isTouchScreenMode();
|
||||
static void resize_static();
|
||||
double scale = 1.0;
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *rend;
|
||||
int window_width = 475;
|
||||
int window_height = 354;
|
||||
bool done = false;
|
||||
|
|
|
@ -5,7 +5,39 @@
|
|||
#include <stdio.h>
|
||||
#include <log.hpp>
|
||||
#ifdef __ANDROID__
|
||||
#include <SDL.h>
|
||||
#include <jni.h>
|
||||
jclass MainActivity;
|
||||
jobject mainActivity;
|
||||
jmethodID GetUserDir_Method;
|
||||
jmethodID OpenFilePicker_Method;
|
||||
jmethodID GetPickedFile_Method;
|
||||
jmethodID ClearSelected_Method;
|
||||
jmethodID IsLoading_Method;
|
||||
JNIEnv *env;
|
||||
bool IsLoading() {
|
||||
return env->CallStaticBooleanMethod(MainActivity, IsLoading_Method, 0);
|
||||
}
|
||||
const char *GetUserDir() {
|
||||
jstring str = (jstring)env->CallStaticObjectMethod(MainActivity, GetUserDir_Method, 0);;
|
||||
jboolean tmp = true;
|
||||
return env->GetStringUTFChars(str, &tmp);
|
||||
}
|
||||
void OpenFilePicker(const char *pwd) {
|
||||
if (pwd == nullptr) {
|
||||
jstring string = env->NewStringUTF(pwd);
|
||||
env->CallStaticVoidMethod(MainActivity, OpenFilePicker_Method, string);
|
||||
} else {
|
||||
env->CallStaticVoidMethod(MainActivity, OpenFilePicker_Method, nullptr);
|
||||
}
|
||||
}
|
||||
const char *GetPickedFile() {
|
||||
jstring str = (jstring)env->CallStaticObjectMethod(MainActivity, GetPickedFile_Method, 0);
|
||||
jboolean tmp = true;
|
||||
return env->GetStringUTFChars(str, &tmp);
|
||||
}
|
||||
void ClearSelected() {
|
||||
env->CallStaticVoidMethod(MainActivity, ClearSelected_Method, 0);
|
||||
}
|
||||
#endif
|
||||
#ifdef __EMSCRIPTEN__
|
||||
extern "C" {
|
||||
|
@ -31,9 +63,6 @@ FileBrowser::FileBrowser(bool save, ImGuiFileBrowserFlags extra_fallback_flags)
|
|||
this->save = save;
|
||||
this->flags = (save ? ImGuiFileBrowserFlags_CreateNewDir|ImGuiFileBrowserFlags_EnterNewFilename : 0) | extra_fallback_flags;
|
||||
fallback = ImGui::FileBrowser(this->flags);
|
||||
#ifdef __ANDROID__
|
||||
fallback.SetPwd(SDL_AndroidGetExternalStoragePath());
|
||||
#endif
|
||||
|
||||
}
|
||||
void FileBrowser::SetTypeFilters(string name, vector<string> filters) {
|
||||
|
@ -75,13 +104,15 @@ void FileBrowser::SetTypeFilters(string name, vector<string> filters) {
|
|||
}
|
||||
void FileBrowser::SetPwd(path path) {
|
||||
pwd = path;
|
||||
#ifndef PORTALS
|
||||
#if !(defined(PORTALS) || defined(__ANDROID__))
|
||||
fallback.SetPwd(path);
|
||||
#endif
|
||||
}
|
||||
bool FileBrowser::HasSelected() {
|
||||
#ifdef PORTALS
|
||||
return selected.has_value();
|
||||
#elif defined(__ANDROID__)
|
||||
return strlen(GetPickedFile()) > 0;
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
return file_picker_confirmed();
|
||||
#else
|
||||
|
@ -91,6 +122,13 @@ bool FileBrowser::HasSelected() {
|
|||
path FileBrowser::GetSelected() {
|
||||
#ifdef PORTALS
|
||||
return selected.value_or(path());
|
||||
#elif defined(__ANDROID__)
|
||||
const char *file = GetPickedFile();
|
||||
if (strlen(file) > 0) {
|
||||
return std::string(file);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
if (HasSelected()) {
|
||||
const char *c_file = get_first_file();
|
||||
|
@ -120,6 +158,10 @@ void FileBrowser::Open() {
|
|||
} else {
|
||||
xdp_portal_open_file(portal, NULL, title.c_str(), variant, NULL, NULL, XDP_OPEN_FILE_FLAG_NONE, NULL, &FileBrowser::FileBrowserOpenCallback, this);
|
||||
}
|
||||
#elif defined(__ANDROID__)
|
||||
ClearSelected();
|
||||
open = true;
|
||||
::OpenFilePicker(nullptr);
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
open_filepicker();
|
||||
#else
|
||||
|
@ -201,6 +243,27 @@ void FileBrowser::FileBrowserSaveCallback(GObject *src, GAsyncResult *res, gpoin
|
|||
void FileBrowser::Display() {
|
||||
#ifdef PORTALS
|
||||
g_main_context_iteration(main_context, false);
|
||||
#elif defined(__ANDROID__)
|
||||
if (HasSelected()) {
|
||||
open = false;
|
||||
}
|
||||
if (IsLoading()) {
|
||||
ImVec2 pos(0, 0);
|
||||
ImVec2 size = ImGui::GetMainViewport()->Size;
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::Begin("Loading...", NULL, ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoTitleBar);
|
||||
ImVec2 textSize = ImGui::CalcTextSize("Loading...");
|
||||
ImVec2 textPos = size;
|
||||
textPos.x -= textSize.x;
|
||||
textPos.y -= textSize.y;
|
||||
textPos.x /= 2;
|
||||
textPos.y /= 2;
|
||||
ImGui::SetCursorPos(textPos);
|
||||
ImGui::TextUnformatted("Loading...");
|
||||
ImGui::End();
|
||||
|
||||
}
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
if (file_picker_visible() || file_picker_loading()) {
|
||||
if((flags & ImGuiFileBrowserFlags_NoModal))
|
||||
|
@ -252,6 +315,9 @@ void FileBrowser::ClearSelected() {
|
|||
#ifdef __EMSCRIPTEN__
|
||||
clear_file_selection();
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
::ClearSelected();
|
||||
#endif
|
||||
#ifndef PORTALS
|
||||
fallback.ClearSelected();
|
||||
#endif
|
||||
|
@ -265,6 +331,8 @@ void FileBrowser::SetTitle(string title) {
|
|||
bool FileBrowser::IsOpened() {
|
||||
#ifdef PORTALS
|
||||
return open;
|
||||
#elif defined(__ANDROID__)
|
||||
return open;
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
return !file_picker_closed() || file_picker_confirmed();
|
||||
#else
|
||||
|
@ -284,4 +352,4 @@ FileBrowser::~FileBrowser() {
|
|||
}
|
||||
g_main_loop_quit(main_loop);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ extern "C" {
|
|||
extern void enable_puter(bool enable);
|
||||
}
|
||||
#endif
|
||||
|
||||
using namespace Looper::Options;
|
||||
void MainLoop::Init() {
|
||||
#ifdef PORTALS
|
||||
g_set_application_name("Looper");
|
||||
|
@ -35,15 +35,12 @@ void MainLoop::Init() {
|
|||
{
|
||||
Json::Value config;
|
||||
std::ifstream stream;
|
||||
stream.open(path(prefPath) / "config.json");
|
||||
path jsonConfigPath = path(prefPath) / "config.json";
|
||||
stream.open(jsonConfigPath);
|
||||
if (stream.is_open()) {
|
||||
stream >> config;
|
||||
if (config.isMember("theme_name")) {
|
||||
path themePath = theme->themeDir / config["theme_name"].asString();
|
||||
if (exists(themePath)) {
|
||||
delete theme;
|
||||
theme = new Theme(themePath);
|
||||
}
|
||||
init_option<std::string>("ui.imgui.theme", config["theme_name"].asString());
|
||||
}
|
||||
if (config.isMember("accent_color")) {
|
||||
if (config["accent_color"].isNumeric()) {
|
||||
|
@ -52,27 +49,52 @@ void MainLoop::Init() {
|
|||
Json::Value accentColor = config["accent_color"];
|
||||
accent_color = ImVec4(accentColor["h"].asFloat(), accentColor["s"].asFloat(), accentColor["v"].asFloat(), accentColor["a"].asFloat());
|
||||
}
|
||||
toml::table accent_color_table;
|
||||
accent_color_table.insert("h", accent_color.x);
|
||||
accent_color_table.insert("s", accent_color.y);
|
||||
accent_color_table.insert("v", accent_color.z);
|
||||
accent_color_table.insert("a", accent_color.w);
|
||||
init_option<toml::table>("ui.imgui.accent_color", accent_color_table);
|
||||
}
|
||||
if (config.isMember("demo_window")) {
|
||||
show_demo_window = config["demo_window"].asBool();
|
||||
init_option<bool>("ui.imgui.demo_window", config["demo_window"].asBool());
|
||||
}
|
||||
if (config.isMember("vsync")) {
|
||||
vsync = config["vsync"].asBool();
|
||||
init_option<bool>("ui.imgui.vsync", config["vsync"].asBool());
|
||||
}
|
||||
if (config.isMember("framerate")) {
|
||||
framerate = config["framerate"].asUInt();
|
||||
init_option<int64_t>("ui.imgui.framerate", (int64_t)config["framerate"].asUInt());
|
||||
}
|
||||
if (config.isMember("lang")) {
|
||||
Json::Value langValue;
|
||||
if (langValue.isNull()) {
|
||||
lang = DEFAULT_LANG;
|
||||
} else {
|
||||
lang = config["lang"].asString();
|
||||
if (!langValue.isNull()) {
|
||||
init_option<std::string>("ui.imgui.lang", config["lang"].asString());
|
||||
}
|
||||
SET_LANG(lang.c_str());
|
||||
}
|
||||
stream.close();
|
||||
}
|
||||
std::remove(jsonConfigPath.c_str());
|
||||
}
|
||||
{
|
||||
std::string themeName = get_option<std::string>("ui.imgui.theme", "light");
|
||||
path themePath = Theme::themeDir / path(themeName + ".toml");
|
||||
if (exists(themePath)) {
|
||||
delete theme;
|
||||
theme = new Theme(themePath);
|
||||
}
|
||||
if (option_set("ui.imgui.lang")) {
|
||||
lang = get_option<std::string>("ui.imgui.lang");
|
||||
} else {
|
||||
lang = DEFAULT_LANG;
|
||||
}
|
||||
SET_LANG(lang.c_str());
|
||||
show_demo_window = get_option<bool>("ui.imgui.demo_window", false);
|
||||
vsync = get_option<bool>("ui.imgui.vsync", true);
|
||||
framerate = (unsigned)get_option<int64_t>("ui.imgui.framerate", 60);
|
||||
accent_color.x = (float)get_option<double>("ui.imgui.accent_color.h", accent_color.x);
|
||||
accent_color.y = (float)get_option<double>("ui.imgui.accent_color.s", accent_color.y);
|
||||
accent_color.z = (float)get_option<double>("ui.imgui.accent_color.v", accent_color.z);
|
||||
accent_color.w = (float)get_option<double>("ui.imgui.accent_color.a", accent_color.w);
|
||||
}
|
||||
if (is_empty(Theme::themeDir)) {
|
||||
path lightPath = Theme::themeDir / "light.toml";
|
||||
path darkPath = Theme::themeDir / "dark.toml";
|
||||
|
@ -114,7 +136,9 @@ void MainLoop::FileLoaded() {
|
|||
streams = playback->get_streams();
|
||||
}
|
||||
void MainLoop::GuiFunction() {
|
||||
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
|
||||
playback->LoopHook();
|
||||
#endif
|
||||
position = playback->GetPosition();
|
||||
length = playback->GetLength();
|
||||
// Set the window title if the file changed, or playback stopped.
|
||||
|
@ -278,9 +302,7 @@ void MainLoop::GuiFunction() {
|
|||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if (ImGui::Checkbox(_TR_CTX("Preference | VSync checkbox", "Enable VSync"), &vsync)) {
|
||||
SDL_GL_SetSwapInterval(vsync ? 1 : 0);
|
||||
}
|
||||
ImGui::Checkbox(_TR_CTX("Preference | VSync checkbox", "Enable VSync"), &vsync);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - ImGui::GetStyle().WindowPadding.x);
|
||||
ImGui::SliderInt("##Framerate", &framerate, 10, 480, _TR_CTX("Preferences | Framerate slider", "Max framerate without VSync: %d"));
|
||||
|
@ -309,6 +331,21 @@ void MainLoop::GuiFunction() {
|
|||
SET_LANG(lang.c_str());
|
||||
}
|
||||
}
|
||||
bool overrideTouchscreenMode = touchScreenModeOverride.has_value();
|
||||
if (ImGui::Checkbox(_TR_CTX("Preference | override enable checkbox", "Override touchscreen mode"), &overrideTouchscreenMode)) {
|
||||
if (overrideTouchscreenMode) {
|
||||
touchScreenModeOverride = isTouchScreenMode();
|
||||
} else {
|
||||
touchScreenModeOverride = {};
|
||||
}
|
||||
}
|
||||
if (overrideTouchscreenMode) {
|
||||
bool touchScreenMode = isTouchScreenMode();
|
||||
if (ImGui::Checkbox(_TRI_CTX(ICON_FK_HAND_O_UP, "Preference | Checkbox (Only shown when override enabled)", "Enable touch screen mode."), &touchScreenMode)) {
|
||||
touchScreenModeOverride = touchScreenMode;
|
||||
}
|
||||
}
|
||||
|
||||
static string filter = "";
|
||||
ImGui::TextUnformatted(_TR_CTX("Preference | Theme selector | Filter label", "Filter:")); ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - ImGui::GetStyle().WindowPadding.x);
|
||||
|
@ -429,32 +466,23 @@ void MainLoop::LoadFile(std::string file) {
|
|||
void MainLoop::Deinit() {
|
||||
|
||||
{
|
||||
Json::Value config;
|
||||
std::ofstream stream;
|
||||
stream.open(path(prefPath) / "config.json");
|
||||
path themePath(theme->file_path);
|
||||
themePath = themePath.filename();
|
||||
if (!themePath.empty()) {
|
||||
config["theme_name"] = themePath.filename().string();
|
||||
set_option<std::string>("ui.imgui.theme", themePath.filename().string());
|
||||
}
|
||||
{
|
||||
Json::Value accentColor;
|
||||
accentColor["h"] = accent_color.x;
|
||||
accentColor["s"] = accent_color.y;
|
||||
accentColor["v"] = accent_color.z;
|
||||
accentColor["a"] = accent_color.w;
|
||||
config["accent_color"] = accentColor;
|
||||
}
|
||||
config["demo_window"] = show_demo_window;
|
||||
config["vsync"] = vsync;
|
||||
config["framerate"] = framerate;
|
||||
set_option<double>("ui.imgui.accent_color.h", accent_color.x);
|
||||
set_option<double>("ui.imgui.accent_color.s", accent_color.y);
|
||||
set_option<double>("ui.imgui.accent_color.v", accent_color.z);
|
||||
set_option<double>("ui.imgui.accent_color.a", accent_color.w);
|
||||
set_option<double>("ui.imgui.demo_window", show_demo_window);
|
||||
set_option<bool>("ui.imgui.vsync", vsync);
|
||||
set_option<int64_t>("ui.imgui.framerate", framerate);
|
||||
if (lang == DEFAULT_LANG) {
|
||||
config["lang"] = Json::Value::nullSingleton();
|
||||
delete_option("ui.imgui.lang");
|
||||
} else {
|
||||
config["lang"] = lang;
|
||||
set_option<std::string>("ui.imgui.lang", lang);
|
||||
}
|
||||
stream << config;
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
MainLoop::MainLoop() : RendererBackend() {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#!/bin/sh
|
||||
#!/bin/env -S NOT_SOURCED=1 /bin/sh
|
||||
if ! [ "$NOT_SOURCED" = "1" ]; then
|
||||
echo "Error: This script must not be sourced!" >&2
|
||||
return 1
|
||||
fi
|
||||
pushd "$(dirname "$0")"
|
||||
./setup-android-project.sh
|
||||
cd sdl-android-project
|
||||
ln -fs "$(dirname "$(pwd)")" ./app/jni
|
||||
#android update project --name looper --path . --target "$ANDROID_PLATFORM"
|
||||
./gradlew build
|
||||
popd
|
||||
|
||||
#cp bin/*.so ../andr
|
||||
|
|
4
dbus.cpp
4
dbus.cpp
|
@ -159,8 +159,8 @@ DBusAPI::DBusAPI(Playback *playback, sdbus::IConnection &connection, std::string
|
|||
connection.enterEventLoopAsync();
|
||||
}
|
||||
#endif
|
||||
const char *DBusAPI::objectPath = "/com/complecwaft/Looper";
|
||||
const char *DBusAPI::busName = "com.complecwaft.Looper";
|
||||
const char *DBusAPI::objectPath = "/com/complecwaft/looper";
|
||||
const char *DBusAPI::busName = "com.complecwaft.looper";
|
||||
DBusAPI *DBusAPI::Create(Playback *playback, bool daemon) {
|
||||
#ifdef DBUS_ENABLED
|
||||
auto connection = sdbus::createSessionBusConnection(busName);
|
||||
|
|
4
dbus.hpp
4
dbus.hpp
|
@ -105,7 +105,7 @@ class MprisAPI : public sdbus::AdaptorInterfaces<org::mpris::MediaPlayer2_adapto
|
|||
#endif
|
||||
class DBusAPI
|
||||
#ifdef DBUS_ENABLED
|
||||
: public sdbus::AdaptorInterfaces<com::complecwaft::Looper_adaptor, com::complecwaft::Looper::Errors_adaptor, org::freedesktop::Application_adaptor>
|
||||
: public sdbus::AdaptorInterfaces<com::complecwaft::looper_adaptor, com::complecwaft::looper::Errors_adaptor, org::freedesktop::Application_adaptor>
|
||||
#endif
|
||||
{
|
||||
std::map<std::string, void*> handles;
|
||||
|
@ -189,7 +189,7 @@ class DBusAPI
|
|||
};
|
||||
class DBusAPISender : public Playback
|
||||
#ifdef DBUS_ENABLED
|
||||
, public sdbus::ProxyInterfaces<com::complecwaft::Looper_proxy, com::complecwaft::Looper::Errors_proxy, org::freedesktop::Application_proxy, sdbus::Peer_proxy>
|
||||
, public sdbus::ProxyInterfaces<com::complecwaft::looper_proxy, com::complecwaft::looper::Errors_proxy, org::freedesktop::Application_proxy, sdbus::Peer_proxy>
|
||||
#endif
|
||||
{
|
||||
// Cache
|
||||
|
|
49
log.cpp
49
log.cpp
|
@ -1,6 +1,9 @@
|
|||
#include "log.hpp"
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
namespace Looper::Log {
|
||||
std::set<FILE*> LogStream::global_outputs;
|
||||
int LogStream::log_level = 0;
|
||||
|
@ -14,6 +17,7 @@ namespace Looper::Log {
|
|||
}
|
||||
return used_outputs;
|
||||
}
|
||||
std::string line;
|
||||
void LogStream::writec(const char chr) {
|
||||
bool is_newline = (chr == '\n' || chr == '\r');
|
||||
if (my_log_level < log_level) {
|
||||
|
@ -25,6 +29,15 @@ namespace Looper::Log {
|
|||
stream->writec(chr);
|
||||
}
|
||||
} else {
|
||||
#ifdef __ANDROID__
|
||||
if (!is_newline) {
|
||||
line += chr;
|
||||
} else {
|
||||
for (auto logPriority : android_outputs) {
|
||||
__android_log_print(logPriority, "Looper", "%s", line.c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
std::set<FILE*> used_outputs = get_used_outputs();
|
||||
for (auto &file : used_outputs) {
|
||||
fwrite(&chr, 1, 1, file);
|
||||
|
@ -104,7 +117,7 @@ namespace Looper::Log {
|
|||
vwritefln(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
LogStream::LogStream(std::initializer_list<std::string> names, int log_level, bool nested)
|
||||
LogStream::LogStream(std::initializer_list<std::string> names, int log_level, bool nested, void *discriminator)
|
||||
: names(names),
|
||||
need_prefix(true),
|
||||
my_log_level(log_level),
|
||||
|
@ -113,24 +126,52 @@ namespace Looper::Log {
|
|||
|
||||
}
|
||||
LogStream::LogStream(std::initializer_list<std::string> names, std::initializer_list<LogStream*> streams, int log_level)
|
||||
: LogStream(names, log_level, true)
|
||||
: LogStream(names, log_level, true, nullptr)
|
||||
{
|
||||
this->streams = std::set(streams);
|
||||
}
|
||||
LogStream::LogStream(std::initializer_list<std::string> names, std::initializer_list<FILE*> outputs, int log_level)
|
||||
: LogStream(names, log_level, false)
|
||||
#ifdef __ANDROID__
|
||||
LogStream::LogStream(std::initializer_list<std::string> names, std::initializer_list<std::variant<FILE*, android_LogPriority>> outputs, int log_level)
|
||||
#else
|
||||
LogStream::LogStream(std::initializer_list<std::string> names, std::initializer_list<FILE*> outputs, int log_level)
|
||||
#endif
|
||||
: LogStream(names, log_level, false, nullptr)
|
||||
{
|
||||
#ifdef __ANDROID__
|
||||
std::set<FILE*> file_outputs;
|
||||
std::set<android_LogPriority> android_outputs;
|
||||
for (auto output : outputs) {
|
||||
android_LogPriority *logPriority = std::get_if<android_LogPriority>(&output);
|
||||
FILE **file = std::get_if<FILE*>(&output);
|
||||
if (logPriority != nullptr) {
|
||||
android_outputs.insert(*logPriority);
|
||||
} else if (file != nullptr){
|
||||
file_outputs.insert(*file);
|
||||
}
|
||||
}
|
||||
this->android_outputs = android_outputs;
|
||||
this->outputs = file_outputs;
|
||||
#else
|
||||
|
||||
this->outputs = std::set(outputs);
|
||||
#endif
|
||||
}
|
||||
static LogStream *debug_stream;
|
||||
static LogStream *info_stream;
|
||||
static LogStream *warning_stream;
|
||||
static LogStream *error_stream;
|
||||
void init_logging() {
|
||||
#ifdef __ANDROID__
|
||||
debug_stream = new LogStream({"DEBUG"}, {ANDROID_LOG_DEBUG}, -1);
|
||||
info_stream = new LogStream({"INFO"}, {ANDROID_LOG_INFO}, 0);
|
||||
warning_stream = new LogStream({"WARNING"}, {ANDROID_LOG_WARN}, 1);
|
||||
error_stream = new LogStream({"ERROR"}, {ANDROID_LOG_ERROR}, 2);
|
||||
#else
|
||||
debug_stream = new LogStream({"DEBUG"}, {stderr}, -1);
|
||||
info_stream = new LogStream({"INFO"}, {stdout}, 0);
|
||||
warning_stream = new LogStream({"WARNING"}, {stderr}, 1);
|
||||
error_stream = new LogStream({"ERROR"}, {stderr}, 2);
|
||||
#endif
|
||||
}
|
||||
LogStream &get_log_stream_by_level(int level) {
|
||||
switch (level) {
|
||||
|
|
17
log.hpp
17
log.hpp
|
@ -4,6 +4,10 @@
|
|||
#include <ostream>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
namespace Looper::Log {
|
||||
struct LogStream {
|
||||
std::set<FILE *> outputs;
|
||||
|
@ -14,7 +18,13 @@ namespace Looper::Log {
|
|||
bool need_prefix;
|
||||
std::vector<std::string> names;
|
||||
std::set<FILE*> get_used_outputs();
|
||||
LogStream(std::initializer_list<std::string> names, int log_level, bool nested);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
std::string line;
|
||||
std::set<android_LogPriority> android_outputs;
|
||||
#endif
|
||||
LogStream(std::initializer_list<std::string> names, int log_level, bool nested, void* discriminator);
|
||||
|
||||
public:
|
||||
static int log_level;
|
||||
void writeprefix();
|
||||
|
@ -30,7 +40,12 @@ namespace Looper::Log {
|
|||
void vwritefln(const char *fmt, va_list args);
|
||||
void writefln(const char *fmt, ...);
|
||||
LogStream(std::initializer_list<std::string> names, std::initializer_list<LogStream*> streams, int log_level = 0);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
LogStream(std::initializer_list<std::string> names, std::initializer_list<std::variant<FILE*, android_LogPriority>> outputs, int log_level = 0);
|
||||
#else
|
||||
LogStream(std::initializer_list<std::string> names, std::initializer_list<FILE*> outputs, int log_level = 0);
|
||||
#endif
|
||||
};
|
||||
void init_logging();
|
||||
LogStream &get_log_stream_by_level(int level);
|
||||
|
|
1
looper
1
looper
|
@ -1 +0,0 @@
|
|||
/home/catmeow/looper
|
29
main.cpp
29
main.cpp
|
@ -19,7 +19,36 @@ std::unordered_set<LicenseData> license_data;
|
|||
std::unordered_set<LicenseData> &get_license_data() {
|
||||
return license_data;
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
#include <SDL.h>
|
||||
#include <jni.h>
|
||||
extern jclass MainActivity;
|
||||
extern jobject mainActivity;
|
||||
extern jmethodID GetUserDir_Method;
|
||||
extern jmethodID OpenFilePicker_Method;
|
||||
extern jmethodID GetPickedFile_Method;
|
||||
extern jmethodID ClearSelected_Method;
|
||||
extern jmethodID IsLoading_Method;
|
||||
extern JNIEnv *env;
|
||||
void initNative() {
|
||||
MainActivity = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("com/complecwaft/looper/MainActivity")));
|
||||
GetUserDir_Method = env->GetStaticMethodID(MainActivity, "GetUserDir",
|
||||
"()Ljava/lang/String;");
|
||||
OpenFilePicker_Method = env->GetStaticMethodID(MainActivity, "OpenFilePicker",
|
||||
"(Ljava/lang/Object;)V");
|
||||
GetPickedFile_Method = env->GetStaticMethodID(MainActivity, "GetPickedFile",
|
||||
"()Ljava/lang/String;");
|
||||
ClearSelected_Method = env->GetStaticMethodID(MainActivity, "ClearSelected", "()V");
|
||||
IsLoading_Method = env->GetStaticMethodID(MainActivity, "IsLoading", "()Z");
|
||||
jfieldID singleton = env->GetStaticFieldID(MainActivity, "mSingleton", "Lcom/complecwaft/looper/MainActivity;");
|
||||
mainActivity = reinterpret_cast<jobject>(env->NewGlobalRef(env->GetStaticObjectField(MainActivity, singleton)));
|
||||
}
|
||||
#endif
|
||||
int main(int argc, char **argv) {
|
||||
#ifdef __ANDROID__
|
||||
env = (JNIEnv*)SDL_AndroidGetJNIEnv();
|
||||
initNative();
|
||||
#endif
|
||||
std::vector<std::string> args;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
args.push_back(std::string(argv[i]));
|
||||
|
|
32
options.hpp
32
options.hpp
|
@ -5,6 +5,7 @@
|
|||
#include <vector>
|
||||
#include <typeinfo>
|
||||
#include <typeindex>
|
||||
#include <optional>
|
||||
#define OPTIONS (*Looper::Options::options)
|
||||
namespace Looper::Options {
|
||||
extern toml::table *options;
|
||||
|
@ -46,6 +47,37 @@ namespace Looper::Options {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
inline void delete_option(std::string name) {
|
||||
DEBUG.writefln("Deleting option '%s'...", name.c_str());
|
||||
toml::path path(name);
|
||||
auto *tmp = &OPTIONS;
|
||||
std::vector<std::string> components;
|
||||
for (auto component : path) {
|
||||
std::string component_str = (std::string)component;
|
||||
components.push_back(component_str);
|
||||
}
|
||||
while (!path.empty()) {
|
||||
if (option_set(path.str())) {
|
||||
toml::path parent_path = path.parent();
|
||||
auto &parent = OPTIONS.at(parent_path.str());
|
||||
auto last_component = path[path.size() - 1];
|
||||
if (parent.is_table()) {
|
||||
auto &table = *parent.as_table();
|
||||
table.erase((std::string)last_component);
|
||||
if (!table.empty()) {
|
||||
return;
|
||||
}
|
||||
} else if (parent.is_array()) {
|
||||
auto &array = *parent.as_array();
|
||||
array.erase(array.begin() + last_component.index());
|
||||
if (!array.empty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
path = parent_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
template<class T>
|
||||
void set_option(std::string name, T value) {
|
||||
DEBUG.writefln("Setting option '%s'...", name.c_str());
|
||||
|
|
127
playback.cpp
127
playback.cpp
|
@ -102,6 +102,9 @@ void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) {
|
|||
#endif
|
||||
if (samples > new_samples) {
|
||||
reset_vgmstream(this->stream);
|
||||
position = 0.0;
|
||||
} else {
|
||||
position += samples / this->stream->sample_rate;
|
||||
}
|
||||
samples = new_samples;
|
||||
for (int i = 0; i < new_bufsize / sizeof(SAMPLETYPE); i++) {
|
||||
|
@ -115,6 +118,15 @@ void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) {
|
|||
}
|
||||
st->receiveSamples((SAMPLETYPE*)stream, len / unit);
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
oboe::DataCallbackResult PlaybackInstance::onAudioReady(
|
||||
oboe::AudioStream *audioStream,
|
||||
void *audioData,
|
||||
int32_t numFrames) {
|
||||
SDLCallbackInner((Uint8*)audioData, numFrames * audioStream->getBytesPerFrame());
|
||||
return oboe::DataCallbackResult::Continue;
|
||||
}
|
||||
#endif
|
||||
void PlaybackInstance::SDLCallback(void *userdata, Uint8 *stream, int len) {
|
||||
((PlaybackInstance*)userdata)->SDLCallbackInner(stream, len);
|
||||
}
|
||||
|
@ -340,6 +352,7 @@ void PlaybackInstance::InitLoopFunction() {
|
|||
desired.userdata = this;
|
||||
st = new SoundTouch();
|
||||
Mix_Init(MIX_INIT_FLAC|MIX_INIT_MID|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG|MIX_INIT_OPUS|MIX_INIT_WAVPACK);
|
||||
#ifndef __ANDROID__
|
||||
if ((device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) {
|
||||
ERROR.writefln("Error opening audio device: '%s'", SDL_GetError());
|
||||
set_error("Failed to open audio device!");
|
||||
|
@ -347,6 +360,56 @@ void PlaybackInstance::InitLoopFunction() {
|
|||
loop_started = false;
|
||||
return;
|
||||
}
|
||||
#else
|
||||
oboe::AudioStreamBuilder builder;
|
||||
builder.setDirection(oboe::Direction::Output);
|
||||
builder.setSharingMode(oboe::SharingMode::Shared);
|
||||
builder.setPerformanceMode(oboe::PerformanceMode::None);
|
||||
builder.setFormat(
|
||||
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
|
||||
oboe::AudioFormat::I16
|
||||
#else
|
||||
oboe::AudioFormat::Float
|
||||
#endif
|
||||
);
|
||||
builder.setDataCallback(this);
|
||||
auto res = builder.openStream(ostream);
|
||||
if (res != oboe::Result::OK) {
|
||||
ERROR.writefln("Error opening audio device.");
|
||||
set_error("Failed to open audio device!");
|
||||
running = false;
|
||||
loop_started = false;
|
||||
return;
|
||||
}
|
||||
obtained = desired;
|
||||
obtained.channels = ostream->getChannelCount();
|
||||
obtained.freq = ostream->getSampleRate();
|
||||
auto bufferFrames = ostream->getBufferSizeInFrames();
|
||||
auto bytesPerFrame = ostream->getBytesPerFrame();
|
||||
auto bytesPerSample = ostream->getBytesPerSample();
|
||||
obtained.size = bufferFrames * bytesPerFrame;
|
||||
obtained.samples = obtained.size / bytesPerSample;
|
||||
oboe::AudioFormat format = ostream->getFormat();
|
||||
DEBUG.writefln("About to start audio stream.\n\
|
||||
Format: %s\n\
|
||||
Channel count: %u\n\
|
||||
Sample rate: %u\n\
|
||||
Buffer size (frames): %u\n\
|
||||
Bytes per frame: %u\n\
|
||||
Total bytes: %u\n\
|
||||
Bytes per sample: %u\n\
|
||||
Total samples: %u"
|
||||
, oboe::convertToText(format)
|
||||
, obtained.channels
|
||||
, obtained.freq
|
||||
, bufferFrames
|
||||
, bytesPerFrame
|
||||
, obtained.size
|
||||
, bytesPerSample
|
||||
, obtained.samples
|
||||
);
|
||||
ostream->requestStart();
|
||||
#endif
|
||||
spec = obtained;
|
||||
st->setSampleRate(spec.freq);
|
||||
st->setChannels(spec.channels);
|
||||
|
@ -384,6 +447,7 @@ void PlaybackInstance::InitLoopFunction() {
|
|||
} else {
|
||||
playback_ready.store(false);
|
||||
}
|
||||
load_finished.store(true);
|
||||
set_signal(PlaybackSignalStarted);
|
||||
}
|
||||
void PlaybackInstance::LoopFunction() {
|
||||
|
@ -407,34 +471,38 @@ void PlaybackInstance::LoopFunction() {
|
|||
}
|
||||
}
|
||||
if (stream_changed.exchange(false)) {
|
||||
std::string file = current_file.value();
|
||||
if (streams[current_stream].name == "" || streams[current_stream].length <= 0 || current_stream < 0 || current_stream >= streams.size()) {
|
||||
if (stream != nullptr) {
|
||||
current_stream = stream->stream_index;
|
||||
current_file_mutex.lock();
|
||||
if (current_file.has_value()) {
|
||||
std::string file = current_file.value();
|
||||
if (current_stream >= streams.size() || current_stream < 0 ||
|
||||
streams[current_stream].name == "" || streams[current_stream].length <= 0) {
|
||||
if (stream != nullptr) {
|
||||
current_stream = stream->stream_index;
|
||||
} else {
|
||||
current_stream = 0;
|
||||
}
|
||||
} else {
|
||||
current_stream = 0;
|
||||
if (stream != nullptr) {
|
||||
UnloadVgm(stream);
|
||||
stream = LoadVgm(file.c_str(), current_stream);
|
||||
} else if (music != nullptr) {
|
||||
UnloadMix(music);
|
||||
music = LoadMix(file.c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (stream != nullptr) {
|
||||
UnloadVgm(stream);
|
||||
stream = LoadVgm(file.c_str(), current_stream);
|
||||
} else if (music != nullptr) {
|
||||
UnloadMix(music);
|
||||
music = LoadMix(file.c_str());
|
||||
if (music || stream) {
|
||||
playback_ready.store(true);
|
||||
} else {
|
||||
playback_ready.store(false);
|
||||
}
|
||||
}
|
||||
if (music || stream) {
|
||||
playback_ready.store(true);
|
||||
} else {
|
||||
playback_ready.store(false);
|
||||
}
|
||||
current_file_mutex.unlock();
|
||||
}
|
||||
if (flag_mutex.try_lock()) {
|
||||
if (seeking.exchange(false)) {
|
||||
if (stream != nullptr) {
|
||||
SDL_LockAudioDevice(device);
|
||||
seek_vgmstream(stream, (int32_t)((double)stream->sample_rate * position));
|
||||
|
||||
st->flush();
|
||||
SDL_UnlockAudioDevice(device);
|
||||
} else {
|
||||
|
@ -493,13 +561,7 @@ void PlaybackInstance::LoopFunction() {
|
|||
}
|
||||
flag_mutex.unlock();
|
||||
}
|
||||
if (stream != nullptr) {
|
||||
double maybe_new_position = (double)stream->current_sample / stream->sample_rate;
|
||||
if (position > maybe_new_position) {
|
||||
position = maybe_new_position;
|
||||
}
|
||||
position += 0.02 * (speed * tempo);
|
||||
} else if (music != nullptr) {
|
||||
if (music != nullptr) {
|
||||
position = Mix_GetMusicPosition(music);
|
||||
}
|
||||
|
||||
|
@ -513,7 +575,15 @@ void PlaybackInstance::DeinitLoopFunction() {
|
|||
if (stream != nullptr) {
|
||||
UnloadVgm(stream);
|
||||
}
|
||||
#ifndef __ANDROID__
|
||||
SDL_CloseAudioDevice(device);
|
||||
#else
|
||||
if (ostream && ostream->getState() != oboe::StreamState::Closed) {
|
||||
ostream->stop();
|
||||
ostream->close();
|
||||
}
|
||||
ostream.reset();
|
||||
#endif
|
||||
Mix_CloseAudio();
|
||||
Mix_Quit();
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
|
@ -573,10 +643,11 @@ void PlaybackInstance::Load(std::string filePath) {
|
|||
if (running.exchange(true)) {
|
||||
load_requested.store(true);
|
||||
} else {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
|
||||
start_loop();
|
||||
#else
|
||||
thread = std::thread(&PlaybackInstance::ThreadFunc, this);
|
||||
loop_started = true;
|
||||
#endif
|
||||
}
|
||||
flag_mutex.lock();
|
||||
|
@ -589,7 +660,7 @@ void PlaybackInstance::Load(std::string filePath) {
|
|||
void PlaybackInstance::Start(std::string filePath, int streamIdx) {
|
||||
Load(filePath);
|
||||
while (loop_started && !load_finished.exchange(false)) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
|
||||
LoopHook();
|
||||
#endif
|
||||
std::this_thread::sleep_for(20ms);
|
||||
|
@ -644,7 +715,7 @@ bool PlaybackInstance::IsPaused() {
|
|||
|
||||
void PlaybackInstance::Stop() {
|
||||
if (running.exchange(false)) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
|
||||
stop_loop();
|
||||
#else
|
||||
thread.join();
|
||||
|
|
18
playback.h
18
playback.h
|
@ -3,6 +3,9 @@
|
|||
extern "C" {
|
||||
#include <vgmstream.h>
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
#include <oboe/Oboe.h>
|
||||
#endif
|
||||
#include <thread>
|
||||
#include <SDL.h>
|
||||
#include <SDL_audio.h>
|
||||
|
@ -212,8 +215,21 @@ class Playback {
|
|||
static Playback *Create(bool *daemon_found, bool daemon = false);
|
||||
};
|
||||
class DBusAPISender;
|
||||
class PlaybackInstance : public Playback {
|
||||
class PlaybackInstance : public Playback
|
||||
#ifdef __ANDROID__
|
||||
, public oboe::AudioStreamDataCallback
|
||||
#endif
|
||||
{
|
||||
private:
|
||||
#ifdef __ANDROID__
|
||||
std::shared_ptr<oboe::AudioStream> ostream;
|
||||
public:
|
||||
oboe::DataCallbackResult onAudioReady(
|
||||
oboe::AudioStream *audioStream,
|
||||
void *audioData,
|
||||
int32_t numFrames) override;
|
||||
private:
|
||||
#endif
|
||||
std::string filePath;
|
||||
std::atomic_bool running;
|
||||
std::atomic_bool file_changed;
|
||||
|
|
3
sdl-android-project/.idea/.gitignore
vendored
Normal file
3
sdl-android-project/.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
6
sdl-android-project/.idea/compiler.xml
Normal file
6
sdl-android-project/.idea/compiler.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
23
sdl-android-project/.idea/deploymentTargetDropDown.xml
Normal file
23
sdl-android-project/.idea/deploymentTargetDropDown.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<value>
|
||||
<entry key="app">
|
||||
<State>
|
||||
<targetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="QUICK_BOOT_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="$USER_HOME$/.android/avd/Copy_of_Pixel_3a_API_34_extension_level_7_x86_64.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2024-04-28T16:05:58.159965481Z" />
|
||||
</State>
|
||||
</entry>
|
||||
</value>
|
||||
</component>
|
||||
</project>
|
19
sdl-android-project/.idea/gradle.xml
Normal file
19
sdl-android-project/.idea/gradle.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="JniFindClass" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
10
sdl-android-project/.idea/migrations.xml
Normal file
10
sdl-android-project/.idea/migrations.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
10
sdl-android-project/.idea/misc.xml
Normal file
10
sdl-android-project/.idea/misc.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
7
sdl-android-project/.idea/vcs.xml
Normal file
7
sdl-android-project/.idea/vcs.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/app/jni" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -12,7 +12,7 @@ android {
|
|||
prefab true
|
||||
}
|
||||
if (buildAsApplication) {
|
||||
namespace "com.complecwaft.Looper"
|
||||
namespace "com.complecwaft.looper"
|
||||
}
|
||||
compileSdkVersion 34
|
||||
defaultConfig {
|
||||
|
@ -56,7 +56,7 @@ android {
|
|||
variant.outputs.each { output ->
|
||||
def outputFile = output.outputFile
|
||||
if (outputFile != null && outputFile.name.endsWith(".aar")) {
|
||||
def fileName = "com.complecwaft.Looper.app.aar";
|
||||
def fileName = "com.complecwaft.looper.app.aar";
|
||||
output.outputFile = new File(outputFile.parent, fileName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,16 @@
|
|||
com.gamemaker.game
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0"
|
||||
android:installLocation="auto">
|
||||
|
||||
android:installLocation="auto"
|
||||
package="com.complecwaft.looper">
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.OPEN_DOCUMENT" />
|
||||
<category android:name="android.intent.category.OPENABLE" />
|
||||
</intent>
|
||||
</queries>
|
||||
<!-- OpenGL ES 2.0 -->
|
||||
<uses-feature android:glEsVersion="0x00020000" />
|
||||
<uses-feature android:glEsVersion="0x00030000" />
|
||||
|
||||
<!-- Touchscreen support -->
|
||||
<uses-feature
|
||||
|
@ -39,6 +43,8 @@
|
|||
|
||||
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="22" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
||||
<!-- Allow access to Bluetooth devices -->
|
||||
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
package com.complecwaft.Looper;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
public class MainActivity extends SDLActivity
|
||||
{
|
||||
private static native void nativeInit();
|
||||
static {
|
||||
nativeInit();
|
||||
}
|
||||
public static String GetUserDir() {
|
||||
return System.getProperty("user.home");
|
||||
}
|
||||
public static void OpenFilePicker(Object types, Object extraInitialUri) {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
if (types instanceof String) {
|
||||
intent.setType(types);
|
||||
} else if (types instanceof Array<String>) {
|
||||
Array<String> type_arr = (Array<String>)types;
|
||||
String output;
|
||||
for (String type : type_arr) {
|
||||
|
||||
}
|
||||
}
|
||||
if (extraInitialUri instanceof String) {
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, (String)extraInitialUri);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package com.complecwaft.looper;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Objects;
|
||||
import android.media.AudioManager;
|
||||
public class MainActivity extends SDLActivity
|
||||
{
|
||||
public static MainActivity mSingleton;
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
mSingleton = this;
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
private static Intent openDocumentIntent = null;
|
||||
public static String GetUserDir() {
|
||||
return System.getProperty("user.home");
|
||||
}
|
||||
private static final int PICK_FILE = 0x33;
|
||||
public static void OpenFilePicker(Object extraInitialUri) {
|
||||
openDocumentIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
openDocumentIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
openDocumentIntent.setType("*/*");
|
||||
if (extraInitialUri instanceof String) {
|
||||
openDocumentIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, (String)extraInitialUri);
|
||||
}
|
||||
if (openDocumentIntent.resolveActivity(mSingleton.getPackageManager()) != null) {
|
||||
mSingleton.startActivityForResult(Intent.createChooser(openDocumentIntent, "Open a file..."), PICK_FILE);
|
||||
} else {
|
||||
Log.d("Looper", "Unable to resolve Intent.ACTION_OPEN_DOCUMENT {}");
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
|
||||
if (requestCode == PICK_FILE && resultCode == RESULT_OK) {
|
||||
Uri uri = null;
|
||||
if (resultData != null) {
|
||||
uri = resultData.getData();
|
||||
InputStream input = null;
|
||||
loading = true;
|
||||
try {
|
||||
input = getApplication().getContentResolver().openInputStream(uri);
|
||||
} catch (FileNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
PickedFile = null;
|
||||
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
File file = new File(getCacheDir(), uri.getLastPathSegment());
|
||||
try {
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
FileOutputStream os = null;
|
||||
os = new FileOutputStream(file);
|
||||
int len = 0;
|
||||
int pos = 0;
|
||||
byte[] buf = new byte[1024*4];
|
||||
|
||||
while ((len = input.read(buf)) >= 0) {
|
||||
os.write(buf, 0, len);
|
||||
pos += len;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
PickedFile = null;
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
loading = false;
|
||||
PickedFile = file.getAbsolutePath();
|
||||
} else {
|
||||
PickedFile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
private static boolean loading;
|
||||
public static boolean IsLoading() {
|
||||
return loading;
|
||||
}
|
||||
private static String PickedFile = null;
|
||||
public static String GetPickedFile() {
|
||||
if (openDocumentIntent == null) {
|
||||
return "";
|
||||
} else if (PickedFile == null) {
|
||||
return "";
|
||||
} else {
|
||||
return PickedFile;
|
||||
}
|
||||
}
|
||||
public static void ClearSelected() {
|
||||
PickedFile = null;
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ buildscript {
|
|||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.1.1'
|
||||
classpath 'com.android.tools.build:gradle:8.3.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#Thu Nov 11 18:20:34 PST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
8
sdl-android-project/local.properties
Normal file
8
sdl-android-project/local.properties
Normal file
|
@ -0,0 +1,8 @@
|
|||
## This file must *NOT* be checked into Version Control Systems,
|
||||
# as it contains information specific to your local configuration.
|
||||
#
|
||||
# Location of the SDK. This is only used by Gradle.
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
#Thu Apr 25 11:32:33 PDT 2024
|
||||
sdk.dir=/home/catmeow/Android/Sdk
|
13
setup-android-project.ps1
Normal file
13
setup-android-project.ps1
Normal file
|
@ -0,0 +1,13 @@
|
|||
function Get-ScriptDirectory {
|
||||
$scriptpath = $MyInvocation.MyCommand.Path
|
||||
return Split-Path $scriptpath
|
||||
}
|
||||
$scriptdir = Get-ScriptDirectory
|
||||
Push-Location $scriptdir
|
||||
$android_project_dir = Join-Path($scriptdir, "sdl-android-project")
|
||||
$android_app_dir = Join-Path($android_project_dir, "app")
|
||||
$android_jni_dir = Join-Path($android_app_dir, "jni")
|
||||
if (test-path $android_jni_dir) {
|
||||
rm $android_jni_dir
|
||||
}
|
||||
New-Item -ItemType Junction -Path $android_jni_dir -Value $scriptdir
|
20
setup-android-project.sh
Executable file
20
setup-android-project.sh
Executable file
|
@ -0,0 +1,20 @@
|
|||
#!/bin/env -S NOT_SOURCED=1 /bin/sh
|
||||
if ! [ "$NOT_SOURCED" = "1" ]; then
|
||||
echo "Error: This script must not be sourced!" >&2
|
||||
return 1
|
||||
fi
|
||||
get_abs_filename() {
|
||||
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" | sed 's@/\./@/@g' | sed 's@/\.$@@g'
|
||||
}
|
||||
|
||||
export PROJECT_DIR="$(get_abs_filename $(dirname "$0"))"
|
||||
export ANDROID_PROJECT_DIR="${PROJECT_DIR}/sdl-android-project"
|
||||
export ANDROID_APP_DIR="${ANDROID_PROJECT_DIR}/app"
|
||||
export ANDROID_JNI_DIR="${ANDROID_APP_DIR}/jni"
|
||||
echo "Project directory: $PROJECT_DIR"
|
||||
echo "Android project directory: $ANDROID_PROJECT_DIR"
|
||||
echo "Android JNI symlink: $ANDROID_JNI_DIR -> $PROJECT_DIR"
|
||||
pushd "${PROJECT_DIR}"
|
||||
[ -d "$ANDROID_JNI_DIR" ] && rm -rf "$ANDROID_JNI_DIR"
|
||||
ln -sf "$PROJECT_DIR" "$ANDROID_JNI_DIR"
|
||||
popd
|
1
subprojects/oboe
Submodule
1
subprojects/oboe
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 86165b8249bc22b9ef70b69e20323244b6f08d88
|
Loading…
Reference in a new issue