if(NOT XTC_BUILD_JUCE_GUI) return() endif() enable_language(CXX) set(XTC_JUCE_ROOT "" CACHE PATH "External JUCE source checkout") if(NOT TARGET juce::juce_gui_extra) if(NOT EXISTS "${XTC_JUCE_ROOT}/modules/juce_gui_extra/juce_gui_extra.h") message(FATAL_ERROR "XTC_BUILD_JUCE_GUI requires XTC_JUCE_ROOT pointing to an external JUCE SDK checkout") endif() set(JUCE_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(JUCE_BUILD_EXTRAS OFF CACHE BOOL "" FORCE) add_subdirectory("${XTC_JUCE_ROOT}" juce-gui-sdk EXCLUDE_FROM_ALL) endif() message(STATUS "Optional JUCE frontend: JUCE is AGPLv3/commercial; review the SDK licence before distributing") set(xtc_juce_sources main_window.cpp graph_widget.cpp sequencer_widget.cpp sequencer_commands.cpp sequencer_track_widget.cpp sequencer_clip_widget.cpp sequencer_velocity_widget.cpp sequencer_ruler_widget.cpp sequencer_instruments.cpp sequencer_waveforms.cpp piano_roll.cpp mixer_widget.cpp plugin_dialog.cpp sample_editor.cpp ui.cpp) list(TRANSFORM xtc_juce_sources PREPEND "${PROJECT_SOURCE_DIR}/src/gui/JUCE/") add_library(xtc-juce-gui STATIC ${xtc_juce_sources}) xtc_warnings(xtc-juce-gui) target_compile_definitions(xtc-juce-gui PUBLIC JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_MODAL_LOOPS_PERMITTED=1 JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1 JUCE_STANDALONE_APPLICATION=1 "$<$:DEBUG=1;_DEBUG=1>" "$<$>:_NDEBUG=1>") target_link_libraries(xtc-juce-gui PUBLIC xtc-core PRIVATE juce::juce_gui_extra) target_include_directories(xtc-juce-gui SYSTEM PUBLIC "${XTC_JUCE_ROOT}/modules") juce_add_gui_app(xtc-juce PRODUCT_NAME "xtc JUCE" COMPANY_NAME xtc BUNDLE_ID org.xtc.workstation) set_target_properties(xtc-juce PROPERTIES OUTPUT_NAME xtc-juce RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") target_sources(xtc-juce PRIVATE src/gui/JUCE/main.cpp) target_link_libraries(xtc-juce PRIVATE xtc-juce-gui) xtc_warnings(xtc-juce) add_custom_target(gui-juce DEPENDS xtc-juce) if(BUILD_TESTING) find_program(XTC_JUCE_XVFB_RUN xvfb-run) # Exercise the same C boundary, editing workflows and fault injection as FLTK. # Adapt toolkit names/initialisation and show the inspector before native focus. file(READ "${PROJECT_SOURCE_DIR}/tests/integration/test_gui.cpp" juce_test) string(REGEX REPLACE "#include ]+>" "#include \"ui.hpp\"" juce_test "${juce_test}") string(REPLACE "../../src/gui/" "${PROJECT_SOURCE_DIR}/src/gui/JUCE/" juce_test "${juce_test}") string(REPLACE "Fl_" "Ui" juce_test "${juce_test}") string(REPLACE "Fl::" "Ui::" juce_test "${juce_test}") string(REPLACE "FL_" "UI_" juce_test "${juce_test}") string(REPLACE "int main(int argc, char **argv)\n{" "int main(int argc, char **argv)\n{\n UiInitialiser initialise;" juce_test "${juce_test}") # JUCE requires a visible component before native keyboard focus is requested. string(REPLACE "if(gain){Ui::focus(&gain->input);" "if(gain){window.show_track_inspector(true);Ui::focus(&gain->input);" juce_test "${juce_test}") # Native TextEditor commits on blur; unwind that additional edit before # the shared test checks undo of its earlier explicit gain edit. string(REPLACE "Ui::focus(nullptr);}" "Ui::focus(nullptr);pump();xtc_track_info blurred{};xtc_project_get_track(seq->project(),0,&blurred);if(blurred.gain==.123f)CHECK(window.undo()==XTC_OK);}" juce_test "${juce_test}") file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test_juce_gui.cpp" CONTENT "${juce_test}") add_executable(test_juce_gui "${CMAKE_CURRENT_BINARY_DIR}/test_juce_gui.cpp") target_include_directories(test_juce_gui PRIVATE "${PROJECT_SOURCE_DIR}/src/gui/JUCE") target_link_libraries(test_juce_gui PRIVATE xtc-juce-gui) xtc_wrap(test_juce_gui xtc_history_reserve) xtc_warnings(test_juce_gui) if(XTC_JUCE_XVFB_RUN) foreach(pair "editing|" "sequencer|SEQUENCER_COMPONENTS" "midi|MIDI_WORKFLOWS" "samples|SAMPLE_EDITOR" "creative|CREATIVE" "next-daw|NEXT_DAW") string(REPLACE "|" ";" pair "${pair}") list(GET pair 0 name) list(GET pair 1 env) add_test(NAME juce-gui-${name} COMMAND ${XTC_JUCE_XVFB_RUN} -a -s "-screen 0 1440x960x24" $) set_tests_properties(juce-gui-${name} PROPERTIES WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" LABELS "gui;juce" RUN_SERIAL TRUE) if(env) set_property(TEST juce-gui-${name} PROPERTY ENVIRONMENT "XTC_TEST_${env}_ONLY=1") endif() endforeach() endif() endif() if(BUILD_TESTING AND JACK_FOUND AND XTC_WITH_JACK) add_executable(test_juce_playback tests/integration/test_juce_playback.cpp) target_link_libraries(test_juce_playback PRIVATE xtc-juce-gui) xtc_warnings(test_juce_playback) if(XTC_ENABLE_LIVE_TESTS AND XTC_JUCE_XVFB_RUN) add_test(NAME juce-playback COMMAND ${XTC_JUCE_XVFB_RUN} -a -s "-screen 0 1440x960x24" $) set_tests_properties(juce-playback PROPERTIES WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" LABELS "live;juce" RUN_SERIAL TRUE) endif() endif() if(BUILD_TESTING AND XTC_JUCE_XVFB_RUN) function(xtc_juce_gui_variant name) add_test(NAME juce-gui-${name} COMMAND ${XTC_JUCE_XVFB_RUN} -a -s "-screen 0 1440x960x24" $) set_tests_properties(juce-gui-${name} PROPERTIES WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" LABELS "gui;juce" RUN_SERIAL TRUE ENVIRONMENT "${ARGN}") endfunction() xtc_juce_gui_variant(modern-editing "XTC_TEST_MODERN_EDITING_ONLY=1") xtc_juce_gui_variant(midi-labels "XTC_TEST_MIDI_LABELS_ONLY=1") set_property(TEST juce-gui-next-daw APPEND PROPERTY ENVIRONMENT XTC_TEST_MODERN_EDITING_ONLY=1) xtc_juce_gui_variant(modern-plugins "XTC_TEST_CLAP=$" "XTC_TEST_VST2=$" "XTC_TEST_VST3=$") xtc_juce_gui_variant(instruments "XTC_TEST_SYNTH_CLAP=$" "XTC_TEST_SYNTH_VST2=$" "XTC_TEST_SYNTH_VST3=$") if(XTC_LADSPA_AVAILABLE) xtc_juce_gui_variant(plugins "XTC_TEST_PLUGIN=$") xtc_juce_gui_variant(track-fx "XTC_TEST_TRACK_FX_ONLY=1" "XTC_TEST_PLUGIN=$" "XTC_TEST_CLAP=$" "XTC_TEST_VST2=$" "XTC_TEST_VST3=$") endif() endif() set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/tests/integration/test_gui.cpp")