Add support for macOS
Some checks failed
Build / build-gentoo (push) Failing after 14s
Build / download-system-deps (push) Successful in 3m28s
Build / get-source-code (push) Successful in 9m18s
Build / build-deb (push) Failing after 3m41s
Build / build-appimage (push) Successful in 4m12s
Build / build-android (push) Failing after 2m47s
Build / build-windows (push) Failing after 7m10s
Some checks failed
Build / build-gentoo (push) Failing after 14s
Build / download-system-deps (push) Successful in 3m28s
Build / get-source-code (push) Successful in 9m18s
Build / build-deb (push) Failing after 3m41s
Build / build-appimage (push) Successful in 4m12s
Build / build-android (push) Failing after 2m47s
Build / build-windows (push) Failing after 7m10s
- Remove unused OpenMP from playback backend - Fix update_assets on macOS - Add support for Objective C++ on macOS - Add macOS-specific code into the Dear ImGui backend - Add macOS .app packaging - Add support for global menus, and include support for macOS global menu into the Dear ImGui backend
This commit is contained in:
parent
e9c3a93e16
commit
51d4ed39ab
26 changed files with 875 additions and 81 deletions
|
@ -5,6 +5,13 @@ if("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten")
|
|||
set(EMSCRIPTEN ON)
|
||||
message("Building for WASM.")
|
||||
endif()
|
||||
if(APPLE)
|
||||
enable_language(OBJCXX)
|
||||
set(extra_linker_flags "-undefined dynamic_lookup")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${extra_linker_flags}")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${extra_linker_flags}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${extra_linker_flags}")
|
||||
endif()
|
||||
option(TESTS "Enables unit testing" OFF)
|
||||
find_package(Threads)
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||
|
@ -309,13 +316,13 @@ prefix_all(LIBRARY_SOURCES
|
|||
playback_backend.hpp
|
||||
translation.cpp
|
||||
translation.hpp
|
||||
playback_process.cpp
|
||||
playback_process.hpp
|
||||
base85.cpp
|
||||
base85.h
|
||||
)
|
||||
add_subdirectory(liblooperui)
|
||||
run_protoc(OUTDIR ${CMAKE_BINARY_DIR}/google/protobuf SOURCE google/protobuf/any.proto OUTVAR _src)
|
||||
add_library(liblooper SHARED ${LIBRARY_SOURCES})
|
||||
target_link_libraries(liblooper PUBLIC liblooper_ui)
|
||||
set_target_properties(liblooper PROPERTIES PREFIX "")
|
||||
if(FOR_WASMER)
|
||||
target_compile_definitions(liblooper PUBLIC FOR_WASMER)
|
||||
|
@ -531,7 +538,7 @@ playback_backend_subdir(NAME "ZSM" READABLE_NAME "ZSM" SUBDIR backends/playback/
|
|||
playback_backend_subdir(NAME "FLUIDSYNTH" READABLE_NAME "Fluidsynth" SUBDIR backends/playback/fluidsynth)
|
||||
playback_backend_subdir(NAME "GME" READABLE_NAME "Game Music Emu" SUBDIR backends/playback/gme)
|
||||
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen_ui_backend_inc.py ${CMAKE_CURRENT_BINARY_DIR} --ui ${ENABLED_UIS} --playback ${ENABLED_PLAYBACK_BACKENDS})
|
||||
prefix_all(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ main.cpp daemon_backend.cpp daemon_backend.hpp proxy_backend.cpp proxy_backend.hpp)
|
||||
prefix_all(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ playback_process.cpp playback_process.hpp main.cpp daemon_backend.cpp daemon_backend.hpp proxy_backend.cpp proxy_backend.hpp)
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/backend_glue.cpp)
|
||||
if(DEFINED EMSCRIPTEN)
|
||||
set(CMAKE_EXECUTABLE_SUFFIX ".html")
|
||||
|
@ -555,7 +562,20 @@ else()
|
|||
list(APPEND SOURCES ${CMAKE_BINARY_DIR}/haiku-res.rsrc)
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/haiku-res.rsrc COMMAND rc -o ${CMAKE_BINARY_DIR}/haiku-res.rsrc ${HAIKU_RES} DEPENDS ${HAIKU_RES} COMMENT Compiling resources)
|
||||
endif()
|
||||
if(APPLE)
|
||||
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${SOURCES})
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES
|
||||
BUNDLE True
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIES com.complecwaft.catmeow.Looper
|
||||
MACOSX_BUNDLE_NAME "Looper"
|
||||
MACOSX_BUNDLE_ICON_FILE icon
|
||||
MACOSX_BUNDLE_VERSION ${TAG}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${TAG}
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/assets/looper.plist.in
|
||||
)
|
||||
else()
|
||||
add_executable(${TARGET_NAME} ${SOURCES})
|
||||
endif()
|
||||
if(HAIKU)
|
||||
# add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND resattr -o ${TARGET_NAME} ${CMAKE_BINARY_DIR}/haiku-res.rsrc DEPENDS ${TARGET_NAME} ${CMAKE_BINARY_DIR}/haiku-res.rsrc COMMENT Adding resources to target attributes)
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND xres -o ${TARGET_NAME} ${CMAKE_BINARY_DIR}/haiku-res.rsrc DEPENDS ${TARGET_NAME} ${CMAKE_BINARY_DIR}/haiku-res.rsrc COMMENT Adding resources to target)
|
||||
|
@ -576,7 +596,14 @@ if(DEFINED EMSCRIPTEN)
|
|||
copy_to_bindir(assets/ForkAwesome/css/fork-awesome.min.css.map fork-awesome.min.css.map)
|
||||
endif()
|
||||
target_link_libraries(${TARGET_NAME} PUBLIC liblooper ${UI_BACKENDS} ${PLAYBACK_BACKENDS})
|
||||
install(TARGETS ${TARGET_NAME} liblooper ${EXTRA_LIBS} ${UI_BACKENDS} ${PLAYBACK_BACKENDS})
|
||||
if(APPLE)
|
||||
install(TARGETS ${TARGET_NAME} BUNDLE DESTINATION Looper.app)
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND ${CMAKE_SOURCE_DIR}/mkicns.sh ${CMAKE_SOURCE_DIR}/assets/icon.svg ${CMAKE_BINARY_DIR}/${TARGET_NAME}.app/Contents/Resources/icon)
|
||||
copy_to_bindir(assets/icon.svg ${TARGET_NAME}.app/Contents/icon.svg)
|
||||
else()
|
||||
install(TARGETS ${TARGET_NAME})
|
||||
endif()
|
||||
install(TARGETS liblooper ${EXTRA_LIBS} ${UI_BACKENDS} ${PLAYBACK_BACKENDS})
|
||||
if (UNIX AND NOT APPLE)
|
||||
install(FILES assets/zsm-mime.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/audio RENAME x-zsound.xml)
|
||||
endif()
|
||||
|
|
34
assets/looper.plist.in
Normal file
34
assets/looper.plist.in
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLongVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python3
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
|
|
@ -2,8 +2,7 @@ set(BACKEND_FLUIDSYNTH_SRC ${CMAKE_CURRENT_SOURCE_DIR}/fluidsynth_backend.cpp)
|
|||
set(BACKEND_FLUIDSYNTH_INC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_playback_backend(fluidsynth_backend ${BACKEND_FLUIDSYNTH_SRC})
|
||||
target_include_directories(fluidsynth_backend PRIVATE ${BACKEND_FLUIDSYNTH_INC})
|
||||
find_package(OpenMP)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(fluidsynth fluidsynth IMPORTED_TARGET)
|
||||
|
||||
target_link_libraries(fluidsynth_backend PUBLIC OpenMP::OpenMP_CXX PkgConfig::fluidsynth)
|
||||
target_link_libraries(fluidsynth_backend PUBLIC PkgConfig::fluidsynth)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
#include "playback_backend.hpp"
|
||||
#include <omp.h>
|
||||
#include <cstdint>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
|
|
@ -3,5 +3,4 @@ set(BACKEND_GME_INC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
|
|||
pkg_check_modules(libgme IMPORTED_TARGET libgme)
|
||||
add_playback_backend(gme_backend ${BACKEND_GME_SRC})
|
||||
target_include_directories(gme_backend PRIVATE ${BACKEND_GME_INC})
|
||||
find_package(OpenMP)
|
||||
target_link_libraries(gme_backend PUBLIC PkgConfig::libgme OpenMP::OpenMP_CXX)
|
||||
target_link_libraries(gme_backend PUBLIC PkgConfig::libgme)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
#include "playback_backend.hpp"
|
||||
#include <omp.h>
|
||||
#include <cstdint>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
#include "sdl_mixer_x.hpp"
|
||||
#ifdef __MACOSX__
|
||||
#include <machine/endian.h>
|
||||
#else
|
||||
#include <endian.h>
|
||||
#endif
|
||||
#include <filesystem>
|
||||
#include "file_backend.hpp"
|
||||
#include <stddef.h>
|
||||
|
|
|
@ -4,5 +4,4 @@ set(BACKEND_ZSM_SRC ${CMAKE_CURRENT_SOURCE_DIR}/zsm_backend.cpp ${X16_DIR}/vera_
|
|||
set(BACKEND_ZSM_INC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${YMFM_DIR} ${X16_DIR})
|
||||
add_playback_backend(zsm_backend ${BACKEND_ZSM_SRC})
|
||||
target_include_directories(zsm_backend PRIVATE ${BACKEND_ZSM_INC})
|
||||
find_package(OpenMP)
|
||||
target_link_libraries(zsm_backend PUBLIC OpenMP::OpenMP_CXX)
|
||||
target_link_libraries(zsm_backend PUBLIC)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
#include "playback_backend.hpp"
|
||||
#include <omp.h>
|
||||
#include "x16emu/ymglue.h"
|
||||
#include <cstdint>
|
||||
#include <stddef.h>
|
||||
|
|
|
@ -10,6 +10,12 @@ 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_sdlrenderer2.cpp imgui_impl_sdl2.cpp)
|
||||
set(BACKEND_IMGUI_SRC_BASE main.cpp file_browser.cpp main.cpp RendererBackend.cpp theme.cpp base85.h file_browser.h main.h RendererBackend.h ui_backend.hpp theme.h)
|
||||
set(BACKEND_IMGUI_OSX_SRC_BASE RendererBackendOSX.mm file_browser_osx.mm)
|
||||
if(APPLE)
|
||||
foreach(SRC IN ITEMS ${BACKEND_IMGUI_OSX_SRC_BASE})
|
||||
list(APPEND BACKEND_IMGUI_SRC_BASE ${SRC})
|
||||
endforeach()
|
||||
endif()
|
||||
foreach(SRC IN ITEMS ${IMGUI_BACKEND_SRC})
|
||||
list(APPEND IMGUI_SRC backends/${SRC})
|
||||
endforeach()
|
||||
|
@ -50,6 +56,9 @@ add_ui_backend(imgui_ui ${BACKEND_IMGUI_SRC})
|
|||
if(USE_GLES)
|
||||
target_compile_definitions(imgui_ui PRIVATE IMGUI_IMPL_OPENGL_ES${GLES_VERSION})
|
||||
endif()
|
||||
if(APPLE)
|
||||
target_link_libraries(imgui_ui PRIVATE "-framework Cocoa" "-framework Foundation" "-framework AppKit" "-framework UniformTypeIdentifiers")
|
||||
endif()
|
||||
if(DEFINED EMSCRIPTEN)
|
||||
target_compile_options(imgui_ui PRIVATE "-sUSE_SDL_IMAGE=2")
|
||||
target_link_options(imgui_ui PUBLIC "-sMAX_WEBGL_VERSION=2" "-sMIN_WEBGL_VERSION=2" "-sFULL_ES3")
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <web_functions.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
#include <SDL_syswm.h>
|
||||
using std::vector;
|
||||
using namespace Looper::Options;
|
||||
void RendererBackend::on_resize() {
|
||||
|
@ -29,6 +30,15 @@ void RendererBackend::on_resize() {
|
|||
UpdateScale();
|
||||
#endif
|
||||
}
|
||||
#ifdef __MACOSX__
|
||||
extern SDL_Window *CreateWindow();
|
||||
extern void SetWindowProperties(bool enableBorderless);
|
||||
extern float GetPostButtonPos();
|
||||
extern float GetTitlebarHeight();
|
||||
extern void SetTitle(const char *str);
|
||||
extern void SetSubtitle(const char *str);
|
||||
extern void SetMenubarWidth(float width);
|
||||
#endif
|
||||
static RendererBackend *renderer_backend;
|
||||
|
||||
void RendererBackend::resize_static() {
|
||||
|
@ -337,8 +347,12 @@ void RendererBackend::BackendInit() {
|
|||
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
|
||||
#endif
|
||||
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN | SDL_WINDOW_BORDERLESS);
|
||||
#ifdef __MACOSX__
|
||||
window = CreateWindow();
|
||||
rend = SDL_CreateRenderer(window, -1, 0);
|
||||
#else
|
||||
SDL_CreateWindowAndRenderer(window_width, window_height, window_flags, &window, &rend);
|
||||
|
||||
#endif
|
||||
#ifndef __ANDROID__
|
||||
SDL_SetWindowMinimumSize(window, window_width, window_height);
|
||||
if (enable_kms) {
|
||||
|
@ -417,18 +431,38 @@ bool RendererBackend::UsingSystemTitlebar() {
|
|||
}
|
||||
void RendererBackend::EnableSystemTitlebar(bool enabled) {
|
||||
enable_system_title_bar = enabled;
|
||||
#ifdef __MACOSX__
|
||||
SetWindowProperties(!enable_system_title_bar);
|
||||
#else
|
||||
SDL_SetWindowBordered(window, enable_system_title_bar ? SDL_TRUE : SDL_FALSE);
|
||||
#endif
|
||||
}
|
||||
bool RendererBackend::BeginMainMenuBar() {
|
||||
main_menu_bar_used = true;
|
||||
float fontHeight = ImGui::GetFontSize();
|
||||
#ifdef __MACOSX__
|
||||
float titlebarHeight = GetTitlebarHeight();
|
||||
titlebar_height = (int)titlebarHeight;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(ImGui::GetStyle().ItemInnerSpacing.x, titlebarHeight - fontHeight));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetStyle().FramePadding.x, titlebarHeight - fontHeight));
|
||||
#endif
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
if (!enable_system_title_bar) {
|
||||
ImVec2 winsize = ImGui::GetWindowSize();
|
||||
float texsize = winsize.y;
|
||||
ImVec2 windowSize = ImGui::GetWindowSize();
|
||||
float posY = (windowSize.y - fontHeight) / 2.0f;
|
||||
#ifndef __MACOSX__
|
||||
float texsize = windowSize.y;
|
||||
ImGui::SetCursorPosX(0);
|
||||
ImGui::SetCursorPosY(0);
|
||||
ImGui::Image((ImTextureID)(icon_texture), ImVec2(texsize, texsize));
|
||||
ImGui::SetCursorPosY(posY);
|
||||
ImGui::TextUnformatted(title_text.c_str());
|
||||
#else
|
||||
float posX = GetPostButtonPos() + ImGui::GetStyle().ItemSpacing.x;
|
||||
ImGui::SetCursorPosX(posX);
|
||||
ImGui::SetCursorPosY(posY);
|
||||
ImGui::TextUnformatted(title_text.c_str());
|
||||
#endif
|
||||
}
|
||||
menubar_start = ImGui::GetCursorPosX();
|
||||
return true;
|
||||
|
@ -475,7 +509,11 @@ SDL_HitTestResult RendererBackend::HitTest(SDL_Window *window, const SDL_Point *
|
|||
return SDL_HITTEST_RESIZE_RIGHT;
|
||||
}
|
||||
}
|
||||
if (area->y < (16 * this->scale) && (area->x < menubar_start || (area->x > menubar_end && area->x < title_btn_start))) {
|
||||
if (area->y < (titlebar_height * this->scale) && (area->x < menubar_start || (area->x > menubar_end
|
||||
#ifndef __MACOSX__
|
||||
&& area->x < title_btn_start
|
||||
#endif
|
||||
))) {
|
||||
return SDL_HITTEST_DRAGGABLE;
|
||||
} else {
|
||||
return SDL_HITTEST_NORMAL;
|
||||
|
@ -487,23 +525,27 @@ void RendererBackend::SetSubtitle(const char *subtitle) {
|
|||
update_real_title();
|
||||
}
|
||||
void RendererBackend::update_real_title() {
|
||||
#ifdef __MACOSX__
|
||||
::SetTitle(title_text.c_str());
|
||||
::SetSubtitle(subtitle.c_str());
|
||||
#else
|
||||
if (subtitle == "") {
|
||||
SDL_SetWindowTitle(window, title_text.c_str());
|
||||
} else {
|
||||
SDL_SetWindowTitle(window, fmt::format("{} - {}", subtitle, title_text).c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void RendererBackend::EndMainMenuBar() {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
if (!enable_system_title_bar) {
|
||||
menubar_end = ImGui::GetCursorPosX();
|
||||
ImVec2 size = ImGui::GetWindowSize();
|
||||
if (subtitle != "") {
|
||||
ImVec4 text_color = Theme::GetColor(LooperCol_Subtitle);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, text_color);
|
||||
ImGui::TextUnformatted(subtitle.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
#ifndef __MACOSX__
|
||||
float btnw = size.y;
|
||||
int btn_count = 3;
|
||||
ImVec2 btn_size(btnw, btnw);
|
||||
|
@ -524,7 +566,12 @@ void RendererBackend::EndMainMenuBar() {
|
|||
else tmp -= spacing;
|
||||
tmp -= ImGui::CalcTextSize(titlebar_icons[i]).x;
|
||||
}
|
||||
#else
|
||||
float tmp = size.x;
|
||||
SetMenubarWidth(menubar_end-menubar_start);
|
||||
#endif
|
||||
title_btn_start = std::ceil(tmp);
|
||||
#ifndef __MACOSX__
|
||||
ImGui::SetCursorPosX(title_btn_start);
|
||||
if (ImGui::MenuItem(titlebar_icons[0])) {
|
||||
SDL_MinimizeWindow(window);
|
||||
|
@ -536,9 +583,14 @@ void RendererBackend::EndMainMenuBar() {
|
|||
if (ImGui::MenuItem(titlebar_icons[2])) {
|
||||
done = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
ImGui::EndMainMenuBar();
|
||||
#ifdef __MACOSX__
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar();
|
||||
#endif
|
||||
}
|
||||
int RendererBackend::Run() {
|
||||
framerate = 60;
|
||||
|
|
|
@ -30,6 +30,7 @@ class RendererBackend {
|
|||
int menubar_start;
|
||||
int menubar_end;
|
||||
int title_btn_start;
|
||||
int titlebar_height = 16;
|
||||
std::string subtitle;
|
||||
std::string title_text;
|
||||
bool enable_system_title_bar;
|
||||
|
|
63
backends/ui/imgui/RendererBackendOSX.mm
Normal file
63
backends/ui/imgui/RendererBackendOSX.mm
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include <Foundation/Foundation.h>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <SDL.h>
|
||||
#include <log.hpp>
|
||||
NSWindow *window;
|
||||
NSTitlebarAccessoryViewController *controller;
|
||||
NSView *titlebarDummyView;
|
||||
extern "C++" {
|
||||
SDL_Window *CreateWindow() {
|
||||
window = [[NSWindow alloc] init];
|
||||
controller = [[NSTitlebarAccessoryViewController alloc] init];
|
||||
titlebarDummyView = [[NSView alloc] init];
|
||||
[controller setView:titlebarDummyView];
|
||||
[controller setLayoutAttribute:NSLayoutAttributeLeft];
|
||||
[window addTitlebarAccessoryViewController:controller];
|
||||
return SDL_CreateWindowFrom((void*)[window contentView]);
|
||||
}
|
||||
void SetWindowProperties(bool enableBorderless) {
|
||||
NSWindowStyleMask windowMask = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskTitled;
|
||||
if (enableBorderless) {
|
||||
windowMask |= NSWindowStyleMaskFullSizeContentView;
|
||||
}
|
||||
[window setTitleVisibility: enableBorderless ? NSWindowTitleHidden : NSWindowTitleVisible];
|
||||
[window setStyleMask: windowMask];
|
||||
[window setTitlebarAppearsTransparent: enableBorderless];
|
||||
NSView *contentView = [window contentView];
|
||||
NSView *frameView = [contentView superview];
|
||||
for (NSTrackingArea *trackingArea : [frameView trackingAreas]) {
|
||||
[contentView addTrackingArea:trackingArea];
|
||||
DEBUG.writeln("Adding tracking area...");
|
||||
}
|
||||
}
|
||||
float GetPostButtonPos() {
|
||||
NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton];
|
||||
NSButton *zoomButton = [window standardWindowButton:NSWindowZoomButton];
|
||||
NSButton *miniatureButton = [window standardWindowButton:NSWindowMiniaturizeButton];
|
||||
NSRect closeRect = closeButton.frame;
|
||||
NSRect zoomRect = zoomButton.frame;
|
||||
NSRect miniatureRect = miniatureButton.frame;
|
||||
float closeX = NSMinX(closeRect);
|
||||
float zoomX = NSMinX(zoomRect);
|
||||
float miniatureX = NSMinX(miniatureRect);
|
||||
float zoomEndX = NSMaxX(zoomRect);
|
||||
return zoomEndX;
|
||||
}
|
||||
void SetMenubarWidth(float width) {
|
||||
[titlebarDummyView setBounds:NSMakeRect(0, 0, width, 0)];
|
||||
}
|
||||
float GetTitlebarHeight() {
|
||||
NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton];
|
||||
NSRect closeRect = closeButton.frame;
|
||||
float topY = NSMinY(closeRect);
|
||||
float bottomY = NSMaxY(closeRect);
|
||||
return bottomY;// + topY;
|
||||
}
|
||||
|
||||
void SetSubtitle(const char *str) {
|
||||
[window setSubtitle: [NSString stringWithUTF8String:str]];
|
||||
}
|
||||
void SetTitle(const char *str) {
|
||||
[window setTitle: [NSString stringWithUTF8String:str]];
|
||||
}
|
||||
}
|
|
@ -39,6 +39,9 @@ const char *GetPickedFile() {
|
|||
void ClearSelected() {
|
||||
env->CallStaticVoidMethod(MainActivity, ClearSelected_Method, 0);
|
||||
}
|
||||
#elif defined(__MACOSX__)
|
||||
extern const char *OpenDialog(std::vector<std::string> allowedFileTypes, std::string initialDirectory);
|
||||
extern const char *SaveDialog(std::vector<std::string> allowedFileTypes, std::string initialDirectory);
|
||||
#endif
|
||||
FileBrowser::FileBrowser(bool save, ImGuiFileBrowserFlags extra_fallback_flags) {
|
||||
#ifdef PORTALS
|
||||
|
@ -92,12 +95,12 @@ void FileBrowser::SetTypeFilters(string name, vector<string> filters) {
|
|||
}
|
||||
void FileBrowser::SetPwd(path path) {
|
||||
pwd = path;
|
||||
#if !(defined(PORTALS) || defined(__ANDROID__))
|
||||
#if !(defined(PORTALS) || defined(__ANDROID__) || defined(__MACOSX__))
|
||||
fallback.SetPwd(path);
|
||||
#endif
|
||||
}
|
||||
bool FileBrowser::HasSelected() {
|
||||
#ifdef PORTALS
|
||||
#if defined(PORTALS) || defined(__MACOSX__)
|
||||
return selected.has_value();
|
||||
#elif defined(__ANDROID__)
|
||||
return strlen(GetPickedFile()) > 0;
|
||||
|
@ -108,7 +111,7 @@ bool FileBrowser::HasSelected() {
|
|||
#endif
|
||||
}
|
||||
path FileBrowser::GetSelected() {
|
||||
#ifdef PORTALS
|
||||
#if defined(PORTALS) || defined(__MACOSX__)
|
||||
return selected.value_or(path());
|
||||
#elif defined(__ANDROID__)
|
||||
const char *file = GetPickedFile();
|
||||
|
@ -146,6 +149,19 @@ 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(__MACOSX__)
|
||||
open = true;
|
||||
const char *output = nullptr;
|
||||
if (save) {
|
||||
output = SaveDialog(filters, pwd.string());
|
||||
} else {
|
||||
output = OpenDialog(filters, pwd.string());
|
||||
}
|
||||
if (output == nullptr) {
|
||||
selected = {};
|
||||
} else {
|
||||
selected = path(output);
|
||||
}
|
||||
#elif defined(__ANDROID__)
|
||||
ClearSelected();
|
||||
open = true;
|
||||
|
@ -293,7 +309,7 @@ void FileBrowser::Display() {
|
|||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
#else
|
||||
#elif !defined(__MACOSX__)
|
||||
fallback.Display();
|
||||
#endif
|
||||
}
|
||||
|
@ -312,14 +328,12 @@ void FileBrowser::ClearSelected() {
|
|||
}
|
||||
void FileBrowser::SetTitle(string title) {
|
||||
this->title = title;
|
||||
#ifndef PORTALS
|
||||
#if !defined(PORTALS) && !defined(__MACOSX__)
|
||||
fallback.SetTitle(title);
|
||||
#endif
|
||||
}
|
||||
bool FileBrowser::IsOpened() {
|
||||
#ifdef PORTALS
|
||||
return open;
|
||||
#elif defined(__ANDROID__)
|
||||
#if defined(PORTALS) || defined(__ANDROID__) || defined(__MACOSX__)
|
||||
return open;
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
return !file_picker_closed() || file_picker_confirmed();
|
||||
|
|
52
backends/ui/imgui/file_browser_osx.mm
Normal file
52
backends/ui/imgui/file_browser_osx.mm
Normal file
|
@ -0,0 +1,52 @@
|
|||
#include <Foundation/Foundation.h>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <UniformTypeIdentifiers/UTCoreTypes.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
extern "C++" {
|
||||
UTType *GetUTIFromExtension(NSString *extension) {
|
||||
return [UTType typeWithFilenameExtension:extension];
|
||||
}
|
||||
NSMutableArray *ConvertExtensions(std::vector<std::string> input) {
|
||||
NSMutableArray *arr = [[[NSMutableArray alloc] init] autorelease];
|
||||
for (auto &str : input) {
|
||||
UTType *maybeUTI = GetUTIFromExtension([NSString stringWithUTF8String:str.c_str()]);
|
||||
if (maybeUTI != nil) {
|
||||
[arr addObject:maybeUTI];
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
const char *OpenDialog(std::vector<std::string> fileTypes, std::string initialDirectory) {
|
||||
NSMutableArray *arr = ConvertExtensions(fileTypes);
|
||||
NSOpenPanel* openFileDialog = [[[NSOpenPanel alloc] init] autorelease];
|
||||
[openFileDialog setCanChooseFiles:YES];
|
||||
[openFileDialog setCanChooseDirectories:NO];
|
||||
[openFileDialog setAllowsMultipleSelection:NO];
|
||||
[openFileDialog setAllowedContentTypes:arr];
|
||||
[openFileDialog setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:initialDirectory.c_str()]]];
|
||||
|
||||
NSModalResponse response = [openFileDialog runModal];
|
||||
if (response == NSModalResponseOK) {
|
||||
return [[(NSURL*)[[openFileDialog URLs] objectAtIndex:0] path] UTF8String];
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const char *SaveDialog(std::vector<std::string> fileTypes, std::string initialDirectory) {
|
||||
NSSavePanel *panel = [[[NSSavePanel alloc] init] autorelease];
|
||||
[panel setCanCreateDirectories:YES];
|
||||
NSMutableArray *arr = ConvertExtensions(fileTypes);
|
||||
[panel setAllowedContentTypes:arr];
|
||||
[panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:initialDirectory.c_str()]]];
|
||||
|
||||
NSModalResponse response = [panel runModal];
|
||||
if (response == NSModalResponseOK) {
|
||||
return [[[panel URL] path] UTF8String];
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||
#include <web_functions.hpp>
|
||||
#include <cats.hpp>
|
||||
#include <menus.hpp>
|
||||
using namespace Looper::Options;
|
||||
void MainLoop::Init() {
|
||||
#ifdef PORTALS
|
||||
|
@ -153,6 +154,64 @@ void MainLoop::Init() {
|
|||
theme = new Theme(darkPath);
|
||||
}
|
||||
}
|
||||
{
|
||||
MenuBuilder builder;
|
||||
auto quitHandler = [this]() {
|
||||
done = true;
|
||||
};
|
||||
auto aboutHandler = [this]() {
|
||||
about_window = !about_window;
|
||||
};
|
||||
auto prefsHandler = [this]() {
|
||||
prefs_window = !prefs_window;
|
||||
};
|
||||
rootMenu = builder
|
||||
.BeginSubmenu("Looper", MenuType_Application, OSOptions::OnlyMac)
|
||||
.AddItemSimple("About Looper", "", aboutHandler)
|
||||
.AddItemSimple("Preferences...", ",", prefsHandler)
|
||||
.AddEmptySubmenu("Services...", MenuType_Services)
|
||||
.AddItemSimple("Quit", "q", quitHandler)
|
||||
.EndSubmenu()
|
||||
.BeginSubmenu("File")
|
||||
.AddItemSimple("Open", "o", [this]() {
|
||||
this->fileDialog.SetTitle(_TR_CTX("File dialog title", "Open..."));
|
||||
this->fileDialog.SetTypeFilters(_TR_CTX("File dialog filter name", "Audio files"), { ".wav", ".ogg", ".mp3", ".qoa", ".flac", ".xm", ".mod"});
|
||||
this->fileDialog.Open();
|
||||
})
|
||||
#ifdef __EMSCRIPTEN__
|
||||
.AddItemSimple("Update", "", [this]() {
|
||||
if (serviceworker_registered()) {
|
||||
update();
|
||||
}
|
||||
})
|
||||
#endif
|
||||
.AddItemSimple("Quit", "", quitHandler, OSOptions::ExcludeMac)
|
||||
.EndSubmenu()
|
||||
.BeginSubmenu("Edit", MenuType_Default, OSOptions::ExcludeMac)
|
||||
.AddItemSimple("Preferences", "", prefsHandler)
|
||||
.EndSubmenu()
|
||||
.BeginSubmenu("Debug")
|
||||
.AddItemSimple("Show ImGui Demo Window", "", [this]() {
|
||||
show_demo_window = !show_demo_window;
|
||||
set_option<double>("ui.imgui.demo_window", show_demo_window);
|
||||
})
|
||||
.AddItemSimple("Property Editor", "", [this]() {
|
||||
property_editor = !property_editor;
|
||||
})
|
||||
.EndSubmenu()
|
||||
.AddEmptySubmenu("Window", MenuType_Window, OSOptions::OnlyMac)
|
||||
.BeginSubmenu("Help", MenuType_Help)
|
||||
.AddItemSimple("About", "", aboutHandler, OSOptions::ExcludeMac)
|
||||
.EndSubmenu()
|
||||
.Build();
|
||||
debugMenu = (Menu*)FindMenu(rootMenu, "Debug");
|
||||
quitItem = (MenuItem*)FindMenu(rootMenu, "Quit");
|
||||
#ifdef __EMSCRIPTEN__
|
||||
updateItem = (MenuItem*)FindMenu(rootMenu, "Update");
|
||||
#endif
|
||||
debugMenu->visible = debug_mode;
|
||||
menu_showing = PublishMenu(rootMenu, nullptr);
|
||||
}
|
||||
EnableSystemTitlebar(get_option<bool>("ui.imgui.enable_system_titlebar", false));
|
||||
theme->Apply(accent_color, (float)scale);
|
||||
SetWindowTitle("Looper");
|
||||
|
@ -235,6 +294,49 @@ void MainLoop::FileLoaded() {
|
|||
}
|
||||
}
|
||||
}
|
||||
struct ImGuiMenuData {
|
||||
bool showing;
|
||||
Menu *menu;
|
||||
};
|
||||
struct ImGuiMenuCallbacks : public MenuIteratorCallbacks {
|
||||
std::stack<ImGuiMenuData> menuData;
|
||||
bool shouldIncludeNextMenuItems() {
|
||||
return menuData.top().showing;
|
||||
}
|
||||
void Init(Menu *root) {
|
||||
menuData.push({.showing = true, .menu = root});
|
||||
}
|
||||
void Finalize() {
|
||||
while (menuData.size() > 1) {
|
||||
EndSubmenu();
|
||||
}
|
||||
}
|
||||
void BeginSubmenu(Menu *menu) {
|
||||
bool showing = false;
|
||||
if (shouldIncludeNextMenuItems()) {
|
||||
showing = ImGui::BeginMenu(menu->title.c_str());
|
||||
}
|
||||
menuData.push({.showing = showing, .menu = menu});
|
||||
}
|
||||
void EndSubmenu() {
|
||||
if (shouldIncludeNextMenuItems()) {
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
menuData.pop();
|
||||
}
|
||||
void AddSeparator() {
|
||||
if (shouldIncludeNextMenuItems()) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
}
|
||||
void AddNormalItem(MenuItem *item) {
|
||||
if (shouldIncludeNextMenuItems()) {
|
||||
if (ImGui::MenuItem(item->title.c_str())) {
|
||||
item->RunAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
void MainLoop::GuiFunction() {
|
||||
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
|
||||
playback->LoopHook();
|
||||
|
@ -251,53 +353,13 @@ void MainLoop::GuiFunction() {
|
|||
if (show_demo_window)
|
||||
ImGui::ShowDemoWindow(&show_demo_window);
|
||||
if (BeginMainMenuBar()) {
|
||||
if (ImGui::BeginMenu(_TRI_CTX(ICON_FK_FILE, "Main menu", "File"))) {
|
||||
if (ImGui::MenuItem(_TRI_CTX(ICON_FK_FOLDER_OPEN, "Main menu | File", "Open"))) {
|
||||
// Set translatable strings here so that they are in the correct language even when it changes at runtime.
|
||||
fileDialog.SetTitle(_TR_CTX("File dialog title", "Open..."));
|
||||
fileDialog.SetTypeFilters(_TR_CTX("File dialog filter name", "Audio files"), { ".wav", ".ogg", ".mp3", ".qoa", ".flac", ".xm", ".mod"});
|
||||
fileDialog.Open();
|
||||
}
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if (serviceworker_registered()) {
|
||||
if (ImGui::MenuItem(_TRI_CTX(ICON_FK_DOWNLOAD, "Main menu | File", "Update"))) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
if (is_puter_enabled()) {
|
||||
#endif
|
||||
if (ImGui::MenuItem(_TRI_CTX(ICON_FK_WINDOW_CLOSE, "Main menu | File", "Quit"))) {
|
||||
done = true;
|
||||
}
|
||||
#ifdef __EMSCRIPTEN__
|
||||
}
|
||||
#endif
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu(_TRI_CTX(ICON_FK_SCISSORS,"Main menu", "Edit"))) {
|
||||
if (ImGui::MenuItem(_TRI_CTX(ICON_FK_COG, "Main menu | Edit", "Preferences..."))) {
|
||||
prefs_window = true;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
#ifndef DEBUG_MODE
|
||||
if (debug_mode)
|
||||
#endif
|
||||
if (ImGui::BeginMenu(_TRI_CTX(ICON_FK_COG, "Main menu (in debug builds)", "Debug"))) {
|
||||
if (ImGui::MenuItem(_TR_CTX("Main menu | Debug", "Show ImGui Demo Window"), nullptr, show_demo_window)) {
|
||||
show_demo_window = !show_demo_window;
|
||||
set_option<double>("ui.imgui.demo_window", show_demo_window);
|
||||
}
|
||||
if (ImGui::MenuItem(_TR_CTX("Main menu | Debug", "Edit properties"), nullptr, property_editor)) {
|
||||
property_editor = !property_editor;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu(_TRI_CTX(ICON_FK_INFO_CIRCLE, "Main menu", "Help"))) {
|
||||
if (ImGui::MenuItem(_TRI_CTX(ICON_FK_INFO, "Main menu | Help", "About"), nullptr, about_window)) {
|
||||
about_window = !about_window;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
if (!menu_showing) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
updateItem->visible = serviceworker_registered();
|
||||
quitItem->visible = is_puter_enabled();
|
||||
#endif
|
||||
ImGuiMenuCallbacks callbacks;
|
||||
IterateMenu(rootMenu, callbacks);
|
||||
}
|
||||
EndMainMenuBar();
|
||||
}
|
||||
|
@ -596,6 +658,8 @@ void MainLoop::GuiFunction() {
|
|||
}
|
||||
if (ImGui::Checkbox(_TR_CTX("Preference | Debug menu enable", "Enable debug menu in release builds"), &debug_mode)) {
|
||||
set_option<bool>("ui.imgui.debug_mode", debug_mode);
|
||||
debugMenu->visible = debug_mode;
|
||||
PublishMenu(rootMenu);
|
||||
}
|
||||
bool tmp_enable_system_title_bar = UsingSystemTitlebar();
|
||||
if (ImGui::Checkbox(_TR_CTX("Preference | System title bar", "Enable system title bar"), &tmp_enable_system_title_bar)) {
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
#endif
|
||||
#include "../../../backend.hpp"
|
||||
#include "ui_backend.hpp"
|
||||
#include <menus.hpp>
|
||||
using namespace LooperUI;
|
||||
using namespace std::filesystem;
|
||||
using std::string;
|
||||
#define IMGUI_FRONTEND
|
||||
|
@ -50,6 +52,11 @@ class MainLoop : public RendererBackend {
|
|||
bool restart_needed = false;
|
||||
bool stopped = true;
|
||||
bool enable_cat = false;
|
||||
Menu *debugMenu;
|
||||
MenuItem *quitItem;
|
||||
MenuItem *updateItem;
|
||||
Menu *rootMenu;
|
||||
bool menu_showing;
|
||||
std::string cat_setting = "__default__";
|
||||
std::vector<UIBackend*> backends;
|
||||
UIBackend *cur_backend;
|
||||
|
|
13
liblooperui/CMakeLists.txt
Normal file
13
liblooperui/CMakeLists.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
prefix_all(LIBLOOPERUI_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/ menus.cpp menus.hpp)
|
||||
if(APPLE)
|
||||
set(backend_src backends/osx_backend.mm)
|
||||
# TODO: Implement dbusmenu
|
||||
#elseif(UNIX AND ENABLE_DBUS)
|
||||
# set(backend_src backends/dbus_backend.cpp)
|
||||
else()
|
||||
set(backend_src backends/noop_backend.cpp)
|
||||
endif()
|
||||
list(APPEND LIBLOOPERUI_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/${backend_src})
|
||||
add_library(liblooper_ui SHARED ${LIBLOOPERUI_SRCS})
|
||||
target_link_libraries(liblooper_ui PUBLIC fmt::fmt)
|
||||
target_include_directories(liblooper_ui PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR})
|
5
liblooperui/backends/noop_backend.cpp
Normal file
5
liblooperui/backends/noop_backend.cpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
#include <menus.hpp>
|
||||
bool LooperUI::PublishMenu(Menu*, PlatformData*) {
|
||||
// No-op
|
||||
return false;
|
||||
}
|
103
liblooperui/backends/osx_backend.mm
Normal file
103
liblooperui/backends/osx_backend.mm
Normal file
|
@ -0,0 +1,103 @@
|
|||
#include <menus.hpp>
|
||||
#include <Foundation/Foundation.h>
|
||||
#include <AppKit/AppKit.h>
|
||||
#include "../../log.hpp"
|
||||
#include <stack>
|
||||
using namespace LooperUI;
|
||||
@interface MenuHelper : NSObject {
|
||||
std::map<id, MenuItem*> menuItems;
|
||||
}
|
||||
- (void)menuItemClicked:(id)sender;
|
||||
- (void)addMenuItem:(id)menuItem :(MenuItem*)looperItem;
|
||||
@end
|
||||
@implementation MenuHelper
|
||||
- (void)addMenuItem:(id)menuItem :(MenuItem*)looperItem {
|
||||
menuItems[menuItem] = looperItem;
|
||||
}
|
||||
- (void)menuItemClicked:(id)sender {
|
||||
if (!menuItems.contains(sender)) {
|
||||
NSLog(@"Failed to find menu item!");
|
||||
return;
|
||||
}
|
||||
MenuItem *looper_item = menuItems[sender];
|
||||
looper_item->RunAction();
|
||||
}
|
||||
@end
|
||||
MenuHelper *menuHelper;
|
||||
NSMenuItem *windowItem;
|
||||
extern "C++" {
|
||||
struct PublishMenuCallbacks : public MenuIteratorCallbacks {
|
||||
std::stack<NSMenu*> menu_stack;
|
||||
void print_spaces() {
|
||||
for (size_t i = 1; i < menu_stack.size(); i++) {
|
||||
DEBUG.writes(" ");
|
||||
}
|
||||
}
|
||||
void Init(Menu *root) {
|
||||
menuHelper = [[[MenuHelper alloc] init] autorelease];
|
||||
NSMenu *osx_root = [[[NSMenu alloc] init] autorelease];
|
||||
[NSApp setMainMenu:osx_root];
|
||||
menu_stack.push(osx_root);
|
||||
}
|
||||
void Finalize() {
|
||||
}
|
||||
void AddNormalItem(MenuItem *item) {
|
||||
NSMenuItem *osx_item = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:item->title.c_str()] action:@selector(menuItemClicked:) keyEquivalent:[NSString stringWithUTF8String:item->keybindString.c_str()]] autorelease];
|
||||
[osx_item setHidden:!item->visible];
|
||||
[osx_item setEnabled:item->enabled];
|
||||
[osx_item setTarget:menuHelper];
|
||||
[menuHelper addMenuItem:osx_item:item];
|
||||
[menu_stack.top() addItem:osx_item];
|
||||
print_spaces();
|
||||
DEBUG.writefln("[Item] %s", item->title.c_str());
|
||||
}
|
||||
void BeginSubmenu(Menu *menu) {
|
||||
print_spaces();
|
||||
NSMenu *submenu;
|
||||
NSMenuItem *menuItem;
|
||||
if (menu->type == MenuType_Window && windowItem != nullptr) {
|
||||
menuItem = windowItem;
|
||||
submenu = [windowItem submenu];
|
||||
[[menuItem menu] removeItem:menuItem];
|
||||
} else {
|
||||
submenu = [[[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:menu->title.c_str()]] autorelease];
|
||||
menuItem = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:menu->title.c_str()] action:nil keyEquivalent:@""] autorelease];
|
||||
}
|
||||
[menuItem setHidden:!menu->visible];
|
||||
[menuItem setEnabled:menu->enabled];
|
||||
[menuItem setSubmenu:submenu];
|
||||
[menu_stack.top() addItem:menuItem];
|
||||
if (menu->type == MenuType_Services) {
|
||||
[NSApp setServicesMenu:submenu];
|
||||
DEBUG.writefln("[Services] %s", menu->title.c_str());
|
||||
} else if (menu->type == MenuType_Window) {
|
||||
if (windowItem == nullptr) {
|
||||
[NSApp setWindowsMenu:submenu];
|
||||
windowItem = menuItem;
|
||||
}
|
||||
DEBUG.writefln("[Window] %s", menu->title.c_str());
|
||||
} else if (menu->type == MenuType_Help) {
|
||||
[NSApp setHelpMenu:submenu];
|
||||
DEBUG.writefln("[Help] %s", menu->title.c_str());
|
||||
} else if (menu->type == MenuType_Application) {
|
||||
DEBUG.writefln("[Application] %s", menu->title.c_str());
|
||||
} else {
|
||||
DEBUG.writefln("[Submenu] %s", menu->title.c_str());
|
||||
}
|
||||
menu_stack.push(submenu);
|
||||
}
|
||||
void EndSubmenu() {
|
||||
menu_stack.pop();
|
||||
}
|
||||
void AddSeparator() {
|
||||
print_spaces();
|
||||
DEBUG.writeln("[Separator]");
|
||||
[menu_stack.top() addItem:[[NSMenuItem separatorItem] autorelease]];
|
||||
}
|
||||
};
|
||||
bool LooperUI::PublishMenu(Menu *menu, PlatformData*) {
|
||||
PublishMenuCallbacks callbacks;
|
||||
IterateMenu(menu, callbacks);
|
||||
return true;
|
||||
}
|
||||
}
|
213
liblooperui/menus.cpp
Normal file
213
liblooperui/menus.cpp
Normal file
|
@ -0,0 +1,213 @@
|
|||
#include "menus.hpp"
|
||||
#ifdef __APPLE__
|
||||
#define IS_MACOSX true
|
||||
#else
|
||||
#define IS_MACOSX false
|
||||
#endif
|
||||
namespace LooperUI {
|
||||
static std::unordered_map<MenuType, bool> menu_type_data {
|
||||
{MenuType_Default, true},
|
||||
{MenuType_Item, true},
|
||||
{MenuType_Application, IS_MACOSX},
|
||||
{MenuType_Window, IS_MACOSX},
|
||||
{MenuType_Help, true},
|
||||
{MenuType_Services, IS_MACOSX}
|
||||
};
|
||||
bool MenuTypeValidForSystem(MenuType type) {
|
||||
if (menu_type_data.contains(type)) {
|
||||
return menu_type_data[type];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MenuItemBase::MenuItemBase(std::string title)
|
||||
: title(title)
|
||||
, type(MenuType_Default)
|
||||
{
|
||||
this->enabled = true;
|
||||
this->visible = true;
|
||||
}
|
||||
MenuItemBase::MenuItemBase(MenuType type)
|
||||
: MenuItemBase("") {
|
||||
this->type = type;
|
||||
}
|
||||
MenuItem::MenuItem(std::string title, std::string keybindString, std::function<void(MenuItem*, void*)> callback, void *userdata)
|
||||
: MenuItemBase(title)
|
||||
{
|
||||
this->keybindString = keybindString;
|
||||
this->userdata = userdata;
|
||||
this->callback = callback;
|
||||
this->type = MenuType_Item;
|
||||
}
|
||||
Menu::iterator Menu::begin() {
|
||||
return children.begin();
|
||||
}
|
||||
Menu::iterator Menu::end() {
|
||||
return children.end();
|
||||
}
|
||||
Menu::const_iterator Menu::cbegin() const {
|
||||
return children.cbegin();
|
||||
}
|
||||
Menu::const_iterator Menu::cend() const {
|
||||
return children.cend();
|
||||
}
|
||||
Menu::reverse_iterator Menu::rbegin() {
|
||||
return children.rbegin();
|
||||
}
|
||||
Menu::reverse_iterator Menu::rend() {
|
||||
return children.rend();
|
||||
}
|
||||
Menu::const_reverse_iterator Menu::crbegin() const {
|
||||
return children.crbegin();
|
||||
}
|
||||
Menu::const_reverse_iterator Menu::crend() const {
|
||||
return children.crend();
|
||||
}
|
||||
MenuItemBase *Menu::operator[](size_t index) {
|
||||
return children[index];
|
||||
}
|
||||
void Menu::Prepend(MenuItemBase *menu) {
|
||||
children.insert(children.begin(), menu);
|
||||
}
|
||||
void Menu::Insert(MenuItemBase *menu, size_t index) {
|
||||
if (index >= children.size()) {
|
||||
Append(menu);
|
||||
} else {
|
||||
children.insert(children.begin() + index, menu);
|
||||
}
|
||||
}
|
||||
bool MenuItemBase::IsSubmenu() {
|
||||
return false;
|
||||
}
|
||||
bool Menu::IsSubmenu() {
|
||||
return true;
|
||||
}
|
||||
Menu::Menu(std::string title, MenuType type)
|
||||
: MenuItemBase(title)
|
||||
{
|
||||
this->type = type;
|
||||
}
|
||||
bool MenuItemBase::HasParent() {
|
||||
return parent != nullptr;
|
||||
}
|
||||
Menu *MenuItemBase::GetParent() {
|
||||
return parent;
|
||||
}
|
||||
void Menu::Append(MenuItemBase *menu) {
|
||||
if (menu->parent != nullptr) {
|
||||
throw std::exception();
|
||||
}
|
||||
menu->parent = this;
|
||||
children.push_back(menu);
|
||||
}
|
||||
MenuBuilder &MenuBuilder::BeginSubmenu(std::string title, MenuType type, OSOptions allowedOS) {
|
||||
throwIfBuilt();
|
||||
Menu *submenu = new Menu(title);
|
||||
submenu->os_options = allowedOS;
|
||||
submenu->type = type;
|
||||
current_menu->Append(submenu);
|
||||
current_menu = submenu;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MenuBuilder &MenuBuilder::EndSubmenu() {
|
||||
throwIfBuilt();
|
||||
if (current_menu->HasParent()) {
|
||||
current_menu = current_menu->GetParent();
|
||||
} else {
|
||||
throw std::exception();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
MenuBuilder &MenuBuilder::AddEmptySubmenu(std::string title, MenuType type, OSOptions allowedOS) {
|
||||
BeginSubmenu(title, type, allowedOS).EndSubmenu();
|
||||
return *this;
|
||||
}
|
||||
MenuBuilder &MenuBuilder::AddItem(std::string title, std::string keybindString, std::function<void(MenuItem*, void*)> callback, void *userdata, OSOptions allowedOS) {
|
||||
throwIfBuilt();
|
||||
MenuItem *newItem = new MenuItem(title, keybindString, callback, userdata);
|
||||
newItem->os_options = allowedOS;
|
||||
current_menu->Append(newItem);
|
||||
return *this;
|
||||
}
|
||||
MenuBuilder &MenuBuilder::AddSeparator(OSOptions allowedOS) {
|
||||
throwIfBuilt();
|
||||
MenuItemBase *newItem = new MenuItemBase(MenuType_Separator);
|
||||
newItem->os_options = allowedOS;
|
||||
current_menu->Append(newItem);
|
||||
return *this;
|
||||
}
|
||||
MenuBuilder &MenuBuilder::AddItemNoUserdata(std::string title, std::string keybindString, std::function<void(MenuItem*)> callback_no_userdata, OSOptions allowedOS) {
|
||||
AddItem(title, keybindString, [callback_no_userdata](MenuItem *item, void*) {
|
||||
callback_no_userdata(item);
|
||||
}, nullptr, allowedOS);
|
||||
return *this;
|
||||
}
|
||||
MenuBuilder &MenuBuilder::AddItemSimple(std::string title, std::string keybindString, std::function<void()> callback_simple, OSOptions allowedOS) {
|
||||
AddItem(title, keybindString, [callback_simple](MenuItem*, void*) {
|
||||
callback_simple();
|
||||
}, nullptr, allowedOS);
|
||||
return *this;
|
||||
}
|
||||
MenuBuilder::MenuBuilder(std::string rootTitle) {
|
||||
root = new Menu(rootTitle);
|
||||
current_menu = root;
|
||||
built = false;
|
||||
}
|
||||
Menu *MenuBuilder::Build() {
|
||||
throwIfBuilt();
|
||||
built = true;
|
||||
return root;
|
||||
}
|
||||
void MenuBuilder::throwIfBuilt() {
|
||||
if (built) throw std::exception();
|
||||
}
|
||||
static void _IterateMenu(Menu *menu, MenuIteratorCallbacks &callbacks) {
|
||||
|
||||
for (MenuItemBase *item : *menu) {
|
||||
bool allowed = (item->os_options == OSOptions::AnyOS) || (item->os_options ==
|
||||
#ifdef __APPLE__
|
||||
OSOptions::OnlyMac
|
||||
#else
|
||||
OSOptions::ExcludeMac
|
||||
#endif
|
||||
);
|
||||
if (!allowed) continue;
|
||||
if (item->type == MenuType_Separator) {
|
||||
callbacks.AddSeparator();
|
||||
} else if (item->IsSubmenu()) {
|
||||
callbacks.BeginSubmenu((Menu*)item);
|
||||
_IterateMenu((Menu*)item, callbacks);
|
||||
callbacks.EndSubmenu();
|
||||
} else {
|
||||
callbacks.AddNormalItem((MenuItem*)item);
|
||||
}
|
||||
}
|
||||
}
|
||||
void IterateMenu(Menu *menu, MenuIteratorCallbacks &callbacks) {
|
||||
callbacks.Init(menu);
|
||||
_IterateMenu(menu, callbacks);
|
||||
callbacks.Finalize();
|
||||
}
|
||||
MenuItemBase *FindMenu(Menu *menu, std::string title) {
|
||||
for (MenuItemBase *item : *menu) {
|
||||
bool allowed = item->os_options == OSOptions::AnyOS || item->os_options ==
|
||||
#ifdef __APPLE__
|
||||
OSOptions::OnlyMac
|
||||
#else
|
||||
OSOptions::ExcludeMac
|
||||
#endif
|
||||
;
|
||||
if (!allowed) continue;
|
||||
if (item->title == title) {
|
||||
return item;
|
||||
} else if (item->IsSubmenu()) {
|
||||
MenuItemBase *maybe_output = FindMenu((Menu*)item, title);
|
||||
if (maybe_output != nullptr) {
|
||||
return maybe_output;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
106
liblooperui/menus.hpp
Normal file
106
liblooperui/menus.hpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
namespace LooperUI {
|
||||
enum MenuType {
|
||||
MenuType_Default,
|
||||
MenuType_Item,
|
||||
MenuType_Separator,
|
||||
MenuType_Application,
|
||||
MenuType_Window,
|
||||
MenuType_Help,
|
||||
MenuType_Services,
|
||||
};
|
||||
bool MenuTypeValidForSystem(MenuType type);
|
||||
class Menu;
|
||||
enum class OSOptions {
|
||||
AnyOS,
|
||||
OnlyMac,
|
||||
ExcludeMac
|
||||
};
|
||||
class MenuItemBase {
|
||||
friend class Menu;
|
||||
Menu *parent;
|
||||
public:
|
||||
std::string id;
|
||||
MenuType type;
|
||||
OSOptions os_options;
|
||||
virtual bool IsSubmenu();
|
||||
Menu *GetParent();
|
||||
bool HasParent();
|
||||
bool visible;
|
||||
bool enabled;
|
||||
std::string title;
|
||||
MenuItemBase(std::string title);
|
||||
MenuItemBase(MenuType type = MenuType_Separator);
|
||||
};
|
||||
class MenuItem : public MenuItemBase {
|
||||
public:
|
||||
void *userdata;
|
||||
std::string keybindString;
|
||||
std::function<void(MenuItem*, void*)> callback;
|
||||
virtual void RunAction() {
|
||||
callback(this, userdata);
|
||||
}
|
||||
MenuItem(std::string title, std::string keybindString, std::function<void(MenuItem*, void*)> callback, void *userdata = nullptr);
|
||||
};
|
||||
class Menu : public MenuItemBase {
|
||||
using container = std::vector<MenuItemBase*>;
|
||||
using iterator = container::iterator;
|
||||
using const_iterator = container::const_iterator;
|
||||
using reverse_iterator = container::reverse_iterator;
|
||||
using const_reverse_iterator = container::const_reverse_iterator;
|
||||
container children;
|
||||
friend class MenuIterator;
|
||||
public:
|
||||
bool IsSubmenu() override;
|
||||
void Append(MenuItemBase *menu);
|
||||
iterator begin();
|
||||
iterator end();
|
||||
const_iterator cbegin() const;
|
||||
const_iterator cend() const;
|
||||
reverse_iterator rbegin();
|
||||
reverse_iterator rend();
|
||||
const_reverse_iterator crbegin() const;
|
||||
const_reverse_iterator crend() const;
|
||||
MenuItemBase *operator [](size_t index);
|
||||
void Prepend(MenuItemBase *menu);
|
||||
void Insert(MenuItemBase *menu, size_t index);
|
||||
Menu(std::string title, MenuType type = MenuType_Default);
|
||||
};
|
||||
class Menubar {
|
||||
|
||||
};
|
||||
class MenuBuilder {
|
||||
private:
|
||||
Menu *root;
|
||||
Menu *current_menu;
|
||||
bool built;
|
||||
void throwIfBuilt();
|
||||
public:
|
||||
MenuBuilder &BeginSubmenu(std::string title, MenuType type = MenuType_Default, OSOptions allowedOS = OSOptions::AnyOS);
|
||||
MenuBuilder &EndSubmenu();
|
||||
MenuBuilder &AddSeparator(OSOptions allowedOS = OSOptions::AnyOS);
|
||||
MenuBuilder &AddEmptySubmenu(std::string title, MenuType type = MenuType_Default, OSOptions allowedOS = OSOptions::AnyOS);
|
||||
MenuBuilder &AddItem(std::string title, std::string keybindString, std::function<void(MenuItem*, void*)> callback, void *userdata = nullptr, OSOptions allowedOS = OSOptions::AnyOS);
|
||||
MenuBuilder &AddItemNoUserdata(std::string title, std::string keybindString, std::function<void(MenuItem*)> callback_no_userdata, OSOptions allowedOS = OSOptions::AnyOS);
|
||||
MenuBuilder &AddItemSimple(std::string title, std::string keybindString, std::function<void()> callback_simple, OSOptions allowedOS = OSOptions::AnyOS);
|
||||
Menu *Build();
|
||||
MenuBuilder(std::string rootTitle = "<root>");
|
||||
};
|
||||
// TODO: For DBusmenu
|
||||
struct PlatformData;
|
||||
struct MenuIteratorCallbacks {
|
||||
inline virtual void Init(Menu *root) { }
|
||||
inline virtual void Finalize() { }
|
||||
inline virtual void AddNormalItem(MenuItem* item) { }
|
||||
inline virtual void BeginSubmenu(Menu *menu) { }
|
||||
inline virtual void EndSubmenu() { }
|
||||
inline virtual void AddSeparator() { }
|
||||
};
|
||||
MenuItemBase *FindMenu(Menu *menu, std::string title);
|
||||
void IterateMenu(Menu *menu, MenuIteratorCallbacks &callbacks);
|
||||
bool PublishMenu(Menu *menu, PlatformData *platform_data = nullptr);
|
||||
}
|
13
main.cpp
13
main.cpp
|
@ -15,6 +15,9 @@
|
|||
#include <os/AppKit.h>
|
||||
#include <image.h>
|
||||
#endif
|
||||
#ifdef __MACOSX__
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
#include "web_functions.hpp"
|
||||
#include "cats.hpp"
|
||||
#include <json/value.h>
|
||||
|
@ -366,6 +369,16 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
}
|
||||
}
|
||||
#elif defined(__MACOSX__)
|
||||
{
|
||||
uint32_t path_len = 16;
|
||||
char *path = (char*)malloc(path_len);
|
||||
_NSGetExecutablePath(path, &path_len);
|
||||
path = (char*)realloc(path, path_len);
|
||||
_NSGetExecutablePath(path, &path_len);
|
||||
executable_path = strdup(fs::canonical(path).c_str());
|
||||
free((void*)path);
|
||||
}
|
||||
#else
|
||||
executable_path = strdup(fs::canonical("/proc/self/exe").c_str());
|
||||
#endif
|
||||
|
|
20
mkicns.sh
Executable file
20
mkicns.sh
Executable file
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
OLDDIR="$(pwd)"
|
||||
while [ -n "$1" ]; do
|
||||
mkdir -p $2.iconset
|
||||
for size in 16 24 32 48 64 128 256 512 1024; do
|
||||
for scale in 1 2; do
|
||||
export realsize=$((size * scale))
|
||||
export name="$2.iconset/$(basename $2)_$size"
|
||||
if [ "$scale" -gt 1 ]; then
|
||||
export name="$name@${scale}x"
|
||||
fi
|
||||
export name="$name.png"
|
||||
rsvg-convert "$1" -w "$size" -h "$size" -o "$name"
|
||||
done
|
||||
done
|
||||
iconutil -c icns --output "$2.icns" "$2.iconset"
|
||||
rm -r "$2.iconset"
|
||||
shift 2
|
||||
done
|
||||
cd "$OLDDIR"
|
|
@ -231,7 +231,7 @@ void PlaybackInstance::InitLoopFunction() {
|
|||
SDL_AudioSpec desired;
|
||||
desired.format = SDL_SAMPLE_FMT;
|
||||
desired.freq = 48000;
|
||||
desired.samples = 400;
|
||||
desired.samples = 1000;
|
||||
desired.channels = 2;
|
||||
desired.callback = PlaybackInstance::SDLCallback;
|
||||
desired.userdata = this;
|
||||
|
|
Loading…
Add table
Reference in a new issue