Repository: nick-thompson/react-juce Branch: master Commit: 0693801a8646 Files: 1112 Total size: 17.5 MB Directory structure: gitextract_y1uslwgj/ ├── .gitbook.yaml ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── cmake.yml │ ├── lint.yml │ ├── node.yml │ └── publish.yml ├── .gitignore ├── .gitmodules ├── .prettierignore ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── docs/ │ ├── .nojekyll │ ├── README.md │ ├── Resources.md │ ├── SUMMARY.md │ ├── components/ │ │ ├── Button.md │ │ ├── Canvas.md │ │ ├── Events.md │ │ ├── Image.md │ │ ├── ListView.md │ │ ├── ScrollView.md │ │ ├── Slider.md │ │ ├── Styles.md │ │ ├── Text.md │ │ ├── TextInput.md │ │ └── View.md │ ├── guides/ │ │ ├── Debugging_With_Duktape.md │ │ ├── Getting_Started.md │ │ ├── Integrating_Your_Project.md │ │ ├── Running_the_Examples.md │ │ └── Why_Not_React_Native.md │ └── native/ │ ├── Custom_Native_Components.md │ └── Interop.md ├── examples/ │ └── GainPlugin/ │ ├── CMakeLists.txt │ ├── PluginProcessor.cpp │ ├── PluginProcessor.h │ └── jsui/ │ ├── .gitignore │ ├── babel.config.js │ ├── package.json │ ├── src/ │ │ ├── AnimatedFlexBox.js │ │ ├── App.js │ │ ├── Knob.js │ │ ├── Label.js │ │ ├── Meter.js │ │ ├── ParameterSlider.js │ │ ├── ParameterToggleButton.js │ │ ├── ParameterValueContext.js │ │ ├── ParameterValueStore.js │ │ ├── index.js │ │ └── nativeMethods.js │ └── webpack.config.js ├── package.json ├── packages/ │ └── react-juce/ │ ├── .gitignore │ ├── babel.config.js │ ├── package.json │ ├── scripts/ │ │ └── init.js │ ├── src/ │ │ ├── components/ │ │ │ ├── Button.tsx │ │ │ ├── Canvas.ts │ │ │ ├── Image.ts │ │ │ ├── ListView.tsx │ │ │ ├── ScrollView.ts │ │ │ ├── Slider.tsx │ │ │ ├── Text.ts │ │ │ ├── TextInput.tsx │ │ │ └── View.ts │ │ ├── index.tsx │ │ └── lib/ │ │ ├── Backend.ts │ │ ├── EventBridge.ts │ │ ├── MacroProperties/ │ │ │ ├── Colors.ts │ │ │ ├── Transform.ts │ │ │ ├── index.ts │ │ │ ├── types.d.ts │ │ │ └── util.ts │ │ ├── MethodTracer.ts │ │ ├── NativeMethods.ts │ │ ├── Renderer.ts │ │ └── SyntheticEvents.ts │ ├── template/ │ │ ├── .gitignore │ │ ├── babel.config.js │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.js │ │ │ └── index.js │ │ └── webpack.config.js │ ├── tsconfig.json │ └── webpack.config.js └── react_juce/ ├── core/ │ ├── AppHarness.cpp │ ├── AppHarness.h │ ├── CanvasView.cpp │ ├── CanvasView.h │ ├── EcmascriptEngine.cpp │ ├── EcmascriptEngine.h │ ├── EcmascriptEngine_Duktape.cpp │ ├── EcmascriptEngine_Hermes.cpp │ ├── FileWatcher.h │ ├── GenericEditor.cpp │ ├── GenericEditor.h │ ├── ImageView.cpp │ ├── ImageView.h │ ├── RawTextView.h │ ├── ReactApplicationRoot.cpp │ ├── ReactApplicationRoot.h │ ├── ScrollView.cpp │ ├── ScrollView.h │ ├── ScrollViewContentShadowView.h │ ├── ShadowView.cpp │ ├── ShadowView.h │ ├── ShadowView_Yoga.cpp │ ├── TextInputView.cpp │ ├── TextInputView.h │ ├── TextShadowView.cpp │ ├── TextShadowView.h │ ├── TextShadowView_Yoga.cpp │ ├── TextView.h │ ├── Utils.cpp │ ├── Utils.h │ ├── View.cpp │ ├── View.h │ ├── ViewManager.cpp │ ├── ViewManager.h │ └── YogaImplInclude.cpp ├── duktape/ │ ├── AUTHORS.rst │ ├── LICENSE.txt │ ├── Makefile.cmdline │ ├── Makefile.codepage │ ├── Makefile.coffee │ ├── Makefile.dukdebug │ ├── Makefile.eval │ ├── Makefile.eventloop │ ├── Makefile.hello │ ├── Makefile.jsoncbor │ ├── Makefile.jxpretty │ ├── Makefile.sandbox │ ├── Makefile.sharedlibrary │ ├── README.rst │ ├── config/ │ │ ├── README.rst │ │ ├── architectures/ │ │ │ ├── architecture_arm32.h.in │ │ │ ├── architecture_arm64.h.in │ │ │ ├── architecture_emscripten.h.in │ │ │ ├── architecture_generic.h.in │ │ │ ├── architecture_m68k.h.in │ │ │ ├── architecture_mips32.h.in │ │ │ ├── architecture_mips64.h.in │ │ │ ├── architecture_powerpc32.h.in │ │ │ ├── architecture_powerpc64.h.in │ │ │ ├── architecture_sparc32.h.in │ │ │ ├── architecture_sparc64.h.in │ │ │ ├── architecture_superh.h.in │ │ │ ├── architecture_x32.h.in │ │ │ ├── architecture_x64.h.in │ │ │ └── architecture_x86.h.in │ │ ├── architectures.yaml │ │ ├── compilers/ │ │ │ ├── compiler_bcc.h.in │ │ │ ├── compiler_clang.h.in │ │ │ ├── compiler_emscripten.h.in │ │ │ ├── compiler_gcc.h.in │ │ │ ├── compiler_generic.h.in │ │ │ ├── compiler_msvc.h.in │ │ │ ├── compiler_tinyc.h.in │ │ │ └── compiler_vbcc.h.in │ │ ├── compilers.yaml │ │ ├── config-options/ │ │ │ ├── DUK_USE_32BIT_PTRS.yaml │ │ │ ├── DUK_USE_64BIT_OPS.yaml │ │ │ ├── DUK_USE_ALIGN_4.yaml │ │ │ ├── DUK_USE_ALIGN_8.yaml │ │ │ ├── DUK_USE_ALIGN_BY.yaml │ │ │ ├── DUK_USE_ALLOW_UNDEFINED_BEHAVIOR.yaml │ │ │ ├── DUK_USE_ARCH_STRING.yaml │ │ │ ├── DUK_USE_ARRAY_BUILTIN.yaml │ │ │ ├── DUK_USE_ARRAY_FASTPATH.yaml │ │ │ ├── DUK_USE_ARRAY_PROP_FASTPATH.yaml │ │ │ ├── DUK_USE_ASSERTIONS.yaml │ │ │ ├── DUK_USE_ATAN2_WORKAROUNDS.yaml │ │ │ ├── DUK_USE_AUGMENT_ERROR_CREATE.yaml │ │ │ ├── DUK_USE_AUGMENT_ERROR_THROW.yaml │ │ │ ├── DUK_USE_AVOID_PLATFORM_FUNCPTRS.yaml │ │ │ ├── DUK_USE_BASE64_FASTPATH.yaml │ │ │ ├── DUK_USE_BASE64_SUPPORT.yaml │ │ │ ├── DUK_USE_BOOLEAN_BUILTIN.yaml │ │ │ ├── DUK_USE_BRANCH_HINTS.yaml │ │ │ ├── DUK_USE_BROWSER_LIKE.yaml │ │ │ ├── DUK_USE_BUFFEROBJECT_SUPPORT.yaml │ │ │ ├── DUK_USE_BUFLEN16.yaml │ │ │ ├── DUK_USE_BUILTIN_INITJS.yaml │ │ │ ├── DUK_USE_BYTECODE_DUMP_SUPPORT.yaml │ │ │ ├── DUK_USE_BYTEORDER.yaml │ │ │ ├── DUK_USE_BYTEORDER_FORCED.yaml │ │ │ ├── DUK_USE_CACHE_ACTIVATION.yaml │ │ │ ├── DUK_USE_CACHE_CATCHER.yaml │ │ │ ├── DUK_USE_CALLSTACK_LIMIT.yaml │ │ │ ├── DUK_USE_COMMONJS_MODULES.yaml │ │ │ ├── DUK_USE_COMPILER_RECLIMIT.yaml │ │ │ ├── DUK_USE_COMPILER_STRING.yaml │ │ │ ├── DUK_USE_COMPUTED_INFINITY.yaml │ │ │ ├── DUK_USE_COMPUTED_NAN.yaml │ │ │ ├── DUK_USE_COROUTINE_SUPPORT.yaml │ │ │ ├── DUK_USE_CPP_EXCEPTIONS.yaml │ │ │ ├── DUK_USE_DATAPTR16.yaml │ │ │ ├── DUK_USE_DATAPTR_DEC16.yaml │ │ │ ├── DUK_USE_DATAPTR_ENC16.yaml │ │ │ ├── DUK_USE_DATE_BUILTIN.yaml │ │ │ ├── DUK_USE_DATE_FMT_STRFTIME.yaml │ │ │ ├── DUK_USE_DATE_FORMAT_STRING.yaml │ │ │ ├── DUK_USE_DATE_GET_LOCAL_TZOFFSET.yaml │ │ │ ├── DUK_USE_DATE_GET_NOW.yaml │ │ │ ├── DUK_USE_DATE_NOW_GETTIMEOFDAY.yaml │ │ │ ├── DUK_USE_DATE_NOW_TIME.yaml │ │ │ ├── DUK_USE_DATE_NOW_WINDOWS.yaml │ │ │ ├── DUK_USE_DATE_NOW_WINDOWS_SUBMS.yaml │ │ │ ├── DUK_USE_DATE_PARSE_STRING.yaml │ │ │ ├── DUK_USE_DATE_PRS_GETDATE.yaml │ │ │ ├── DUK_USE_DATE_PRS_STRPTIME.yaml │ │ │ ├── DUK_USE_DATE_TZO_GMTIME.yaml │ │ │ ├── DUK_USE_DATE_TZO_GMTIME_R.yaml │ │ │ ├── DUK_USE_DATE_TZO_GMTIME_S.yaml │ │ │ ├── DUK_USE_DATE_TZO_WINDOWS.yaml │ │ │ ├── DUK_USE_DATE_TZO_WINDOWS_NO_DST.yaml │ │ │ ├── DUK_USE_DDDPRINT.yaml │ │ │ ├── DUK_USE_DDPRINT.yaml │ │ │ ├── DUK_USE_DEBUG.yaml │ │ │ ├── DUK_USE_DEBUGGER_DUMPHEAP.yaml │ │ │ ├── DUK_USE_DEBUGGER_FWD_LOGGING.yaml │ │ │ ├── DUK_USE_DEBUGGER_FWD_PRINTALERT.yaml │ │ │ ├── DUK_USE_DEBUGGER_INSPECT.yaml │ │ │ ├── DUK_USE_DEBUGGER_PAUSE_UNCAUGHT.yaml │ │ │ ├── DUK_USE_DEBUGGER_SUPPORT.yaml │ │ │ ├── DUK_USE_DEBUGGER_THROW_NOTIFY.yaml │ │ │ ├── DUK_USE_DEBUGGER_TRANSPORT_TORTURE.yaml │ │ │ ├── DUK_USE_DEBUG_BUFSIZE.yaml │ │ │ ├── DUK_USE_DEBUG_LEVEL.yaml │ │ │ ├── DUK_USE_DEBUG_WRITE.yaml │ │ │ ├── DUK_USE_DEEP_C_STACK.yaml │ │ │ ├── DUK_USE_DOUBLE_BE.yaml │ │ │ ├── DUK_USE_DOUBLE_LE.yaml │ │ │ ├── DUK_USE_DOUBLE_LINKED_HEAP.yaml │ │ │ ├── DUK_USE_DOUBLE_ME.yaml │ │ │ ├── DUK_USE_DPRINT.yaml │ │ │ ├── DUK_USE_DPRINT_COLORS.yaml │ │ │ ├── DUK_USE_DPRINT_RDTSC.yaml │ │ │ ├── DUK_USE_DUKTAPE_BUILTIN.yaml │ │ │ ├── DUK_USE_ENCODING_BUILTINS.yaml │ │ │ ├── DUK_USE_ERRCREATE.yaml │ │ │ ├── DUK_USE_ERRTHROW.yaml │ │ │ ├── DUK_USE_ES6.yaml │ │ │ ├── DUK_USE_ES6_OBJECT_PROTO_PROPERTY.yaml │ │ │ ├── DUK_USE_ES6_OBJECT_SETPROTOTYPEOF.yaml │ │ │ ├── DUK_USE_ES6_PROXY.yaml │ │ │ ├── DUK_USE_ES6_REGEXP_BRACES.yaml │ │ │ ├── DUK_USE_ES6_REGEXP_SYNTAX.yaml │ │ │ ├── DUK_USE_ES6_UNICODE_ESCAPE.yaml │ │ │ ├── DUK_USE_ES7.yaml │ │ │ ├── DUK_USE_ES7_EXP_OPERATOR.yaml │ │ │ ├── DUK_USE_ES8.yaml │ │ │ ├── DUK_USE_ES9.yaml │ │ │ ├── DUK_USE_ESBC_LIMITS.yaml │ │ │ ├── DUK_USE_ESBC_MAX_BYTES.yaml │ │ │ ├── DUK_USE_ESBC_MAX_LINENUMBER.yaml │ │ │ ├── DUK_USE_EXAMPLE.yaml │ │ │ ├── DUK_USE_EXEC_FUN_LOCAL.yaml │ │ │ ├── DUK_USE_EXEC_INDIRECT_BOUND_CHECK.yaml │ │ │ ├── DUK_USE_EXEC_PREFER_SIZE.yaml │ │ │ ├── DUK_USE_EXEC_REGCONST_OPTIMIZE.yaml │ │ │ ├── DUK_USE_EXEC_TIMEOUT_CHECK.yaml │ │ │ ├── DUK_USE_EXPLICIT_NULL_INIT.yaml │ │ │ ├── DUK_USE_EXTSTR_FREE.yaml │ │ │ ├── DUK_USE_EXTSTR_INTERN_CHECK.yaml │ │ │ ├── DUK_USE_FASTINT.yaml │ │ │ ├── DUK_USE_FAST_REFCOUNT_DEFAULT.yaml │ │ │ ├── DUK_USE_FATAL_HANDLER.yaml │ │ │ ├── DUK_USE_FATAL_MAXLEN.yaml │ │ │ ├── DUK_USE_FILE_IO.yaml │ │ │ ├── DUK_USE_FINALIZER_SUPPORT.yaml │ │ │ ├── DUK_USE_FINALIZER_TORTURE.yaml │ │ │ ├── DUK_USE_FLEX_C99.yaml │ │ │ ├── DUK_USE_FLEX_ONESIZE.yaml │ │ │ ├── DUK_USE_FLEX_ZEROSIZE.yaml │ │ │ ├── DUK_USE_FULL_TVAL.yaml │ │ │ ├── DUK_USE_FUNCPTR16.yaml │ │ │ ├── DUK_USE_FUNCPTR_DEC16.yaml │ │ │ ├── DUK_USE_FUNCPTR_ENC16.yaml │ │ │ ├── DUK_USE_FUNCTION_BUILTIN.yaml │ │ │ ├── DUK_USE_FUNC_FILENAME_PROPERTY.yaml │ │ │ ├── DUK_USE_FUNC_NAME_PROPERTY.yaml │ │ │ ├── DUK_USE_GCC_PRAGMAS.yaml │ │ │ ├── DUK_USE_GC_TORTURE.yaml │ │ │ ├── DUK_USE_GET_MONOTONIC_TIME.yaml │ │ │ ├── DUK_USE_GET_MONOTONIC_TIME_CLOCK_GETTIME.yaml │ │ │ ├── DUK_USE_GET_MONOTONIC_TIME_WINDOWS_QPC.yaml │ │ │ ├── DUK_USE_GET_RANDOM_DOUBLE.yaml │ │ │ ├── DUK_USE_GLOBAL_BINDING.yaml │ │ │ ├── DUK_USE_GLOBAL_BUILTIN.yaml │ │ │ ├── DUK_USE_HASHBYTES_UNALIGNED_U32_ACCESS.yaml │ │ │ ├── DUK_USE_HEAPPTR16.yaml │ │ │ ├── DUK_USE_HEAPPTR_DEC16.yaml │ │ │ ├── DUK_USE_HEAPPTR_ENC16.yaml │ │ │ ├── DUK_USE_HEX_FASTPATH.yaml │ │ │ ├── DUK_USE_HEX_SUPPORT.yaml │ │ │ ├── DUK_USE_HOBJECT_ARRAY_ABANDON_LIMIT.yaml │ │ │ ├── DUK_USE_HOBJECT_ARRAY_FAST_RESIZE_LIMIT.yaml │ │ │ ├── DUK_USE_HOBJECT_ARRAY_MINGROW_ADD.yaml │ │ │ ├── DUK_USE_HOBJECT_ARRAY_MINGROW_DIVISOR.yaml │ │ │ ├── DUK_USE_HOBJECT_ENTRY_MINGROW_ADD.yaml │ │ │ ├── DUK_USE_HOBJECT_ENTRY_MINGROW_DIVISOR.yaml │ │ │ ├── DUK_USE_HOBJECT_HASH_PART.yaml │ │ │ ├── DUK_USE_HOBJECT_HASH_PROP_LIMIT.yaml │ │ │ ├── DUK_USE_HOBJECT_LAYOUT_1.yaml │ │ │ ├── DUK_USE_HOBJECT_LAYOUT_2.yaml │ │ │ ├── DUK_USE_HOBJECT_LAYOUT_3.yaml │ │ │ ├── DUK_USE_HSTRING_ARRIDX.yaml │ │ │ ├── DUK_USE_HSTRING_CLEN.yaml │ │ │ ├── DUK_USE_HSTRING_EXTDATA.yaml │ │ │ ├── DUK_USE_HSTRING_LAZY_CLEN.yaml │ │ │ ├── DUK_USE_HTML_COMMENTS.yaml │ │ │ ├── DUK_USE_IDCHAR_FASTPATH.yaml │ │ │ ├── DUK_USE_INJECT_HEAP_ALLOC_ERROR.yaml │ │ │ ├── DUK_USE_INTEGER_BE.yaml │ │ │ ├── DUK_USE_INTEGER_LE.yaml │ │ │ ├── DUK_USE_INTEGER_ME.yaml │ │ │ ├── DUK_USE_INTERRUPT_COUNTER.yaml │ │ │ ├── DUK_USE_INTERRUPT_DEBUG_FIXUP.yaml │ │ │ ├── DUK_USE_JC.yaml │ │ │ ├── DUK_USE_JSON_BUILTIN.yaml │ │ │ ├── DUK_USE_JSON_DECNUMBER_FASTPATH.yaml │ │ │ ├── DUK_USE_JSON_DECSTRING_FASTPATH.yaml │ │ │ ├── DUK_USE_JSON_DEC_RECLIMIT.yaml │ │ │ ├── DUK_USE_JSON_EATWHITE_FASTPATH.yaml │ │ │ ├── DUK_USE_JSON_ENC_RECLIMIT.yaml │ │ │ ├── DUK_USE_JSON_QUOTESTRING_FASTPATH.yaml │ │ │ ├── DUK_USE_JSON_STRINGIFY_FASTPATH.yaml │ │ │ ├── DUK_USE_JSON_SUPPORT.yaml │ │ │ ├── DUK_USE_JX.yaml │ │ │ ├── DUK_USE_LEXER_SLIDING_WINDOW.yaml │ │ │ ├── DUK_USE_LIGHTFUNC_BUILTINS.yaml │ │ │ ├── DUK_USE_LITCACHE_SIZE.yaml │ │ │ ├── DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE.yaml │ │ │ ├── DUK_USE_MARK_AND_SWEEP.yaml │ │ │ ├── DUK_USE_MARK_AND_SWEEP_RECLIMIT.yaml │ │ │ ├── DUK_USE_MATH_BUILTIN.yaml │ │ │ ├── DUK_USE_MATH_FMAX.yaml │ │ │ ├── DUK_USE_MATH_FMIN.yaml │ │ │ ├── DUK_USE_MATH_ROUND.yaml │ │ │ ├── DUK_USE_MS_STRINGTABLE_RESIZE.yaml │ │ │ ├── DUK_USE_NATIVE_CALL_RECLIMIT.yaml │ │ │ ├── DUK_USE_NATIVE_STACK_CHECK.yaml │ │ │ ├── DUK_USE_NONSTD_ARRAY_CONCAT_TRAILER.yaml │ │ │ ├── DUK_USE_NONSTD_ARRAY_MAP_TRAILER.yaml │ │ │ ├── DUK_USE_NONSTD_ARRAY_SPLICE_DELCOUNT.yaml │ │ │ ├── DUK_USE_NONSTD_FUNC_CALLER_PROPERTY.yaml │ │ │ ├── DUK_USE_NONSTD_FUNC_SOURCE_PROPERTY.yaml │ │ │ ├── DUK_USE_NONSTD_FUNC_STMT.yaml │ │ │ ├── DUK_USE_NONSTD_GETTER_KEY_ARGUMENT.yaml │ │ │ ├── DUK_USE_NONSTD_JSON_ESC_U2028_U2029.yaml │ │ │ ├── DUK_USE_NONSTD_REGEXP_DOLLAR_ESCAPE.yaml │ │ │ ├── DUK_USE_NONSTD_SETTER_KEY_ARGUMENT.yaml │ │ │ ├── DUK_USE_NONSTD_STRING_FROMCHARCODE_32BIT.yaml │ │ │ ├── DUK_USE_NO_DOUBLE_ALIASING_SELFTEST.yaml │ │ │ ├── DUK_USE_NUMBER_BUILTIN.yaml │ │ │ ├── DUK_USE_OBJECT_BUILTIN.yaml │ │ │ ├── DUK_USE_OBJSIZES16.yaml │ │ │ ├── DUK_USE_OCTAL_SUPPORT.yaml │ │ │ ├── DUK_USE_OS_STRING.yaml │ │ │ ├── DUK_USE_PACKED_TVAL.yaml │ │ │ ├── DUK_USE_PACKED_TVAL_POSSIBLE.yaml │ │ │ ├── DUK_USE_PACK_CLANG_ATTR.yaml │ │ │ ├── DUK_USE_PACK_DUMMY_MEMBER.yaml │ │ │ ├── DUK_USE_PACK_GCC_ATTR.yaml │ │ │ ├── DUK_USE_PACK_MSVC_PRAGMA.yaml │ │ │ ├── DUK_USE_PANIC_ABORT.yaml │ │ │ ├── DUK_USE_PANIC_EXIT.yaml │ │ │ ├── DUK_USE_PANIC_HANDLER.yaml │ │ │ ├── DUK_USE_PANIC_SEGFAULT.yaml │ │ │ ├── DUK_USE_PARANOID_DATE_COMPUTATION.yaml │ │ │ ├── DUK_USE_PARANOID_ERRORS.yaml │ │ │ ├── DUK_USE_PARANOID_MATH.yaml │ │ │ ├── DUK_USE_PC2LINE.yaml │ │ │ ├── DUK_USE_PERFORMANCE_BUILTIN.yaml │ │ │ ├── DUK_USE_POW_NETBSD_WORKAROUND.yaml │ │ │ ├── DUK_USE_POW_WORKAROUNDS.yaml │ │ │ ├── DUK_USE_PREFER_SIZE.yaml │ │ │ ├── DUK_USE_PROMISE_BUILTIN.yaml │ │ │ ├── DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS.yaml │ │ │ ├── DUK_USE_RDTSC.yaml │ │ │ ├── DUK_USE_REFCOUNT16.yaml │ │ │ ├── DUK_USE_REFCOUNT32.yaml │ │ │ ├── DUK_USE_REFERENCE_COUNTING.yaml │ │ │ ├── DUK_USE_REFLECT_BUILTIN.yaml │ │ │ ├── DUK_USE_REFZERO_FINALIZER_TORTURE.yaml │ │ │ ├── DUK_USE_REGEXP_CANON_BITMAP.yaml │ │ │ ├── DUK_USE_REGEXP_CANON_WORKAROUND.yaml │ │ │ ├── DUK_USE_REGEXP_COMPILER_RECLIMIT.yaml │ │ │ ├── DUK_USE_REGEXP_EXECUTOR_RECLIMIT.yaml │ │ │ ├── DUK_USE_REGEXP_SUPPORT.yaml │ │ │ ├── DUK_USE_REPL_FPCLASSIFY.yaml │ │ │ ├── DUK_USE_REPL_ISFINITE.yaml │ │ │ ├── DUK_USE_REPL_ISINF.yaml │ │ │ ├── DUK_USE_REPL_ISNAN.yaml │ │ │ ├── DUK_USE_REPL_SIGNBIT.yaml │ │ │ ├── DUK_USE_ROM_GLOBAL_CLONE.yaml │ │ │ ├── DUK_USE_ROM_GLOBAL_INHERIT.yaml │ │ │ ├── DUK_USE_ROM_OBJECTS.yaml │ │ │ ├── DUK_USE_ROM_PTRCOMP_FIRST.yaml │ │ │ ├── DUK_USE_ROM_STRINGS.yaml │ │ │ ├── DUK_USE_SECTION_B.yaml │ │ │ ├── DUK_USE_SELF_TESTS.yaml │ │ │ ├── DUK_USE_SETJMP.yaml │ │ │ ├── DUK_USE_SHEBANG_COMMENTS.yaml │ │ │ ├── DUK_USE_SHUFFLE_TORTURE.yaml │ │ │ ├── DUK_USE_SIGSETJMP.yaml │ │ │ ├── DUK_USE_SOURCE_NONBMP.yaml │ │ │ ├── DUK_USE_STRHASH16.yaml │ │ │ ├── DUK_USE_STRHASH_DENSE.yaml │ │ │ ├── DUK_USE_STRHASH_SKIP_SHIFT.yaml │ │ │ ├── DUK_USE_STRICT_DECL.yaml │ │ │ ├── DUK_USE_STRICT_UTF8_SOURCE.yaml │ │ │ ├── DUK_USE_STRING_BUILTIN.yaml │ │ │ ├── DUK_USE_STRLEN16.yaml │ │ │ ├── DUK_USE_STRTAB_CHAIN.yaml │ │ │ ├── DUK_USE_STRTAB_CHAIN_SIZE.yaml │ │ │ ├── DUK_USE_STRTAB_GROW_LIMIT.yaml │ │ │ ├── DUK_USE_STRTAB_MAXSIZE.yaml │ │ │ ├── DUK_USE_STRTAB_MINSIZE.yaml │ │ │ ├── DUK_USE_STRTAB_PROBE.yaml │ │ │ ├── DUK_USE_STRTAB_PTRCOMP.yaml │ │ │ ├── DUK_USE_STRTAB_RESIZE_CHECK_MASK.yaml │ │ │ ├── DUK_USE_STRTAB_SHRINK_LIMIT.yaml │ │ │ ├── DUK_USE_STRTAB_TORTURE.yaml │ │ │ ├── DUK_USE_SYMBOL_BUILTIN.yaml │ │ │ ├── DUK_USE_TAILCALL.yaml │ │ │ ├── DUK_USE_TARGET_INFO.yaml │ │ │ ├── DUK_USE_TRACEBACKS.yaml │ │ │ ├── DUK_USE_TRACEBACK_DEPTH.yaml │ │ │ ├── DUK_USE_UNALIGNED_ACCESSES_POSSIBLE.yaml │ │ │ ├── DUK_USE_UNDERSCORE_SETJMP.yaml │ │ │ ├── DUK_USE_UNION_INITIALIZERS.yaml │ │ │ ├── DUK_USE_USER_DECLARE.yaml │ │ │ ├── DUK_USE_USER_INITJS.yaml │ │ │ ├── DUK_USE_VALSTACK_GROW_SHIFT.yaml │ │ │ ├── DUK_USE_VALSTACK_LIMIT.yaml │ │ │ ├── DUK_USE_VALSTACK_SHRINK_CHECK_SHIFT.yaml │ │ │ ├── DUK_USE_VALSTACK_SHRINK_SLACK_SHIFT.yaml │ │ │ ├── DUK_USE_VALSTACK_UNSAFE.yaml │ │ │ ├── DUK_USE_VARIADIC_MACROS.yaml │ │ │ ├── DUK_USE_VERBOSE_ERRORS.yaml │ │ │ ├── DUK_USE_VERBOSE_EXECUTOR_ERRORS.yaml │ │ │ ├── DUK_USE_VOLUNTARY_GC.yaml │ │ │ └── DUK_USE_ZERO_BUFFER_DATA.yaml │ │ ├── examples/ │ │ │ ├── compliance.yaml │ │ │ ├── debugger_support.yaml │ │ │ ├── disable_bufferobjects.yaml │ │ │ ├── disable_es6.yaml │ │ │ ├── enable_debug_print0.yaml │ │ │ ├── enable_debug_print1.yaml │ │ │ ├── enable_debug_print2.yaml │ │ │ ├── enable_fastint.yaml │ │ │ ├── low_memory.yaml │ │ │ ├── low_memory_strip.yaml │ │ │ ├── performance_sensitive.yaml │ │ │ ├── rom_builtins.yaml │ │ │ ├── security_sensitive.yaml │ │ │ ├── shallow_c_stack.yaml │ │ │ └── timing_sensitive.yaml │ │ ├── feature-options/ │ │ │ ├── DUK_OPT_ASSERTIONS.yaml │ │ │ ├── DUK_OPT_BUFFEROBJECT_SUPPORT.yaml │ │ │ ├── DUK_OPT_BUFLEN16.yaml │ │ │ ├── DUK_OPT_DATAPTR16.yaml │ │ │ ├── DUK_OPT_DATAPTR_DEC16.yaml │ │ │ ├── DUK_OPT_DATAPTR_ENC16.yaml │ │ │ ├── DUK_OPT_DDDPRINT.yaml │ │ │ ├── DUK_OPT_DDPRINT.yaml │ │ │ ├── DUK_OPT_DEBUG.yaml │ │ │ ├── DUK_OPT_DEBUGGER_DUMPHEAP.yaml │ │ │ ├── DUK_OPT_DEBUGGER_FWD_LOGGING.yaml │ │ │ ├── DUK_OPT_DEBUGGER_FWD_PRINTALERT.yaml │ │ │ ├── DUK_OPT_DEBUGGER_SUPPORT.yaml │ │ │ ├── DUK_OPT_DEBUGGER_TRANSPORT_TORTURE.yaml │ │ │ ├── DUK_OPT_DEBUG_BUFSIZE.yaml │ │ │ ├── DUK_OPT_DECLARE.yaml │ │ │ ├── DUK_OPT_DEEP_C_STACK.yaml │ │ │ ├── DUK_OPT_DLL_BUILD.yaml │ │ │ ├── DUK_OPT_DPRINT.yaml │ │ │ ├── DUK_OPT_DPRINT_COLORS.yaml │ │ │ ├── DUK_OPT_DPRINT_RDTSC.yaml │ │ │ ├── DUK_OPT_EXAMPLE.yaml │ │ │ ├── DUK_OPT_EXEC_TIMEOUT_CHECK.yaml │ │ │ ├── DUK_OPT_EXTERNAL_STRINGS.yaml │ │ │ ├── DUK_OPT_EXTSTR_FREE.yaml │ │ │ ├── DUK_OPT_EXTSTR_INTERN_CHECK.yaml │ │ │ ├── DUK_OPT_FASTINT.yaml │ │ │ ├── DUK_OPT_FORCE_ALIGN.yaml │ │ │ ├── DUK_OPT_FORCE_BYTEORDER.yaml │ │ │ ├── DUK_OPT_FUNCPTR16.yaml │ │ │ ├── DUK_OPT_FUNCPTR_DEC16.yaml │ │ │ ├── DUK_OPT_FUNCPTR_ENC16.yaml │ │ │ ├── DUK_OPT_FUNC_NONSTD_CALLER_PROPERTY.yaml │ │ │ ├── DUK_OPT_FUNC_NONSTD_SOURCE_PROPERTY.yaml │ │ │ ├── DUK_OPT_GC_TORTURE.yaml │ │ │ ├── DUK_OPT_HAVE_CUSTOM_H.yaml │ │ │ ├── DUK_OPT_HEAPPTR16.yaml │ │ │ ├── DUK_OPT_HEAPPTR_DEC16.yaml │ │ │ ├── DUK_OPT_HEAPPTR_ENC16.yaml │ │ │ ├── DUK_OPT_INTERRUPT_COUNTER.yaml │ │ │ ├── DUK_OPT_JSON_STRINGIFY_FASTPATH.yaml │ │ │ ├── DUK_OPT_LIGHTFUNC_BUILTINS.yaml │ │ │ ├── DUK_OPT_NONSTD_FUNC_CALLER_PROPERTY.yaml │ │ │ ├── DUK_OPT_NONSTD_FUNC_SOURCE_PROPERTY.yaml │ │ │ ├── DUK_OPT_NO_ARRAY_SPLICE_NONSTD_DELCOUNT.yaml │ │ │ ├── DUK_OPT_NO_AUGMENT_ERRORS.yaml │ │ │ ├── DUK_OPT_NO_BROWSER_LIKE.yaml │ │ │ ├── DUK_OPT_NO_BUFFEROBJECT_SUPPORT.yaml │ │ │ ├── DUK_OPT_NO_BYTECODE_DUMP_SUPPORT.yaml │ │ │ ├── DUK_OPT_NO_COMMONJS_MODULES.yaml │ │ │ ├── DUK_OPT_NO_ES6_OBJECT_PROTO_PROPERTY.yaml │ │ │ ├── DUK_OPT_NO_ES6_OBJECT_SETPROTOTYPEOF.yaml │ │ │ ├── DUK_OPT_NO_ES6_PROXY.yaml │ │ │ ├── DUK_OPT_NO_FILE_IO.yaml │ │ │ ├── DUK_OPT_NO_FUNC_STMT.yaml │ │ │ ├── DUK_OPT_NO_JC.yaml │ │ │ ├── DUK_OPT_NO_JSONC.yaml │ │ │ ├── DUK_OPT_NO_JSONX.yaml │ │ │ ├── DUK_OPT_NO_JX.yaml │ │ │ ├── DUK_OPT_NO_MARK_AND_SWEEP.yaml │ │ │ ├── DUK_OPT_NO_MS_STRINGTABLE_RESIZE.yaml │ │ │ ├── DUK_OPT_NO_NONSTD_ACCESSOR_KEY_ARGUMENT.yaml │ │ │ ├── DUK_OPT_NO_NONSTD_ARRAY_CONCAT_TRAILER.yaml │ │ │ ├── DUK_OPT_NO_NONSTD_ARRAY_MAP_TRAILER.yaml │ │ │ ├── DUK_OPT_NO_NONSTD_ARRAY_SPLICE_DELCOUNT.yaml │ │ │ ├── DUK_OPT_NO_NONSTD_FUNC_STMT.yaml │ │ │ ├── DUK_OPT_NO_NONSTD_JSON_ESC_U2028_U2029.yaml │ │ │ ├── DUK_OPT_NO_NONSTD_STRING_FROMCHARCODE_32BIT.yaml │ │ │ ├── DUK_OPT_NO_OBJECT_ES6_PROTO_PROPERTY.yaml │ │ │ ├── DUK_OPT_NO_OBJECT_ES6_SETPROTOTYPEOF.yaml │ │ │ ├── DUK_OPT_NO_OCTAL_SUPPORT.yaml │ │ │ ├── DUK_OPT_NO_PACKED_TVAL.yaml │ │ │ ├── DUK_OPT_NO_PC2LINE.yaml │ │ │ ├── DUK_OPT_NO_REFERENCE_COUNTING.yaml │ │ │ ├── DUK_OPT_NO_REGEXP_SUPPORT.yaml │ │ │ ├── DUK_OPT_NO_SECTION_B.yaml │ │ │ ├── DUK_OPT_NO_SOURCE_NONBMP.yaml │ │ │ ├── DUK_OPT_NO_STRICT_DECL.yaml │ │ │ ├── DUK_OPT_NO_TRACEBACKS.yaml │ │ │ ├── DUK_OPT_NO_VERBOSE_ERRORS.yaml │ │ │ ├── DUK_OPT_NO_VOLUNTARY_GC.yaml │ │ │ ├── DUK_OPT_NO_ZERO_BUFFER_DATA.yaml │ │ │ ├── DUK_OPT_OBJSIZES16.yaml │ │ │ ├── DUK_OPT_PANIC_HANDLER.yaml │ │ │ ├── DUK_OPT_REFCOUNT16.yaml │ │ │ ├── DUK_OPT_SEGFAULT_ON_PANIC.yaml │ │ │ ├── DUK_OPT_SELF_TESTS.yaml │ │ │ ├── DUK_OPT_SETJMP.yaml │ │ │ ├── DUK_OPT_SHUFFLE_TORTURE.yaml │ │ │ ├── DUK_OPT_SIGSETJMP.yaml │ │ │ ├── DUK_OPT_STRHASH16.yaml │ │ │ ├── DUK_OPT_STRICT_UTF8_SOURCE.yaml │ │ │ ├── DUK_OPT_STRLEN16.yaml │ │ │ ├── DUK_OPT_STRTAB_CHAIN.yaml │ │ │ ├── DUK_OPT_STRTAB_CHAIN_SIZE.yaml │ │ │ ├── DUK_OPT_TARGET_INFO.yaml │ │ │ ├── DUK_OPT_TRACEBACK_DEPTH.yaml │ │ │ ├── DUK_OPT_UNDERSCORE_SETJMP.yaml │ │ │ └── DUK_OPT_USER_INITJS.yaml │ │ ├── header-snippets/ │ │ │ ├── 64bitops.h.in │ │ │ ├── alignment_fillin.h.in │ │ │ ├── architecture_fillins.h.in │ │ │ ├── byteorder_derived.h.in │ │ │ ├── byteorder_fillin.h.in │ │ │ ├── compiler_fillins.h.in │ │ │ ├── cpp_exception_sanity.h.in │ │ │ ├── date_provider.h.in │ │ │ ├── gcc_clang_visibility.h.in │ │ │ ├── inline_workaround.h.in │ │ │ ├── msvc_visibility.h.in │ │ │ ├── object_layout.h.in │ │ │ ├── packed_tval_fillin.h.in │ │ │ ├── platform_conditionalincludes.h.in │ │ │ ├── platform_cppextras.h.in │ │ │ ├── platform_fillins.h.in │ │ │ ├── platform_sharedincludes.h.in │ │ │ ├── reject_fast_math.h.in │ │ │ ├── types1.h.in │ │ │ ├── types2.h.in │ │ │ ├── types_c99.h.in │ │ │ └── types_legacy.h.in │ │ ├── helper-snippets/ │ │ │ ├── DUK_F_AIX.h.in │ │ │ ├── DUK_F_AMIGAOS.h.in │ │ │ ├── DUK_F_ANDROID.h.in │ │ │ ├── DUK_F_APPLE.h.in │ │ │ ├── DUK_F_ARM.h.in │ │ │ ├── DUK_F_BCC.h.in │ │ │ ├── DUK_F_BSD.h.in │ │ │ ├── DUK_F_C99.h.in │ │ │ ├── DUK_F_CLANG.h.in │ │ │ ├── DUK_F_CPP.h.in │ │ │ ├── DUK_F_CPP11.h.in │ │ │ ├── DUK_F_CYGWIN.h.in │ │ │ ├── DUK_F_DURANGO.h.in │ │ │ ├── DUK_F_EMSCRIPTEN.h.in │ │ │ ├── DUK_F_FLASHPLAYER.h.in │ │ │ ├── DUK_F_FREEBSD.h.in │ │ │ ├── DUK_F_GCC.h.in │ │ │ ├── DUK_F_HPUX.h.in │ │ │ ├── DUK_F_LINUX.h.in │ │ │ ├── DUK_F_M68K.h.in │ │ │ ├── DUK_F_MINGW.h.in │ │ │ ├── DUK_F_MINT.h.in │ │ │ ├── DUK_F_MIPS.h.in │ │ │ ├── DUK_F_MSVC.h.in │ │ │ ├── DUK_F_NETBSD.h.in │ │ │ ├── DUK_F_NO_STDINT_H.h.in │ │ │ ├── DUK_F_OPENBSD.h.in │ │ │ ├── DUK_F_ORBIS.h.in │ │ │ ├── DUK_F_POSIX.h.in │ │ │ ├── DUK_F_PPC.h.in │ │ │ ├── DUK_F_QNX.h.in │ │ │ ├── DUK_F_SPARC.h.in │ │ │ ├── DUK_F_SUN.h.in │ │ │ ├── DUK_F_SUPERH.h.in │ │ │ ├── DUK_F_TINSPIRE.h.in │ │ │ ├── DUK_F_TINYC.h.in │ │ │ ├── DUK_F_TOS.h.in │ │ │ ├── DUK_F_UCLIBC.h.in │ │ │ ├── DUK_F_ULL_CONSTS.h.in │ │ │ ├── DUK_F_UNIX.h.in │ │ │ ├── DUK_F_VBCC.h.in │ │ │ ├── DUK_F_WINDOWS.h.in │ │ │ └── DUK_F_X86.h.in │ │ ├── platforms/ │ │ │ ├── platform_aix.h.in │ │ │ ├── platform_amigaos.h.in │ │ │ ├── platform_android.h.in │ │ │ ├── platform_apple.h.in │ │ │ ├── platform_cygwin.h.in │ │ │ ├── platform_durango.h.in │ │ │ ├── platform_emscripten.h.in │ │ │ ├── platform_flashplayer.h.in │ │ │ ├── platform_generic.h.in │ │ │ ├── platform_genericbsd.h.in │ │ │ ├── platform_genericunix.h.in │ │ │ ├── platform_hpux.h.in │ │ │ ├── platform_linux.h.in │ │ │ ├── platform_openbsd.h.in │ │ │ ├── platform_orbis.h.in │ │ │ ├── platform_posix.h.in │ │ │ ├── platform_qnx.h.in │ │ │ ├── platform_solaris.h.in │ │ │ ├── platform_tinspire.h.in │ │ │ ├── platform_tos.h.in │ │ │ └── platform_windows.h.in │ │ ├── platforms.yaml │ │ └── tags.yaml │ ├── debugger/ │ │ ├── Makefile │ │ ├── README.rst │ │ ├── duk_classnames.yaml │ │ ├── duk_debug.js │ │ ├── duk_debug_meta.json │ │ ├── duk_debug_proxy.js │ │ ├── duk_debugcommands.yaml │ │ ├── duk_debugerrors.yaml │ │ ├── duk_opcodes.yaml │ │ ├── package.json │ │ └── static/ │ │ ├── index.html │ │ ├── style.css │ │ └── webui.js │ ├── duk_dist_meta.json │ ├── examples/ │ │ ├── README.rst │ │ ├── alloc-hybrid/ │ │ │ ├── README.rst │ │ │ ├── duk_alloc_hybrid.c │ │ │ └── duk_alloc_hybrid.h │ │ ├── alloc-logging/ │ │ │ ├── README.rst │ │ │ ├── duk_alloc_logging.c │ │ │ ├── duk_alloc_logging.h │ │ │ └── log2gnuplot.py │ │ ├── alloc-torture/ │ │ │ ├── README.rst │ │ │ ├── duk_alloc_torture.c │ │ │ └── duk_alloc_torture.h │ │ ├── cmdline/ │ │ │ ├── README.rst │ │ │ ├── duk_cmdline.c │ │ │ ├── duk_cmdline.h │ │ │ └── duk_cmdline_lowmem.c │ │ ├── codepage-conv/ │ │ │ ├── README.rst │ │ │ ├── duk_codepage_conv.c │ │ │ ├── duk_codepage_conv.h │ │ │ └── test.c │ │ ├── coffee/ │ │ │ ├── README.rst │ │ │ ├── globals.coffee │ │ │ ├── hello.coffee │ │ │ └── mandel.coffee │ │ ├── cpp-exceptions/ │ │ │ ├── README.rst │ │ │ └── cpp_exceptions.cpp │ │ ├── debug-trans-dvalue/ │ │ │ ├── Makefile │ │ │ ├── README.rst │ │ │ ├── duk_trans_dvalue.c │ │ │ ├── duk_trans_dvalue.h │ │ │ └── test.c │ │ ├── debug-trans-socket/ │ │ │ ├── README.rst │ │ │ ├── duk_trans_socket.h │ │ │ ├── duk_trans_socket_unix.c │ │ │ └── duk_trans_socket_windows.c │ │ ├── dummy-date-provider/ │ │ │ ├── README.rst │ │ │ └── dummy_date_provider.c │ │ ├── eval/ │ │ │ ├── README.rst │ │ │ └── eval.c │ │ ├── eventloop/ │ │ │ ├── README.rst │ │ │ ├── basic-test.js │ │ │ ├── c_eventloop.c │ │ │ ├── c_eventloop.h │ │ │ ├── c_eventloop.js │ │ │ ├── client-socket-test.js │ │ │ ├── ecma_eventloop.js │ │ │ ├── fileio.c │ │ │ ├── main.c │ │ │ ├── poll.c │ │ │ ├── server-socket-test.js │ │ │ ├── socket.c │ │ │ └── timer-test.js │ │ ├── guide/ │ │ │ ├── README.rst │ │ │ ├── fib.js │ │ │ ├── prime.js │ │ │ ├── primecheck.c │ │ │ ├── process.js │ │ │ ├── processlines.c │ │ │ └── uppercase.c │ │ ├── hello/ │ │ │ ├── README.rst │ │ │ └── hello.c │ │ ├── jxpretty/ │ │ │ ├── README.rst │ │ │ └── jxpretty.c │ │ └── sandbox/ │ │ ├── README.rst │ │ └── sandbox.c │ ├── extras/ │ │ ├── README.rst │ │ ├── alloc-pool/ │ │ │ ├── Makefile │ │ │ ├── README.rst │ │ │ ├── duk_alloc_pool.c │ │ │ ├── duk_alloc_pool.h │ │ │ ├── ptrcomp.yaml │ │ │ ├── ptrcomp_fixup.h │ │ │ └── test.c │ │ ├── cbor/ │ │ │ ├── Makefile │ │ │ ├── README.rst │ │ │ ├── cbordecode.py │ │ │ ├── duk_cbor.c │ │ │ ├── duk_cbor.h │ │ │ ├── jsoncbor.c │ │ │ └── run_testvectors.js │ │ ├── console/ │ │ │ ├── Makefile │ │ │ ├── README.rst │ │ │ ├── duk_console.c │ │ │ ├── duk_console.h │ │ │ └── test.c │ │ ├── duk-v1-compat/ │ │ │ ├── Makefile │ │ │ ├── README.rst │ │ │ ├── duk_v1_compat.c │ │ │ ├── duk_v1_compat.h │ │ │ ├── test.c │ │ │ ├── test_compile1.js │ │ │ ├── test_compile2.js │ │ │ ├── test_eval1.js │ │ │ └── test_eval2.js │ │ ├── logging/ │ │ │ ├── Makefile │ │ │ ├── README.rst │ │ │ ├── duk_logging.c │ │ │ ├── duk_logging.h │ │ │ └── test.c │ │ ├── minimal-printf/ │ │ │ ├── Makefile │ │ │ ├── README.rst │ │ │ ├── duk_minimal_printf.c │ │ │ ├── duk_minimal_printf.h │ │ │ └── test.c │ │ ├── module-duktape/ │ │ │ ├── Makefile │ │ │ ├── README.rst │ │ │ ├── duk_module_duktape.c │ │ │ ├── duk_module_duktape.h │ │ │ └── test.c │ │ ├── module-node/ │ │ │ ├── Makefile │ │ │ ├── README.rst │ │ │ ├── duk_module_node.c │ │ │ ├── duk_module_node.h │ │ │ └── test.c │ │ └── print-alert/ │ │ ├── Makefile │ │ ├── README.rst │ │ ├── duk_print_alert.c │ │ ├── duk_print_alert.h │ │ └── test.c │ ├── licenses/ │ │ ├── commonjs.txt │ │ ├── lua.txt │ │ ├── murmurhash2.txt │ │ ├── splitmix64.txt │ │ └── xoroshiro128plus.txt │ ├── mandel.js │ ├── polyfills/ │ │ ├── console-minimal.js │ │ ├── duktape-buffer.js │ │ ├── duktape-error-setter-nonwritable.js │ │ ├── duktape-error-setter-writable.js │ │ ├── duktape-isfastint.js │ │ ├── global.js │ │ ├── object-assign.js │ │ ├── object-prototype-definegetter.js │ │ ├── object-prototype-definesetter.js │ │ ├── performance-now.js │ │ └── promise.js │ ├── src/ │ │ ├── duk_config.h │ │ ├── duk_source_meta.json │ │ ├── duktape.c │ │ └── duktape.h │ ├── src-input/ │ │ ├── SpecialCasing-8bit.txt │ │ ├── SpecialCasing.txt │ │ ├── UnicodeData-8bit.txt │ │ ├── UnicodeData.txt │ │ ├── builtins.yaml │ │ ├── duk_alloc_default.c │ │ ├── duk_api_buffer.c │ │ ├── duk_api_bytecode.c │ │ ├── duk_api_call.c │ │ ├── duk_api_codec.c │ │ ├── duk_api_compile.c │ │ ├── duk_api_debug.c │ │ ├── duk_api_heap.c │ │ ├── duk_api_inspect.c │ │ ├── duk_api_internal.h │ │ ├── duk_api_memory.c │ │ ├── duk_api_object.c │ │ ├── duk_api_random.c │ │ ├── duk_api_stack.c │ │ ├── duk_api_string.c │ │ ├── duk_api_time.c │ │ ├── duk_bi_array.c │ │ ├── duk_bi_boolean.c │ │ ├── duk_bi_buffer.c │ │ ├── duk_bi_date.c │ │ ├── duk_bi_date_unix.c │ │ ├── duk_bi_date_windows.c │ │ ├── duk_bi_duktape.c │ │ ├── duk_bi_encoding.c │ │ ├── duk_bi_error.c │ │ ├── duk_bi_function.c │ │ ├── duk_bi_global.c │ │ ├── duk_bi_json.c │ │ ├── duk_bi_math.c │ │ ├── duk_bi_number.c │ │ ├── duk_bi_object.c │ │ ├── duk_bi_performance.c │ │ ├── duk_bi_pointer.c │ │ ├── duk_bi_promise.c │ │ ├── duk_bi_protos.h │ │ ├── duk_bi_proxy.c │ │ ├── duk_bi_reflect.c │ │ ├── duk_bi_regexp.c │ │ ├── duk_bi_string.c │ │ ├── duk_bi_symbol.c │ │ ├── duk_bi_thread.c │ │ ├── duk_bi_thrower.c │ │ ├── duk_dblunion.h │ │ ├── duk_debug.h │ │ ├── duk_debug_fixedbuffer.c │ │ ├── duk_debug_macros.c │ │ ├── duk_debug_vsnprintf.c │ │ ├── duk_debugger.c │ │ ├── duk_debugger.h │ │ ├── duk_error.h │ │ ├── duk_error_augment.c │ │ ├── duk_error_longjmp.c │ │ ├── duk_error_macros.c │ │ ├── duk_error_misc.c │ │ ├── duk_error_throw.c │ │ ├── duk_exception.h │ │ ├── duk_forwdecl.h │ │ ├── duk_harray.h │ │ ├── duk_hboundfunc.h │ │ ├── duk_hbuffer.h │ │ ├── duk_hbuffer_alloc.c │ │ ├── duk_hbuffer_assert.c │ │ ├── duk_hbuffer_ops.c │ │ ├── duk_hbufobj.h │ │ ├── duk_hbufobj_misc.c │ │ ├── duk_hcompfunc.h │ │ ├── duk_heap.h │ │ ├── duk_heap_alloc.c │ │ ├── duk_heap_finalize.c │ │ ├── duk_heap_hashstring.c │ │ ├── duk_heap_markandsweep.c │ │ ├── duk_heap_memory.c │ │ ├── duk_heap_misc.c │ │ ├── duk_heap_refcount.c │ │ ├── duk_heap_stringcache.c │ │ ├── duk_heap_stringtable.c │ │ ├── duk_heaphdr.h │ │ ├── duk_heaphdr_assert.c │ │ ├── duk_henv.h │ │ ├── duk_hnatfunc.h │ │ ├── duk_hobject.h │ │ ├── duk_hobject_alloc.c │ │ ├── duk_hobject_assert.c │ │ ├── duk_hobject_class.c │ │ ├── duk_hobject_enum.c │ │ ├── duk_hobject_misc.c │ │ ├── duk_hobject_pc2line.c │ │ ├── duk_hobject_props.c │ │ ├── duk_hproxy.h │ │ ├── duk_hstring.h │ │ ├── duk_hstring_assert.c │ │ ├── duk_hstring_misc.c │ │ ├── duk_hthread.h │ │ ├── duk_hthread_alloc.c │ │ ├── duk_hthread_builtins.c │ │ ├── duk_hthread_misc.c │ │ ├── duk_hthread_stacks.c │ │ ├── duk_internal.h │ │ ├── duk_jmpbuf.h │ │ ├── duk_js.h │ │ ├── duk_js_arith.c │ │ ├── duk_js_bytecode.h │ │ ├── duk_js_call.c │ │ ├── duk_js_compiler.c │ │ ├── duk_js_compiler.h │ │ ├── duk_js_executor.c │ │ ├── duk_js_ops.c │ │ ├── duk_js_var.c │ │ ├── duk_json.h │ │ ├── duk_lexer.c │ │ ├── duk_lexer.h │ │ ├── duk_numconv.c │ │ ├── duk_numconv.h │ │ ├── duk_refcount.h │ │ ├── duk_regexp.h │ │ ├── duk_regexp_compiler.c │ │ ├── duk_regexp_executor.c │ │ ├── duk_replacements.c │ │ ├── duk_replacements.h │ │ ├── duk_selftest.c │ │ ├── duk_selftest.h │ │ ├── duk_strings.h │ │ ├── duk_tval.c │ │ ├── duk_tval.h │ │ ├── duk_unicode.h │ │ ├── duk_unicode_support.c │ │ ├── duk_unicode_tables.c │ │ ├── duk_util.h │ │ ├── duk_util_bitdecoder.c │ │ ├── duk_util_bitencoder.c │ │ ├── duk_util_bufwriter.c │ │ ├── duk_util_cast.c │ │ ├── duk_util_double.c │ │ ├── duk_util_hashbytes.c │ │ ├── duk_util_memory.c │ │ ├── duk_util_misc.c │ │ ├── duk_util_tinyrandom.c │ │ ├── duktape.h.in │ │ └── strings.yaml │ ├── src-noline/ │ │ ├── duk_config.h │ │ ├── duk_source_meta.json │ │ ├── duktape.c │ │ └── duktape.h │ ├── src-separate/ │ │ ├── duk_alloc_default.c │ │ ├── duk_api_buffer.c │ │ ├── duk_api_bytecode.c │ │ ├── duk_api_call.c │ │ ├── duk_api_codec.c │ │ ├── duk_api_compile.c │ │ ├── duk_api_debug.c │ │ ├── duk_api_heap.c │ │ ├── duk_api_inspect.c │ │ ├── duk_api_internal.h │ │ ├── duk_api_memory.c │ │ ├── duk_api_object.c │ │ ├── duk_api_random.c │ │ ├── duk_api_stack.c │ │ ├── duk_api_string.c │ │ ├── duk_api_time.c │ │ ├── duk_bi_array.c │ │ ├── duk_bi_boolean.c │ │ ├── duk_bi_buffer.c │ │ ├── duk_bi_date.c │ │ ├── duk_bi_date_unix.c │ │ ├── duk_bi_date_windows.c │ │ ├── duk_bi_duktape.c │ │ ├── duk_bi_encoding.c │ │ ├── duk_bi_error.c │ │ ├── duk_bi_function.c │ │ ├── duk_bi_global.c │ │ ├── duk_bi_json.c │ │ ├── duk_bi_math.c │ │ ├── duk_bi_number.c │ │ ├── duk_bi_object.c │ │ ├── duk_bi_performance.c │ │ ├── duk_bi_pointer.c │ │ ├── duk_bi_promise.c │ │ ├── duk_bi_protos.h │ │ ├── duk_bi_proxy.c │ │ ├── duk_bi_reflect.c │ │ ├── duk_bi_regexp.c │ │ ├── duk_bi_string.c │ │ ├── duk_bi_symbol.c │ │ ├── duk_bi_thread.c │ │ ├── duk_bi_thrower.c │ │ ├── duk_builtins.c │ │ ├── duk_builtins.h │ │ ├── duk_config.h │ │ ├── duk_dblunion.h │ │ ├── duk_debug.h │ │ ├── duk_debug_fixedbuffer.c │ │ ├── duk_debug_macros.c │ │ ├── duk_debug_vsnprintf.c │ │ ├── duk_debugger.c │ │ ├── duk_debugger.h │ │ ├── duk_error.h │ │ ├── duk_error_augment.c │ │ ├── duk_error_longjmp.c │ │ ├── duk_error_macros.c │ │ ├── duk_error_misc.c │ │ ├── duk_error_throw.c │ │ ├── duk_exception.h │ │ ├── duk_forwdecl.h │ │ ├── duk_harray.h │ │ ├── duk_hboundfunc.h │ │ ├── duk_hbuffer.h │ │ ├── duk_hbuffer_alloc.c │ │ ├── duk_hbuffer_assert.c │ │ ├── duk_hbuffer_ops.c │ │ ├── duk_hbufobj.h │ │ ├── duk_hbufobj_misc.c │ │ ├── duk_hcompfunc.h │ │ ├── duk_heap.h │ │ ├── duk_heap_alloc.c │ │ ├── duk_heap_finalize.c │ │ ├── duk_heap_hashstring.c │ │ ├── duk_heap_markandsweep.c │ │ ├── duk_heap_memory.c │ │ ├── duk_heap_misc.c │ │ ├── duk_heap_refcount.c │ │ ├── duk_heap_stringcache.c │ │ ├── duk_heap_stringtable.c │ │ ├── duk_heaphdr.h │ │ ├── duk_heaphdr_assert.c │ │ ├── duk_henv.h │ │ ├── duk_hnatfunc.h │ │ ├── duk_hobject.h │ │ ├── duk_hobject_alloc.c │ │ ├── duk_hobject_assert.c │ │ ├── duk_hobject_class.c │ │ ├── duk_hobject_enum.c │ │ ├── duk_hobject_misc.c │ │ ├── duk_hobject_pc2line.c │ │ ├── duk_hobject_props.c │ │ ├── duk_hproxy.h │ │ ├── duk_hstring.h │ │ ├── duk_hstring_assert.c │ │ ├── duk_hstring_misc.c │ │ ├── duk_hthread.h │ │ ├── duk_hthread_alloc.c │ │ ├── duk_hthread_builtins.c │ │ ├── duk_hthread_misc.c │ │ ├── duk_hthread_stacks.c │ │ ├── duk_internal.h │ │ ├── duk_jmpbuf.h │ │ ├── duk_js.h │ │ ├── duk_js_arith.c │ │ ├── duk_js_bytecode.h │ │ ├── duk_js_call.c │ │ ├── duk_js_compiler.c │ │ ├── duk_js_compiler.h │ │ ├── duk_js_executor.c │ │ ├── duk_js_ops.c │ │ ├── duk_js_var.c │ │ ├── duk_json.h │ │ ├── duk_lexer.c │ │ ├── duk_lexer.h │ │ ├── duk_numconv.c │ │ ├── duk_numconv.h │ │ ├── duk_refcount.h │ │ ├── duk_regexp.h │ │ ├── duk_regexp_compiler.c │ │ ├── duk_regexp_executor.c │ │ ├── duk_replacements.c │ │ ├── duk_replacements.h │ │ ├── duk_selftest.c │ │ ├── duk_selftest.h │ │ ├── duk_source_meta.json │ │ ├── duk_strings.h │ │ ├── duk_tval.c │ │ ├── duk_tval.h │ │ ├── duk_unicode.h │ │ ├── duk_unicode_support.c │ │ ├── duk_unicode_tables.c │ │ ├── duk_util.h │ │ ├── duk_util_bitdecoder.c │ │ ├── duk_util_bitencoder.c │ │ ├── duk_util_bufwriter.c │ │ ├── duk_util_cast.c │ │ ├── duk_util_double.c │ │ ├── duk_util_hashbytes.c │ │ ├── duk_util_memory.c │ │ ├── duk_util_misc.c │ │ ├── duk_util_tinyrandom.c │ │ └── duktape.h │ └── tools/ │ ├── combine_src.py │ ├── configure.py │ ├── create_spdx_license.py │ ├── duk_meta_to_strarray.py │ ├── dukutil.py │ ├── dump_bytecode.py │ ├── extract_caseconv.py │ ├── extract_chars.py │ ├── extract_unique_options.py │ ├── genbuiltins.py │ ├── genconfig.py │ ├── json2yaml.py │ ├── merge_debug_meta.py │ ├── prepare_unicode_data.py │ ├── resolve_combined_lineno.py │ ├── scan_strings.py │ ├── scan_used_stridx_bidx.py │ └── yaml2json.py ├── react_juce.cpp └── react_juce.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitbook.yaml ================================================ root: ./docs/ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: nick-thompson patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/workflows/cmake.yml ================================================ name: CMake on: [push, pull_request] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v2 with: submodules: true - name: Fetch juce dependencies if: matrix.os == 'ubuntu-latest' run: sudo apt-get update && sudo apt-get install libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxext-dev libfreetype6-dev libasound2-dev - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory # We'll use this as our working directory for all subsequent commands run: cmake -E make_directory ${{runner.workspace}}/build - name: Configure CMake # Use a bash shell so we can use the same syntax for environment variable # access regardless of the host operating system shell: bash working-directory: ${{runner.workspace}}/build # Note the current convention is to use the -S and -B options here to specify source # and build directories, but this is only available with CMake 3.13 and higher. # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE - name: Build working-directory: ${{runner.workspace}}/build shell: bash # Execute the build. You can specify a specific target with "--target " run: cmake --build . --config $BUILD_TYPE ================================================ FILE: .github/workflows/lint.yml ================================================ name: Lint on: [push, pull_request] jobs: run-linters: name: Run linters runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: bahmutov/npm-install@v1 - name: Lint run: npm run lint ================================================ FILE: .github/workflows/node.yml ================================================ name: Node.js on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v1 with: node-version: "12.x" registry-url: "https://npm.pkg.github.com" - name: Install dependencies working-directory: ${{runner.workspace}}/react-juce/packages/react-juce run: npm ci - name: Build react-juce working-directory: ${{runner.workspace}}/react-juce/packages/react-juce run: npm run build ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish Packages on: release: types: [created] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: "12.x" registry-url: "https://registry.npmjs.org" - name: Install dependencies working-directory: ${{runner.workspace}}/react-juce/packages/react-juce run: npm ci - name: Build react-juce working-directory: ${{runner.workspace}}/react-juce/packages/react-juce run: npm run build - name: Publish working-directory: ${{runner.workspace}}/react-juce/packages/react-juce run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} ================================================ FILE: .gitignore ================================================ # Compiled object files *.slo *.lo *.o *.obj *.tlog *.log .vscode/ .cache/ build examples/TestRunner/Builds examples/**/build # For VSCode users *.code-workspace # For JetBrains users .idea # For vim users *.swp ._* *.mode1v3 *.pbxuser *.perspectivev3 *.user *.ncb *.suo *.ilk *.pch *.pdb *.dep *.idb *.manifest *.manifest.res *.o *.d *.sdf *.opensdf *.VC.db *.VC.opendb xcuserdata xcshareddata *.xccheckout *.xcscmblueprint contents.xcworkspacedata .DS_Store .svn .deps .dirstamp profile node_modules **/MacOSX/build **/iOS/build **/IDEWorkspaceChecks.plist **/Linux/build **/LinuxMakefile/build **/VisualStudio2005/Debug **/VisualStudio2005/Release **/VisualStudio2008/Debug **/VisualStudio2008/Release **/VisualStudio2010/Debug **/VisualStudio2010/Release **/VisualStudio2012/Debug **/VisualStudio2012/Release **/VisualStudio2013/Win32 **/VisualStudio2013/x64 **/VisualStudio2015/Win32 **/VisualStudio2015/x64 **/VisualStudio2017/Win32 **/VisualStudio2017/x64 **/Builds/x64 **/.vs **/CodeBlocks/bin **/CodeBlocks/obj **/CodeBlocks/*.depend **/CodeBlocks/*.layout **/Builds/Android/.gradle **/Builds/Android/.idea **/Builds/Android/build **/Builds/Android/**/*.iml **/Builds/Android/local.properties **/Builds/Android/app/build **/Builds/Android/app/.externalNativeBuild **/Builds/Android/lib/build **/Builds/Android/lib/.externalNativeBuild **/Builds/CLion/cmake-build-* **/Builds/CLion/.idea **/doxygen/doc **/doxygen/build extras/Projucer/JUCECompileEngine.dylib **/AAX_SDK_2p3p1/Libs/*.build **/AAX_SDK_2p3p1/Libs/AAXLibrary/WinBuild/**/int **/AAX_SDK_2p3p1/Libs/Debug **/AAX_SDK_2p3p1/Libs/Release **/AAX_SDK_2p3p1/Documentation ================================================ FILE: .gitmodules ================================================ [submodule "juce"] path = ext/juce url = ../../juce-framework/JUCE.git [submodule "yoga"] path = react_juce/yoga url = ../../facebook/yoga.git [submodule "react_juce/hermes"] path = react_juce/hermes url = ../../facebook/hermes.git ================================================ FILE: .prettierignore ================================================ react_juce build dist ext juce ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(reactjuce VERSION 0.1.0) # Change this option to ON if you want to build the AudioPluginHost for example # (practical to debug the plugin processor directly from your IDE) option(JUCE_BUILD_EXTRAS "Build JUCE Extras" OFF) # Change this option to set the JS Interpreter/Engine you wish to run React-JUCE against. set(REACTJUCE_JS_LIBRARY DUKTAPE CACHE STRING "The JS Engine to use: either HERMES or DUKTAPE") add_subdirectory(ext/juce) # Adding any custom modules you might have: juce_add_module(react_juce) # Setup the JS engine/interpreter to use. if (REACTJUCE_JS_LIBRARY STREQUAL "HERMES") add_subdirectory(react_juce/hermes) target_compile_definitions( react_juce INTERFACE REACTJUCE_USE_HERMES=1 ) #TODO: We should be able to remove this include bloc once the following PR is merged # over at hermes upstream: https://github.com/facebook/hermes/pull/454 target_include_directories( react_juce INTERFACE react_juce/hermes/API/ react_juce/hermes/public/ ) target_link_libraries( react_juce INTERFACE hermesapi ) elseif (REACTJUCE_JS_LIBRARY STREQUAL "QUICKJS") #TODO: Add QuickJS cmake settings/includes here. elseif (REACTJUCE_JS_LIBRARY STREQUAL "DUKTAPE") target_compile_definitions( react_juce INTERFACE REACTJUCE_USE_DUKTAPE=1 ) endif() # If you want to create new projects, you can init them in the examples folder # and add them here with the add_subdirectory command add_subdirectory(examples/GainPlugin) ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing We'd love to see you get involved with React-JUCE! Here are our current working guidelines, and we'll be updating and iterating on these as we go. - Have a question? Please open a Discussion topic, not an issue - Document every feature request, bugfix, or change with an issue - Issues that have been assigned to someone should be considered "in progress" - Issues with no assignee are free to claim. Please assign yourself if you want to tackle an issue to prevent duplicate efforts - If you don't see an issue for some work you're planning, open one so that we can mark an assignee and further avoid duplication - Priority will be established by way of labels - Timelines will be established by way of milestones # Style Guide For javascript, typescript, markdown and yaml we enforce default [prettier](https://prettier.io/) formatting via a pipeline check. You can set up your IDE to format automatically, instantiate our formatting pre-commit hook by running `npm i && npx husky install` in the top-level directory, or ad-hoc by running `npm run beautify`. On the C++ side, we don't yet have a coding style guideline, so please try to adhere to the style you see in the codebase. We will aim to get some tooling involved soon to ameliorate that situation! ## Thank you :pray: :heart: :rocket: ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) [2019] [Nicholas Thompson] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # React-JUCE > Write cross-platform native apps with React.js and JUCE React-JUCE (formerly named Blueprint) is a hybrid JavaScript/C++ framework that enables a [React.js](https://reactjs.org/) frontend for a [JUCE](http://juce.com/) application or plugin. It provides an embedded, ES5 JavaScript engine via [Duktape](http://duktape.org/), native hooks for rendering the React component tree to `juce::Component` instances, and a flexbox layout engine via [Yoga](https://yogalayout.com/). For more information, see the introductory blog post here: [Blueprint: A JUCE Rendering Backend for React.js](https://nickwritesablog.com/blueprint-a-juce-rendering-backend-for-react-js) ## Status **Approaching Beta**. We hope to announce a beta release in the coming weeks, after which we will aim our focus at stability and completeness on the path to a 1.0 release. **Anticipated Breaking Changes** - We'll be renaming Blueprint to react-juce before beta (#34) - ~~Updating the examples and `npm init` template to point to npm instead of the local package~~ - ~~`ReactApplicationRoot::evaluate` and `ReactApplicationRoot::evaluateFile` (#115)~~ - ~~Refactoring the hot reloader and decoupling the EcmascriptEngine from ReactApplicationRoot (#65)~~ ## Resources - Documentation: [Docs & Getting Started](https://docs.react-juce.dev) - Discussions: [GitHub Discussions](https://github.com/nick-thompson/react-juce/discussions) - Community: [The Audio Programmer Discord Server](https://discord.gg/3H4wwVf49v) - Join the `#blueprint` channel and say hi! ## Maintainers - [@nick-thompson](https://github.com/nick-thompson) - [@joshmarler](https://github.com/JoshMarler) ## Examples React-JUCE is a young project, but already it provides the framework on which the entire user interface for [Creative Intent's Remnant](https://www.creativeintent.co/product/remnant) plugin is built. ![Creative Intent Remnant: Screenshot](https://github.com/nick-thompson/react-juce/blob/master/RemnantScreenShot.jpg) Besides that, you can check out the example code in the `examples/` directory. See the "Documentation" section below for building and running the demo plugin. If you have a project written with React-JUCE that you want to share, get in touch! I would love to showcase your work. ## Contributing See [CONTRIBUTING.md](https://github.com/nick-thompson/react-juce/blob/master/CONTRIBUTING.md) ## License See [LICENSE.md](https://github.com/nick-thompson/react-juce/blob/master/LICENSE.md) ================================================ FILE: docs/.nojekyll ================================================ ================================================ FILE: docs/README.md ================================================ # Introduction React-JUCE is a hybrid JavaScript/C++ framework that enables a [React.js](https://reactjs.org/) frontend for a [JUCE](http://juce.com/) application or plugin. It provides an embedded, ECMAScript-compliant JavaScript engine via [Duktape](http://duktape.org/), native hooks for rendering the React component tree via `juce::Component` instances, and a flexbox layout engine via [Yoga](https://yogalayout.com/). For more information, see the introductory blog post here: [React-JUCE: A JUCE Rendering Backend for React.js](https://nickwritesablog.com/blueprint-a-juce-rendering-backend-for-react-js) ## Examples React-JUCE is a young project, but already it provides the framework on which the entire user interface for [Creative Intent's Remnant](https://www.creativeintent.co/product/remnant) plugin is built. ![Creative Intent Remnant: Screenshot](_media/RemnantScreenShot.jpg) Besides that, you can check out the example code in the `examples/` directory. See the "Documentation" section below for building and running the demo plugin. If you have a project written with React-JUCE that you want to share, get in touch! I would love to showcase your work. ## Contributing Yes, please! I would be very happy to welcome your involvement. Take a look at the [open issues](https://github.com/nick-thompson/react-juce/issues) or the [project tracker](https://github.com/nick-thompson/react-juce/projects/1) to see if there's outstanding work that you might be able to get started. Or feel free to propose an idea or offer feedback by [opening an issue](https://github.com/nick-thompson/react-juce/issues/new) as well. I don't have a formal style guide at the moment, so please try to match the present formatting in any code contributions. ## License See [LICENSE.md](https://github.com/nick-thompson/react-juce/blob/master/LICENSE.md) ================================================ FILE: docs/Resources.md ================================================ # Resources - [Introductory Blog Post](https://nickwritesablog.com/blueprint-a-juce-rendering-backend-for-react-js/) - [The Audio Programmer Podcast](http://img.youtube.com/vi/GOuFg773eCM/0.jpg) - [![The Audio Programmer Podcast](http://img.youtube.com/vi/GOuFg773eCM/0.jpg)](https://www.youtube.com/watch?v=GOuFg773eCM) - [ADC 2019 Blueprint: rendering React.js to JUCE](https://www.youtube.com/watch?v=51pdMfGU-4g) - [![ADC 2019 Blueprint: rendering React.js to JUCE](https://img.youtube.com/vi/51pdMfGU-4g/0.jpg)](https://www.youtube.com/watch?v=51pdMfGU-4g) ================================================ FILE: docs/SUMMARY.md ================================================ # Summary ## Guides - [Getting Started](guides/Getting_Started.md) - [Running the Examples](guides/Running_the_Examples.md) - [Integrating Your Project](guides/Integrating_Your_Project.md) - [Debugging with Duktape](guides/Debugging_With_Duktape.md) - [Why Not React Native?](guides/Why_Not_React_Native.md) ## Components - [View](components/View.md) - [Image](components/Image.md) - [Text](components/Text.md) - [Canvas](components/Canvas.md) - [Button](components/Button.md) - [Slider](components/Slider.md) - [ListView](components/ListView.md) - [ScrollView](components/ScrollView.md) - [Style Properties](components/Styles.md) - [Synthetic Events](components/Events.md) ## Native - [Custom Native Components](native/Custom_Native_Components.md) - [JS/Native Interop](native/Interop.md) ## Have a Question? - [GitHub Discussions](https://github.com/nick-thompson/react-juce/discussions) - [Discord Chatroom](https://discord.gg/3H4wwVf49v) - [Open an Issue](https://github.com/nick-thompson/react-juce/issues) ================================================ FILE: docs/components/Button.md ================================================ # Button A basic React component for rendering a button with web-like behavior and event interactions. ## Example ```js import React, { Component } from "react"; import { View, Button, Text } from "react-juce"; function App(props) { return ( ); } const styles = { outer: { width: "100%", height: "100%", backgroundColor: "#17191f", justifyContent: "center", alignItems: "center", }, button: { borderColor: "#66cffd", padding: 20, }, }; ``` ## Props `Button` inherits support for all of the core `View` properties described in [View](View.md). #### onClick A callback property which will be invoked in response to a mouse click. The callback should accept a single [`SyntheticMouseEvent`](Events.md) argument. | Type | Required | Supported | | -------- | -------- | ----------------------------------------------------------------------------------------- | | function | No | Partial: [Standard](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event) | ## Styles `Button` supports all of the default style properties described in [Style Properties](Styles.md). ================================================ FILE: docs/components/Canvas.md ================================================ # TODO ================================================ FILE: docs/components/Events.md ================================================ # TODO: Describe synthetic event support ================================================ FILE: docs/components/Image.md ================================================ # Image A React component for displaying different types of images, including network images, local image files, and image data URLs. The example below demonstrates displaying a local image file, and a data url. ## Example ```js import React, { Component } from "react"; import { View, Image } from "react-juce"; // Using a bundling tool like Webpack or Rollup, we can configure this import // to read the image file from disk at the time of bundling, and embed the data // URL in our javascript bundle which can be imported just like this. import logoDataUri from "./logo.jpg"; function App(props) { return ( console.log("Mouse event!", e)}> ); } const styles = { outer: { width: "100%", height: "100%", backgroundColor: "ff17191f", justifyContent: "center", alignItems: "center", }, image: { width: "25%", height: "25%", padding: 20, }, }; ``` ## Props `Image` inherits support for all of the core `View` properties described in [View](View.md). #### source The image source (either a remote URL ([#14](https://github.com/nick-thompson/react-juce/issues/14)), a local file resource, or a data uri). | Type | Required | Supported | | ------ | -------- | -------------------------------------------------------------------------- | | string | No | Partial: [Standard](https://developer.mozilla.org/en-US/docs/Glossary/URL) | ## Styles `Image` supports all of the default style properties described in [Style Properties](Styles.md). ================================================ FILE: docs/components/ListView.md ================================================ # TODO ================================================ FILE: docs/components/ScrollView.md ================================================ # TODO ================================================ FILE: docs/components/Slider.md ================================================ # Slider A basic React component for rendering a linear slider or rotary knob, common in many audio applications. `Slider` is simply a composition of a single `View` component, a single `Canvas` component, and a set of default drawing operations to render common slider representations. ## Example ```js import React, { Component } from "react"; import { View, Slider, Text } from "react-juce"; function App(props) { return ( Cutoff Frequency ); } const styles = { outer: { width: "100%", height: "100%", backgroundColor: "#17191f", justifyContent: "center", alignItems: "center", }, }; ``` ## Props `Slider` inherits support for all of the core `View` properties described in [View](View.md). #### sensitivity Sets the sensitivity of a mouse drag interaction as it relates to changing the slider value. The default value is `0.005`. Smaller numbers are less sensitive, larger numbers more sensitive. | Type | Required | Supported | | ------ | -------- | ------------ | | number | No | Non-Standard | #### value Sets the current value of the slider. See React.js' notion of [controlled and uncontrolled components](https://reactjs.org/docs/forms.html#controlled-components). The `Slider` value property and `onChange` behavior follow this model. | Type | Required | Supported | | ------ | -------- | ------------ | | number | No | Non-Standard | #### onDraw A callback property which will be invoked when its time to draw to the underlying `Canvas`. This property is much like the `onDraw` property on the `Canvas` view itself, but with additional arguments: (`context`, `width`, `height`, `value`). These arguments are, respectively, the underlying canvas rendering context, the current width of the underlying canvas object, the current height of the underlying canvas object, and the current slider value on the range [0, 1]. Default drawing operations are provided as static members of the `Slider` class, which can be passed directly as the `onDraw` property: - `Slider.drawLinearHorizontal` - `Slider.drawLinearVertical` - `Slider.drawRotary` (Default) | Type | Required | Supported | | -------- | -------- | ------------ | | function | No | Non-Standard | #### onChange A callback property which will be invoked when the slider's value changes. The callback should accept a single argument: a number on the range [0, 1]. | Type | Required | Supported | | -------- | -------- | ------------ | | function | No | Non-Standard | #### mapDragGestureToValue A callback property which will be invoked to map a drag gesture to a new slider value. This allows for customization of the drag behavior, for example: - Behavior that maps drags "up and to the right" as increasing in value, and "down and to the left" as decreasing. - Behavior that maps only rightward drag as increasing in value and leftward drag as decreasing invalue. - Behavior that maps only upwards drag as increasing in value and downwards drag as decreasing invalue. The callback should accept five arguments: `(mouseDownX, mouseDownY, sensitivity, valueAtDragStart, dragEvent)`. Respectively, these arguments are the x and y position of the mouse at the time of the mouseDown event, the `sensitivity` of the slider as defined by the `sensitivity` prop above, the slider value at the time the drag started, and the current `SyntheticMouseEvent` for the drag operation. Default mapping operations are provided as static members of the `Slider` class, which can be passed directly as the `mapDragGestureToValue` property: - `Slider.linearHorizontalGestureMap` - `Slider.linearVerticalGestureMap` - `Slider.rotaryGestureMap` (Default) | Type | Required | Supported | | -------- | -------- | ------------ | | function | No | Non-Standard | ## Styles `Slider` supports all of the default style properties described in [Style Properties](Styles.md). ================================================ FILE: docs/components/Styles.md ================================================ # Style Properties Style properties in React-JUCE generally aim to match the behavior and experience of writing CSS or writing a StyleSheet with React Native. It's important to note, however, that these style properties _are not_ proper CSS, and that we have no notion of parsing CSS stylesheets. In React-JUCE, these properties are simple directives passed to the underlying `juce::Component` heirarchy that yield behavior that matches much of the CSS specificiation. At present, these style properties are applied as a top-level prop to any given component, as you would with any other prop. In the future, we may adopt an API closer to that of React Native's StyleSheet. ## Layout Props | Property | Support | Spec | | ------------------ | ------- | -------------------------------------------------------------------------------- | | flex | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | flex-grow | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | flex-shrink | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | flex-basis | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | flex-direction | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | justify-content | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | align-items | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | align-content | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | align-self | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | flex-wrap | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | position | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | left | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | top | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | right | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | bottom | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | width | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | height | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | min-width | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | min-height | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | max-width | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | max-height | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | aspect-ratio | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | direction | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | overflow | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | margin | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | margin-left | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | margin-top | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | margin-right | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | margin-bottom | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | margin-start | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | margin-end | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | margin-horizontal | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | margin-vertical | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | padding | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | padding-left | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | padding-top | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | padding-right | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | padding-bottom | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | padding-start | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | padding-end | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | padding-horizontal | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | | padding-vertical | Yes | [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) | ## View Props | Property | Support | Spec | | -------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | | border-left-width | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-width](https://developer.mozilla.org/en-US/docs/Web/CSS/border-width) | | border-right-width | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-width](https://developer.mozilla.org/en-US/docs/Web/CSS/border-width) | | border-top-width | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-width](https://developer.mozilla.org/en-US/docs/Web/CSS/border-width) | | border-bottom-width | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-width](https://developer.mozilla.org/en-US/docs/Web/CSS/border-width) | | border-left-color | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-color](https://developer.mozilla.org/en-US/docs/Web/CSS/border-color) | | border-right-color | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-color](https://developer.mozilla.org/en-US/docs/Web/CSS/border-color) | | border-top-color | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-color](https://developer.mozilla.org/en-US/docs/Web/CSS/border-color) | | border-bottom-color | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-color](https://developer.mozilla.org/en-US/docs/Web/CSS/border-color) | | border-bottom-left-radius | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-radius](https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius) | | border-bottom-right-radius | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-radius](https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius) | | border-top-left-radius | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-radius](https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius) | | border-top-right-radius | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-radius](https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius) | | border-width | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-width](https://developer.mozilla.org/en-US/docs/Web/CSS/border-width) | | border-color | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-color](https://developer.mozilla.org/en-US/docs/Web/CSS/border-color) | | border-radius | Planned [(#143)](https://github.com/nick-thompson/react-juce/issues/143) | [MDN border-radius](https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius) | | background-color | Partial [(#84)](https://github.com/nick-thompson/react-juce/issues/84) | [MDN background-color](https://developer.mozilla.org/en-US/docs/Web/CSS/background-color) | | opacity | Yes | [juce::Component::setAlpha](https://docs.juce.com/master/classComponent.html#a1b9329a87c71ed01319071e0fedac128) | ================================================ FILE: docs/components/Text.md ================================================ # Text A React component for displaying text. Unlike React Native and React-DOM, `Text` does not yet support nesting ([#6](https://github.com/nick-thompson/react-juce/issues/6)), though it does happily support styling, and touch handling. ## Example ```js import React, { Component } from "react"; import { View, Text } from "react-juce"; function App(props) { return ( Hello, {props.name} ); } const styles = { outer: { width: "100%", height: "100%", backgroundColor: "#17191f", justifyContent: "center", alignItems: "center", }, labelText: { color: "#626262", fontSize: 16.0, lineSpacing: 1.6, }, nameText: { color: "#6262f8", fontSize: 16.0, lineSpacing: 1.6, }, }; ``` ## Props `Text` inherits support for all of the core `View` properties described in [View](View.md). #### color The color used to draw the text | Type | Required | Supported | | ------ | -------- | ------------------------------------------------------------------ | | string | No | [Standard](https://developer.mozilla.org/en-US/docs/Web/CSS/color) | #### fontSize The font size used to draw the text | Type | Required | Supported | | ------ | -------- | --------------------------------------------------------------------- | | string | No | [Partial](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size) | #### fontFamily The font family name with which to draw the text | Type | Required | Supported | | ------ | -------- | ----------------------------------------------------------------------- | | string | No | [Partial](https://developer.mozilla.org/en-US/docs/Web/CSS/font-family) | #### fontStyle Sets whether the text should be rendered bold, italics, or normal. | Type | Required | Supported | | ------ | -------- | ---------------------------------------------------------------------- | | string | No | [Partial](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style) | #### justification Sets the horizontal alignment of the text within the container. TODO: Rename this prop and match the text-align spec | Type | Required | Supported | | ------ | -------- | -------------------------------------------------------------------- | | string | No | [Non-Standard](https://docs.juce.com/master/classJustification.html) | #### kerningFactor Sets the horizontal spacing between text characters. TODO: Rename this prop and match the letter-spacing spec | Type | Required | Supported | | ------ | -------- | --------------------------------------------------------------------------------------------- | | string | No | [Non-Standard](https://docs.juce.com/master/classFont.html#a996b7095b0956f62b71f24893e72a914) | #### lineSpacing Sets the height of a line box, commonly used to apply vertical spacing between neighboring lines of text. TODO: Rename this prop and match the line-height spec | Type | Required | Supported | | ------ | -------- | --------------------------------------------------------------------------------------------------------- | | string | No | [Non-Standard](https://docs.juce.com/master/classAttributedString.html#a0506d7b2000aaebd5873ea23eca6ae6a) | #### wordWrap Sets the word-wrap behavior for when the text content overflows its content box. | Type | Required | Supported | | ------ | -------- | --------------------------------------------------------------------------------------------------------- | | string | No | [Non-Standard](https://docs.juce.com/master/classAttributedString.html#ad752f270294ec5b2cef0c80863ee3a3c) | ## Styles `Text` supports all of the default style properties described in [Style Properties](Styles.md). ================================================ FILE: docs/components/TextInput.md ================================================ TextInput component imitates web's ``. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text So it supports props like `placeholder`, `maxlength`, `onInput`, etc... TextInput also supports React's controlled components model: https://reactjs.org/docs/uncontrolled-components.html. If the user supplies a `value` prop, then it never renders anything into that text editor other than the string supplied by that `value` prop. ## Example ```js import React, { Component } from "react"; import { Text, TextInput, View } from "react-juce"; class App extends Component { constructor(props) { super(props); this._onInput = this._onInput.bind(this); this.state = { textValue: "", }; } _onInput(event) { console.log(`onInput: ${event.value}`); this.setState({ textValue: event.value }); } render() { return ( ); } } const styles = { text_input: { backgroundColor: "ff303030", color: "ff66FDCF", fontSize: 15.0, fontFamily: "Menlo", fontStyle: Text.FontStyleFlags.bold, "placeholder-color": "ffAAAAAA", height: 30, width: 200, }, }; export default App; ``` ## Props **TODO**: Document props. See [the class](https://github.com/nick-thompson/react-juce/blob/master/packages/react-juce/src/components/TextInput.tsx#L11) for props in the meantime. ================================================ FILE: docs/components/View.md ================================================ # View The most fundamental of the core React-JUCE components, `View` is a container that supports layout with flexbox, style, and some event handling. `View` maps directly to the underlying native `reactjuce::View` instance, which is effectively a simple `juce::Component`. `View` is designed to be nested inside other views and can have 0 to many children of any type. ## Example ```js import React, { Component } from "react"; import { View } from "react-juce"; function App(props) { return ( console.log("Mouse event!", e)}> ); } const styles = { outer: { width: "100%", height: "100%", backgroundColor: "ff17191f", justifyContent: "center", alignItems: "center", }, inner: { width: "25%", height: "25%", backgroundColor: "ffa7191f", }, }; ``` ## Props #### onMeasure A callback which will be invoked any time the `View`'s layout calculation changes. The callback should accept a single argument, a [SyntheticEvent](Events.md) object holding a `width` and `height` property reflecting the new size of the `View`. | Type | Required | Supported | | -------- | -------- | ----------------- | | function | No | Yes: Non-Standard | #### onMouseDown A callback which will be invoked in response to a mouse button down event on the underlying native component. The callback should accept a single argument, a [SyntheticMouseEvent](Events.md) object similar in interface to [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent). | Type | Required | Supported | | -------- | -------- | ----------------- | | function | No | Partial: Standard | #### onMouseUp A callback which will be invoked in response to a mouse button up event on the underlying native component. The callback should accept a single argument, a [SyntheticMouseEvent](Events.md) object similar in interface to [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent). | Type | Required | Supported | | -------- | -------- | ----------------- | | function | No | Partial: Standard | #### onMouseEnter A callback which will be invoked in response to a mouse entering the local bounds of a native component. The callback should accept a single argument, a `SyntheticMouseEvent` object similar in interface to [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent). | Type | Required | Supported | | -------- | -------- | ----------------- | | function | No | Partial: Standard | #### onMouseLeave A callback which will be invoked in response to a mouse leaving the local bounds of a native component. The callback should accept a single argument, a `SyntheticMouseEvent` object similar in interface to [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent). | Type | Required | Supported | | -------- | -------- | ----------------- | | function | No | Partial: Standard | #### onMouseDoubleClick A callback which will be invoked in response to a mouse button double click on the underlying native component. The callback should accept a single argument, a [SyntheticMouseEvent](Events.md) object similar in interface to [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent). | Type | Required | Supported | | -------- | -------- | ----------------- | | function | No | Partial: Standard | #### onKeyPress A callback which will be invoked in response to a key press event while the underlying native component has focus. The callback should accept a single argument, a [SyntheticKeyboardEvent](Events.md) object similar in interface to [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent). | Type | Required | Supported | | -------- | -------- | ----------------- | | function | No | Partial: Standard | ## Styles `View` supports all of the default style properties described in [Style Properties](Styles.md). ================================================ FILE: docs/guides/Debugging_With_Duktape.md ================================================ # Debugger Support (Because console.log() isn't always enough) Note, there are known issues debugging on Windows. See [Known Issues](#known-issues) JS debugging support for React-JUCE/Duktape is available via [Visual Studio Code](https://code.visualstudio.com/) using the [Duktape Debugger](https://marketplace.visualstudio.com/items?itemName=HaroldBrenes.duk-debug) extension. Currently this is the only debug client known to support the use of source maps with Duktape. If you do become aware of other Duktape debug clients that may work well with React-JUCE, please open an issue with details for us to investigate. React-JUCE provides an implementation of the Duktape debugging interface via `EcmascriptEngine::debuggerAttach` and `EcmascriptEngine::debuggerDetach`. If you are using the `ReactApplicationRoot` class to host your React app/ui then you can enjoy a debugging experience similar to that of React Native. In debug builds with `JUCE_DEBUG` defined, ensure your editor has focus and use the `CTRL-d/CMD-d` command to trigger a call to `EcmascriptEngine::debuggerAttach`. This command blocks the UI and awaits connection from a debug client. You can then attach your debugger, continue execution and set breakpoints etc. To configure the Duktape debugger extension for use with React-JUCE, you will need to do the following: - Download/Add the Duktape debugger extension. - Ensure you have compiled React-JUCE and your application/plugin in debug mode (with `JUCE_DEBUG` defined). - Add a launch configuration for debugging your UI. An example launch configuration for use with the `GainPlugin` example project is provided below. Note that `localRoot` points to the root `workspaceFolder`. In this case we assume `workspaceFolder` to be `examples/GainPlugin/jsui`. The `outDir` entry points to the build directory containing the compiled JS bundle you wish to debug. This `outDir` directory should also contain the bundle's associated source map. ``` { "version": "0.2.0", "configurations": [ { "name": "JSUI Debug Attach", "type": "duk", "request": "attach", "address": "localhost", "port": 9091, "localRoot": "${workspaceFolder}", "sourceMaps": true, "outDir": "${workspaceFolder}/build/js", "stopOnEntry": false, "debugLog": false } ] } ``` To ensure debugging works reliably, you need to ensure your source map has been generated by webpack using [devtool](https://webpack.js.org/configuration/devtool/). The following option should be added to the `output` key in your webpack config: `devtoolModuleFilenameTemplate: 'webpack:///[absolute-resource-path]'` You also need to ensure that your bundle is not minified, this can be achieved by building your JS bundle with `webpack --mode=development`. In the case of the `GainPlugin` example this can be achieved by running `npm run start` in `GainPlugin/jsui`. The `webpack.config.js` template provided by React-JUCE enables all of this for you. See: [Starting a new Project](New_Project.md) for a project template setup which includes debugger support. Project template files are available under `react-juce/packages/react-juce/template`. Example `webpack.config.js`: ``` module.exports = { entry: './src/index.js', // This is the section you care about for debugging. output: { path: __dirname + '/build/js', filename: 'main.js', sourceMapFilename: "[file].map", devtoolModuleFilenameTemplate: 'webpack:///[absolute-resource-path]' }, devtool: "source-map", module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: ['babel-loader'] }, { test: /\.svg$/, exclude: /node_modules/, use: ['svg-inline-loader'] }, { test: /\.(png|jpeg|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: true, esModule: false } } ] } ] }, } ``` To test debugging support using the `GainPlugin` example do the following: - Open the `GainPlugin/jsui` directory in Visual Studio Code. - Add the `launch.json` configuration from above to your `.vscode` directory under `GainPlugin/jsui`. - Run `npm run start` in the `jsui` directory - Start the standalone plugin app in your IDE (i.e. Visual Studio/Xcode) - Give the `GainPlugin`'s UI/PluginEditor focus and hit `CTRL-d` (Win) / `CMD-d` (Mac) - Start the `JSUI Debug Attach` launch task from the VSCode debugger menu. - Set some breakpoints, profit! ## Known Issues - Debugging support is currently experimental and issues may occur from time to time whilst we refine the experience. - At present there are some issues debugging duktape under Windows. The vscode-duktape-debug extension requires a patch to resolve source map entries, without this patch setting breakpoints in source files fails. To obtain a version of the extension with this patch you can fetch https://github.com/JoshMarler/vscode-duktape-debug/tree/topic/fix-windows-debug-blueprint. To use this version of the extension run `npm install` in the `vscode-duktape-debug` root followed by `npm run compile`. You can then copy the contents of `vscode-duktape-debug/dist` to your vscode extensions directory which is usually under `.vscode` in your home folder. Copy the contents of `vscode-duktape-debug/dist` to `.vscode/extensions/haroldbrenes.duk-debug-0.5.6`. There are issues attaching the debugger with breakpoints already set in vscode on Windows. You will need to attach the debugger and then set breakpoints once attached. ## Acknowledgements Thanks to [harold-b](https://github.com/harold-b/vscode-duktape-debug) for his excellent extension. ================================================ FILE: docs/guides/Getting_Started.md ================================================ # Getting Started This guide assumes you have some familiarity with [React.js](https://reactjs.org/) and with [JUCE](https://juce.com/), and it is therefore recommended that you spend some time getting comfortable there, if you're not already, before embarking on this guide. ## Dependencies To get started with React-JUCE, you'll first need to install a few dependencies: - [Node.js](https://nodejs.org/en/) v8.11.0+ - [npm](https://www.npmjs.com/) v5.6.0+ - [JUCE](https://juce.com/) v5.4.2+ (Optional if you are only running the examples) - Xcode10.2+ (MacOS) - Visual Studio 2017+ (Windows) Once you have the dependencies installed, we need to clone the React-JUCE repository itself. React-JUCE's git repository contains necessary submodules, so we'll need to collect those as well, which we can do one of two ways: ```bash $ git clone --recurse-submodules git@github.com:nick-thompson/react-juce.git ``` or ```bash $ git clone git@github.com:nick-thompson/react-juce.git $ cd react-juce $ git submodule update --init --recursive ``` Note that the `git@github.com` prefix here indicates cloning via SSH. If you prefer to work with git via HTTPS you'll want to swap in `https://github.com/nick-thompson/react-juce.git` in the above commands. At this point, we've got everything ready to get our project up and running. Let's move on to the next step, [running the demo plugin](Running_the_Examples.md). ================================================ FILE: docs/guides/Integrating_Your_Project.md ================================================ # Starting a New Project This step assumes you've already reviewed the [Getting Started](Getting_Started.md) guide. If not, please start there! Ok, so you're ready to add some React.js to your JUCE project. Whether that's a totally new project or a preexisting project, getting React-JUCE involved is the same. If you're starting your project from scratch, I recommend running through the [Getting started with the Projucer](https://docs.juce.com/master/tutorial_new_projucer_project.html) tutorial on the JUCE website, if you haven't already. Now before we write any code, we have to add React-JUCE to our JUCE project. Fortunately, JUCE makes this super easy with the JUCE module format, and React-JUCE abides by that format. Follow along with the [Manage JUCE modules](https://docs.juce.com/master/tutorial_manage_projucer_project.html#tutorial_manage_projucer_project_managing_modules) section of the Projucer tutorial, wherein you'll need to add the React-JUCE module by pointing the Projucer to its location on disk. The actual React-JUCE JUCE module is located in the `react_juce` subdirectory of the root of the React-JUCE project. ## Template Generator Next, the first thing we want to do here is write some React.js, so let's start with a "Hello World!" of our own. React-JUCE's `react-juce` npm package carries a template generator that you can use to boostrap a React application for your project. For this step, let's assume your JUCE project directory is at `~/MyProject`, the source files are at `~/MyProject/Source`, and we want to put the React application source at `~/MyProject/Source/jsui` (note, you can put this wherever you want). Now, to use the template generator, we start again at the root of the React-JUCE git repository: ```bash $ pwd /Users/nick/Dev/react-juce $ cd packages/react-juce $ npm run init -- ~/MyProject/Source/jsui ``` The template generator will create the `jsui` directory as suggested in the example command above, fill it with a basic "Hello World!" app, and install local dependencies like React.js and Webpack. Like the [[GainPlugin Example|running-the-example]], we now need to build our output bundle. ```bash $ cd ~/MyProject/Source/jsui $ npm start ``` At this point we've got our app bundle ready to roll, so let's turn over to the native side to mount this into our JUCE project. ## Native Code Because we've already added the React-JUCE module to our Projucer project, we can jump straight into the code on the native side. Part of the native React-JUCE API is a particularly important class called `reactjuce::ReactApplicationRoot`. This class is mostly just a `juce::Component`, and in that way you should think about using it the same way you might use a `juce::Slider` in your application. For example, let's suppose that we have our `MainComponent` or our `AudioProcessorPluginEditor` at the top of our project: ```cpp class MainComponent : public Component { public: //============================================================================== MainComponent(); ~MainComponent(); //============================================================================== void paint (Graphics&) override; void resized() override; private: //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent) }; ``` Adding the `reactjuce::ReactApplicationRoot` is easy, and should be familiar if you've worked with `juce::Component`s before: ```cpp class MainComponent : public Component { public: //============================================================================== MainComponent() { addAndMakeVisible(appRoot); setSize(400, 300); } ~MainComponent(); //============================================================================== void paint (Graphics&) override; void resized() override { appRoot.setBounds(getLocalBounds()); } private: //============================================================================== reactjuce::ReactApplicationRoot appRoot; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent) }; ``` It's important to note here that our `appRoot` will be the point at which our React application code from earlier will "take over," and it's also important to note that you can add this `appRoot` wherever you need it in your application. For example, if you want to write your entire interface in React, you should mount your `appRoot` at the top of your application in your `MainComponent` or your `AudioProcessorPluginEditor`. If instead you want only to write your preset switcher in React, you can build the rest of your interface as usual with JUCE, and add the `appRoot` wherever the preset switcher should go within the context of your interface. Now we're almost done here, but if you compile and run right now you won't see your "Hello from React.js!"– of course, we haven't told the `appRoot` where to find the JavaScript bundle we made! So, putting the last piece together here: ```cpp class MainComponent : public Component { public: //============================================================================== MainComponent() { // First thing we have to do is load our javascript bundle from the build // directory so that we can evaluate it within our appRot. // TODO: Replace this with the appropriate path to your javascript bundle! File bundle = File("/path/to/your/jsui/build/js/main.js"); addAndMakeVisible(appRoot); appRoot.evaluate(bundle); setSize(400, 300); } ~MainComponent(); //============================================================================== void paint (Graphics&) override; void resized() override { appRoot.setBounds(getLocalBounds()); } private: //============================================================================== reactjuce::ReactApplicationRoot appRoot; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent) }; ``` That's it! We've now integrated React-JUCE into our project, and now you can write freely in React.js and watch your application take shape. ================================================ FILE: docs/guides/Running_the_Examples.md ================================================ # Running the GainPlugin Example project This step assumes you've already reviewed the [Getting Started](Getting_Started.md) guide. If not, please start there! ## GainPlugin ![Gain Plugin example project user interface](../_media/gainplugin.jpg) React-JUCE includes an extremely simple utility gain plugin to demonstrate the minimal code required for integrating React-JUCE into a JUCE project. To get it running, we only need two steps: compiling the JavaScript bundle, and then compiling and running the native code. ### JavaScript Bundle All React-JUCE projects have two components: we have the native code that knows how to mount the React application into our JUCE project and satisfy calls from React's reconciler, and then we have the React.js application code that we want to run in the native environment. In order to ease this handoff, we're using a tool in this example called [Webpack](https://webpack.js.org/) that can compile a complete JavaScript project into a single file. That single file is then the only thing we need to execute in the native environment. To get the GainPlugin up and running for the first time, we have to first perform that compilation step. So, from the root of the `React-JUCE` git repository: ```bash $ cd examples/GainPlugin/Source/jsui/ ``` The `jsui/` directory here is the top level directory of all the React.js application code that we used to build the example interface. We use a build step managed by npm to compile all of our application javascript into a single file that can be easily loaded up in the native app: ```bash $ npm install $ npm run build ``` At this point, you'll see an output file in `examples/GainPlugin/Source/jsui/build/js/`. That file location is important, because that's where the native code looks for executing the output file. ### Native Now that we have our JavaScript bundle, the last step is simple: just hit Build and Run from your IDE! When the JUCE PluginEditor gets initialized, it will find your JavaScript build and evaluate it within the embedded interpreter to draw the GainPlugin interface. ## Hot Reloading The GainPlugin example is preconfigured with hot reloading, so that as you edit the React application code, your interface will redraw itself instantly. In order to take advantage of this, we need to recompile our JavaScript bundle on the fly as we're editing our code. Fortunately, Webpack makes that simple, so from within the `jsui/` directory: ```bash $ npm start ``` This will first build your JavaScript bundle, then wait and watch your source files for changes. When it finds a change, it will pick that change up and incrementally rebuild your bundle. In turn, React-JUCE will pick up the change to the bundle file and redraw your interface. Now that you're up and running, take a minute to tweak the GainPlugin React application to get a sense of the workflow! When you're done, let's move on to the next step, [adding React-JUCE to your own project](New_Project.md). ================================================ FILE: docs/guides/Why_Not_React_Native.md ================================================ # Why Not React Native? The React community first formed in 2013 when React was introduced as a framework for writing web applications. Shortly thereafter, the team announced React Native for rendering native iOS and Android mobile apps, and the React community grew accordingly. The community then introduced a project for rendering native Windows desktop apps from React, and since then several projects have spun off with similar goals. Why, then, do we need another one in React-JUCE? To start, I think it's helpful to clarify the goals of these projects and the underlying philosophy that brings them together. Then, I think's helpful to recognize JUCE's utility and popularity in the audio application development space. Finally, once we've covered those points, I think the answer to our original question becomes quite clear, and shows why React-JUCE stands to make an impact in the audio application development space. ## The React Philosophy When the React team introduced React Native in 2015, they made a point to clarify that React and React Native were different projects. With its introduction, they started sharing the idea "Learn once, write anywhere," which we still see in their messaging today. What we see from that message is that their goal was never to be able to write a React web app and then push a button to ship that same app to native mobile. Rather, the goal was slightly higher level. At the initial React.js announcement, the library focused totally on web applications. When React Native showed up, we saw the team split out the React DOM package. This step is very illuminating. After that, to talk about React in the context of a cross-platform web/mobile app, we have to consider three things: React, React DOM, and React Native. This distinction is helpful in that it shows React itself is only an abstract tool. It deals in abstract representations of a user interface, and the business logic around that user interface. But eventually this abstraction layer decays to real rendering primitives, and here we see what separates React DOM and React Native. React DOM provides a library of rendering primitives targeting the Web DOM. React Native provides a library of rendering primitives targeting native platform components in iOS and Android. (As a brief aside, this distinction in rendering primitives is exactly where React-JUCE fits into the picture as well, offering a library of rendering primitives that target JUCE.) Through this lens, React Native's "Learn once, write anywhere" philosophy makes sense. Different rendering primitives on different platforms may have totally different semantics, and the idea of writing an application against one set of rendering primitives and then pushing a button to render against a new set of primitives just falls over in some cases. But the idea of _working in React_ never changes. With these tools, a single team can write a web application and then turn around and write a native iOS, Android or Windows desktop application with total familiarity. This isn't "push a button, render anywhere," it's about lowering the barrier to entry to new platforms, unifying the development experience, and empowering developers to cross what have long been great divides in our industry. ## The JUCE Ecosystem Our brief survey of the React landscape tells a compelling story when viewed from the perspective of a generic application developer. But what happens when we look from the perspective of an audio application developer? React Native solves a very non-trivial problem in the way it bridges between iOS and Android. A React Native developer can write a single application and deploy it to two totally different platforms, while React Native takes care of answering all the prerequisite questions about how to get an application up and running on each respective platform, how to provision that top level window, and how to evaluate the application JavaScript bundle in an embedded interpreter. JUCE takes its place in the audio application development ecosystem answering very similar questions. Nearly all audio applications start with the same two requirements: - A window provisioned somewhere (either directly or perhaps by a host program) into which we can render our interface - An audio buffer from the driver on which we can operate our signal processing Unfortunately, in audio, and especially when we consider audio plugin development, this is a combinatorial problem which grows extremely quickly. To answer even the first bullet point above, we have a lot to consider. - How do I get to that top-level window when running as an AAX plugin on Windows? - What rendering primitives do I use once I'm there? Windows Platform APIs? - How do I get to that top-level window when running as an AU plugin on MacOS? - And what rendering primitives can I use here? CoreGraphics? - And what if I want to ship the same application as a standalone Linux application? Or even an embedded Linux application? How do I get to that top-level window against X11 there? I could keep that list running for a _long_ time. And that's just to address the first of the core requirements of an audio application; the same list runs back for getting access to the underlying audio callback in all of these scenarios. Here, I think it's fair to say that JUCE has solved a massively challenging problem very elegantly in abstracting over all of these combinations. To write an audio application with JUCE, developers need only start from the core questions about _what_ the application will do once it has access to that window and the underlying audio buffer, and JUCE will take care of extending that functionality across all of the combinations of target platform, windowing system, audio driver, and plugin host. This is why, in my opinion, JUCE has become such a widely adopted framework for audio application development. ## React + JUCE = <3 Against this backdrop, a compelling case for React-JUCE is already starting to reveal itself. But before going any further, I think it's worthwhile to say that React Native and React Native Windows _are_ great options. If your goal is singular in focus, say, to ship an audio application on iOS, then starting at React Native is an excellent choice. React Native will solve the top level window question for you, and you may then have to solve the problem of "How do I get to that audio buffer when running as a standalone on iOS," but that's a problem you'll only need to solve once. Or you might start at JUCE and figure out how to mount React Native into the window that JUCE provisions for you. Either way, in a scenario like this, your project will be neatly limited in the scope of the problems that you have to solve. I would happily recommend to people with a goal that has this type of focus to use React Native, or React Native Windows. But this approach starts to break down when the project goal broadens. For example, maybe you want to ship your audio application as a standalone desktop app and a standalone iOS app. Now, if you start with React Native, you can find your answer to the question above for getting to the audio buffer just the same on iOS, but what happens when you want to ship to desktop? Well, maybe the audio buffer part is the same, and surely if you've integrated JUCE into your React Native project you can rely on JUCE for that part here too, but React Native doesn't render to desktop. So do you integrate a different framework for writing the desktop interface? Maybe you can integrate React Native Windows, but what about MacOS? Suddenly we're creeping back into the combinatorial problem we mentioned above, and we haven't even cracked this door open to include shipping the application as a cross-platform audio plugin, or potentially as a Linux application. Here is exactly the place where React-JUCE finds its home. With React-JUCE, we can leverage JUCE's elegant solution to this hugely combinatorial problem, while at the same time leveraging the capabilities that React has introduced to the world of app UI programming, with no extra overhead. Shipping an audio application with React as a standalone application across MacOS, Windows, Linux, and as a plugin across AAX, AU, VST3, LV2 is now as easy as it should be. And there's more still. It would be a shame to acknowledge the popularity of JUCE without addressing all of the many projects already shipped or actively underway with JUCE. React-JUCE is designed to be trivially integrated into any existing JUCE project: anywhere you can mount a `juce::Component`, whether that's in an existing project or a brand new project, you can mount React-JUCE, and start writing React. I started this project with the goal of fully embracing the "Learn once, write anywhere" message introduced by React and React Native. It's my hope now that in the same way React Native empowers React developers to approach mobile platforms, React-JUCE empowers developers to approach the audio application space. ================================================ FILE: docs/native/Custom_Native_Components.md ================================================ # TODO ================================================ FILE: docs/native/Interop.md ================================================ # JavaScript/Native Interop There are generally two ways to cross the Native/JavaScript boundary: 1. You can pass C++ values and functions into JavaScript, and JavaScript values and functions into C++ directly or via an event model 2. Or, you can create a custom native View and render it in your React component tree ## Value Passing To pass C++ values and functions into your JS environment, see `EcmascriptEngine::registerNativeProperty` and `EcmascriptEngine::registerNativeMethod`. These methods will install the given values on either the `global` object, or named object of your choosing. In the other direction, you can pass values from JavaScript into C++ either via invoking the methods that were installed via `EcmascriptEngine::registerNativeMethod` or by assigning a value to a named object accessible from the global object, and looking that value up or invoking it on the C++ side with `EcmascriptEngine::evaluateInline` or `EcmascriptEngine::invoke`. ### Example ```cpp // From C++ engine->registerNativeProperty("world", "Hello!"); engine->registerNativeMethod("sayHello", [this](juce::var::NativeFunctionArgs const& args) { // Here, `args.arguments[0]` comes from JavaScript std::cout << args.arguments[0].toStdString() << std::endl; }); ``` ```js // Then, from js console.log("Hello", global.world); global.sayHello("World!"); ``` And similarly, in the other direction: ```js // From js global.myFun = (x) => x * x; global.myValue = 17; ``` ```cpp // From C++ auto myValue = engine->evaluateInline("global.myValue"); auto result = engine->invoke("myFun", (int) myValue); ``` ## Custom Native View Now the other option is to define a totally custom native view, and register it with React. To do this, you can make your own C++ class that inherits `reactjuce::View`, and override the necessary methods. Basically, a `reactjuce::View` is just a `juce::Component` with some extra goodies. For greater detail, see [Custom Native Components](Custom_Native_Components.md), but for a brief example, see below. ### Example ```cpp class MyCoolView : public reactjuce::View { void paint(juce::Graphics& g) override { // Do your own paint routine like usual. // You can also treat this whole class instance like your normal juce::Components. Add children, `addAndMakeVisible`, // `resized` and everything! } } ``` Now once you've got your custom view implementation, you have to tell React about it so that you can use it. The end goal with this is to be able to write something like this: ```js function MyReactApp(props) { return ( ); } ``` With this, you can write your juce::Components like normal, but use React to compose these things together into your full application. So, how do we register your C++ class with React? All you need is `ReactApplicationRoot::registerViewType`: ```cpp mAppRoot.registerViewType("MyCoolView", []() { // This lambda will be called when we need to construct one of your custom view instances. So, first, we make one: auto view = std::make_unique(); // Then we need to construct a shadow view, which will govern the layout. 99% of the time, you can just use the // default shadow view: auto shadowView = std::make_unique(view.get()); // Then just return these guys as a pair return {std::move(view), std::move(shadowView)}; }); ``` That's it, React can now understand your `MyCoolView`! Now there's one last cosmetic issue. In React, you can always make elements with `React.createElement("MyCoolView", props, children);`, but you rarely see that because people are used to using JSX and writing ``. If you want to use JSX like that you have to make this little wrapper to abstract over the dynamic string argument: ```js function MyCoolView(props) { return React.createElement("MyCoolView", props, props.children); } // Now I can use it elsewhere in JSX... function MyApp(props) { return ; } ``` ================================================ FILE: examples/GainPlugin/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(GainPlugin VERSION 0.1.0) # `juce_add_plugin` adds a static library target with the name passed as the first argument. # This target is a normal CMake target, but has a lot of extra properties set # up by default. As well as this shared code static library, this function adds targets for each of # the formats specified by the FORMATS arguments. This function accepts many optional arguments. # Check the readme at `docs/CMake API.md` in the JUCE repo for the full list. juce_add_plugin(GainPlugin # VERSION ... # Set this if the plugin version is different to the project version # ICON_BIG ... # ICON_* arguments specify a path to an image file to use as an icon for the Standalone # ICON_SMALL ... # COMPANY_NAME ... # Specify the name of the plugin's author # IS_SYNTH TRUE/FALSE # Is this a synth or an effect? # NEEDS_MIDI_INPUT TRUE/FALSE # Does the plugin need midi input? # NEEDS_MIDI_OUTPUT TRUE/FALSE # Does the plugin need midi output? # IS_MIDI_EFFECT TRUE/FALSE # Is this plugin a MIDI effect? # EDITOR_WANTS_KEYBOARD_FOCUS TRUE/FALSE # Does the editor need keyboard focus? # COPY_PLUGIN_AFTER_BUILD TRUE/FALSE # Should the plugin be installed to a default location after building? PLUGIN_MANUFACTURER_CODE Reju # A four-character manufacturer id with at least one upper-case character PLUGIN_CODE Dem0 # A unique four-character plugin id with at least one upper-case character FORMATS AU VST3 Standalone # The formats to build. Other valid formats are: AAX Unity VST AU AUv3 PRODUCT_NAME "ReactJUCEGainPlugin") # The name of the final executable, which can differ from the target name # `juce_generate_juce_header` will create a JuceHeader.h for a given target, which will be generated # into your build tree. This should be included with `#include `. The include path for # this header will be automatically added to the target. The main function of the JuceHeader is to # include all your JUCE module headers; if you're happy to include module headers directly, you # probably don't need to call this. juce_generate_juce_header(GainPlugin) # `target_sources` adds source files to a target. We pass the target that needs the sources as the # first argument, then a visibility parameter for the sources (PRIVATE is normally best practice, # although it doesn't really affect executable targets). Finally, we supply a list of source files # that will be built into the target. This is a standard CMake command. target_sources(GainPlugin PRIVATE PluginProcessor.cpp) # Add an explicit include path here so that React-juce's EcmascriptEngine can find # the included Duktape source. You can optionally point this elsewhere if you'd like to # include a custom Duktape build. target_include_directories(GainPlugin PRIVATE react_juce/) # `target_compile_definitions` adds some preprocessor definitions to our target. In a Projucer # project, these might be passed in the 'Preprocessor Definitions' field. JUCE modules also make use # of compile definitions to switch certain features on/off, so if there's a particular feature you # need that's not on by default, check the module header for the correct flag to set here. These # definitions will be visible both to your code, and also the JUCE module code, so for new # definitions, pick unique names that are unlikely to collide! This is a standard CMake command. target_compile_definitions(GainPlugin PRIVATE GAINPLUGIN_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" JUCE_WEB_BROWSER=0 # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call JUCE_USE_CURL=0 # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call JUCE_VST3_CAN_REPLACE_VST2=0) # `target_link_libraries` links libraries and JUCE modules to other libraries or executables. Here, # we're linking our executable target to the `juce::juce_audio_utils` module. Inter-module # dependencies are resolved automatically, so `juce_core`, `juce_events` and so on will also be # linked automatically. If we'd generated a binary data target above, we would need to link to it # here too. This is a standard CMake command. target_link_libraries(GainPlugin PRIVATE juce::juce_recommended_config_flags juce::juce_recommended_lto_flags juce::juce_recommended_warning_flags juce::juce_core juce::juce_audio_basics juce::juce_audio_devices juce::juce_audio_processors juce::juce_audio_utils juce::juce_graphics juce::juce_gui_basics react_juce) ================================================ FILE: examples/GainPlugin/PluginProcessor.cpp ================================================ /* ============================================================================== This file was auto-generated! It contains the basic framework code for a JUCE plugin processor. ============================================================================== */ #include "PluginProcessor.h" //============================================================================== /** Helper function for generating the parameter layout. */ AudioProcessorValueTreeState::ParameterLayout createParameterLayout() { AudioProcessorValueTreeState::ParameterLayout params ( std::make_unique( "MainGain", "Gain", NormalisableRange(0.0, 1.0), 0.8, String(), AudioProcessorParameter::genericParameter, [](float value, int /* maxLength */) { return String(Decibels::gainToDecibels(value), 1) + "dB"; }, nullptr ), std::make_unique( "MainMute", "Mute", false ) ); return params; } //============================================================================== GainPluginAudioProcessor::GainPluginAudioProcessor() : AudioProcessor (BusesProperties() .withInput ("Input", AudioChannelSet::stereo(), true) .withOutput ("Output", AudioChannelSet::stereo(), true)), params(*this, nullptr, JucePlugin_Name, createParameterLayout()) { } GainPluginAudioProcessor::~GainPluginAudioProcessor() { stopTimer(); } //============================================================================== const String GainPluginAudioProcessor::getName() const { return JucePlugin_Name; } bool GainPluginAudioProcessor::acceptsMidi() const { #if JucePlugin_WantsMidiInput return true; #else return false; #endif } bool GainPluginAudioProcessor::producesMidi() const { #if JucePlugin_ProducesMidiOutput return true; #else return false; #endif } bool GainPluginAudioProcessor::isMidiEffect() const { #if JucePlugin_IsMidiEffect return true; #else return false; #endif } double GainPluginAudioProcessor::getTailLengthSeconds() const { return 0.0; } int GainPluginAudioProcessor::getNumPrograms() { return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, // so this should be at least 1, even if you're not really implementing programs. } int GainPluginAudioProcessor::getCurrentProgram() { return 0; } void GainPluginAudioProcessor::setCurrentProgram (int /* index */) {} const String GainPluginAudioProcessor::getProgramName (int /* index */) { return {}; } void GainPluginAudioProcessor::changeProgramName (int /* index */, const String& /* newName */) {} //============================================================================== void GainPluginAudioProcessor::prepareToPlay (double sampleRate, int /* samplesPerBlock */) { gain.reset(sampleRate, 0.02); } void GainPluginAudioProcessor::releaseResources() { // When playback stops, you can use this as an opportunity to free up any // spare memory, etc. } #ifndef JucePlugin_PreferredChannelConfigurations bool GainPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { #if JucePlugin_IsMidiEffect ignoreUnused (layouts); return true; #else // This is the place where you check if the layout is supported. // In this template code we only support mono or stereo. if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono() && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo()) return false; // This checks if the input layout matches the output layout #if ! JucePlugin_IsSynth if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) return false; #endif return true; #endif } #endif void GainPluginAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& /* midiMessages */) { ScopedNoDenormals noDenormals; auto totalNumInputChannels = getTotalNumInputChannels(); auto totalNumOutputChannels = getTotalNumOutputChannels(); // In case we have more outputs than inputs, this code clears any output // channels that didn't contain input data, (because these aren't // guaranteed to be empty - they may contain garbage). // This is here to avoid people getting screaming feedback // when they first compile a plugin, but obviously you don't need to keep // this code if your algorithm always overwrites all the output channels. for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) buffer.clear (i, 0, buffer.getNumSamples()); // Our intense dsp processing gain.setTargetValue(*params.getRawParameterValue("MainGain")); gain.applyGain(buffer, buffer.getNumSamples()); if (auto *muteParam = dynamic_cast(params.getParameter("MainMute"))) { bool muted = muteParam->get(); if (muted) buffer.applyGain(0.0f); } // Read current block gain peak value gainPeakValue = buffer.getMagnitude (0, buffer.getNumSamples()); } //============================================================================== bool GainPluginAudioProcessor::hasEditor() const { return true; // (change this to false if you choose to not supply an editor) } AudioProcessorEditor* GainPluginAudioProcessor::createEditor() { // The GainPlugin example uses the GenericEditor, which is a default // AudioProcessorEditor provided that will automatically bootstrap // your React root, install some native method hooks for parameter interaction // if you provide an AudioProcessorValueTreeState, and manage hot reloading // of the source bundle. You can always start with the GenericEditor // then switch to a custom editor when you need more explicit control. File sourceDir = File(GAINPLUGIN_SOURCE_DIR); File bundle = sourceDir.getChildFile("jsui/build/js/main.js"); auto* editor = new reactjuce::GenericEditor(*this, bundle); editor->setResizable(true, true); editor->setResizeLimits(400, 240, 400 * 2, 240 * 2); editor->getConstrainer()->setFixedAspectRatio(400.0 / 240.0); editor->setSize (400, 240); // Start timer to dispatch gainPeakValues event to update Meter values startTimer(100); return editor; } void GainPluginAudioProcessor::timerCallback() { if (auto* editor = dynamic_cast(getActiveEditor())) { // Dispatch gainPeakValues event used by Meter React component editor->getReactAppRoot().dispatchEvent( "gainPeakValues", static_cast(gainPeakValue), static_cast(gainPeakValue) ); } } //============================================================================== void GainPluginAudioProcessor::getStateInformation (MemoryBlock& /* destData */) { // You should use this method to store your parameters in the memory block. // You could do that either as raw data, or use the XML or ValueTree classes // as intermediaries to make it easy to save and load complex data. } void GainPluginAudioProcessor::setStateInformation (const void* /* data */, int /* sizeInBytes */) { // You should use this method to restore your parameters from this memory block, // whose contents will have been created by the getStateInformation() call. } //============================================================================== // This creates new instances of the plugin.. AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new GainPluginAudioProcessor(); } ================================================ FILE: examples/GainPlugin/PluginProcessor.h ================================================ /* ============================================================================== This file was auto-generated! It contains the basic framework code for a JUCE plugin processor. ============================================================================== */ #pragma once #include "../JuceLibraryCode/JuceHeader.h" //============================================================================== /** */ class GainPluginAudioProcessor : public AudioProcessor, private Timer { public: //============================================================================== GainPluginAudioProcessor(); ~GainPluginAudioProcessor() override; //============================================================================== void prepareToPlay (double sampleRate, int samplesPerBlock) override; void releaseResources() override; #ifndef JucePlugin_PreferredChannelConfigurations bool isBusesLayoutSupported (const BusesLayout& layouts) const override; #endif void processBlock (AudioBuffer&, MidiBuffer&) override; //============================================================================== AudioProcessorEditor* createEditor() override; bool hasEditor() const override; //============================================================================== const String getName() const override; bool acceptsMidi() const override; bool producesMidi() const override; bool isMidiEffect() const override; double getTailLengthSeconds() const override; //============================================================================== int getNumPrograms() override; int getCurrentProgram() override; void setCurrentProgram (int index) override; const String getProgramName (int index) override; void changeProgramName (int index, const String& newName) override; //============================================================================== void getStateInformation (MemoryBlock& destData) override; void setStateInformation (const void* data, int sizeInBytes) override; //============================================================================== AudioProcessorValueTreeState& getValueTreeState() { return params; } void timerCallback() override; private: //============================================================================== AudioProcessorValueTreeState params; LinearSmoothedValue gain; std::atomic gainPeakValue; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainPluginAudioProcessor) }; ================================================ FILE: examples/GainPlugin/jsui/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: examples/GainPlugin/jsui/babel.config.js ================================================ module.exports = { presets: [ [ "@babel/preset-env", { modules: "umd", }, ], "@babel/preset-react", ], plugins: [ "@babel/plugin-proposal-class-properties", [ "@babel/plugin-transform-runtime", { absoluteRuntime: false, corejs: 3, version: "7.11.2", }, ], ], }; ================================================ FILE: examples/GainPlugin/jsui/package.json ================================================ { "name": "ui", "version": "0.1.0", "private": true, "dependencies": { "@babel/runtime-corejs3": "^7.11.2", "file-loader": "6.1.0", "react": "^16.13.1", "react-juce": "^0.2.16" }, "devDependencies": { "@babel/core": "^7.11.1", "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-transform-runtime": "^7.11.0", "@babel/preset-env": "^7.11.0", "@babel/preset-react": "^7.10.4", "babel-loader": "^8.0.4", "svg-inline-loader": "^0.8.0", "url-loader": "4.1.0", "webpack": "^5.46.0", "webpack-cli": "^4.7.2" }, "scripts": { "start": "webpack -w --mode=development", "build": "webpack --mode=production" } } ================================================ FILE: examples/GainPlugin/jsui/src/AnimatedFlexBox.js ================================================ import React, { Component } from "react"; import { View, Text } from "react-juce"; class AnimatedFlexBoxExample extends Component { render() { return ( Look at me, cell #1! Look at me, cell #2! Look at me, cell #3! Look at me, cell #4! Look at me, cell #5! Look at me, cell #6! Look at me, cell #7! Look at me, cell #8! ); } } const styles = { container: { width: "100%", height: "100%", backgroundColor: "#17191f", justifyContent: "flex-start", alignContent: "flex-start", flexWrap: "wrap", layoutAnimated: { duration: 200.0, frameRate: 45, easing: View.EasingFunctions.quadraticInOut, }, }, cell: { flex: 0.0, width: 100.0, height: 100.0, justifyContent: "space-around", alignItems: "center", backgroundColor: "#87898f", margin: 6.0, padding: 6.0, }, text: { fontSize: 16.0, lineSpacing: 1.6, justification: Text.JustificationFlags.centred, }, }; export default AnimatedFlexBoxExample; ================================================ FILE: examples/GainPlugin/jsui/src/App.js ================================================ // import AnimatedFlexBoxExample from "./AnimatedFlexBox"; import Meter from "./Meter"; import Knob from "./Knob"; import ParameterToggleButton from "./ParameterToggleButton"; import React, { Component } from "react"; import { Canvas, Image, Text, View } from "react-juce"; function animatedDraw(ctx) { let now = Date.now() / 10; let width = now % 100; let red = Math.sqrt(width / 100) * 255; let hex = Math.floor(red).toString(16); ctx.fillStyle = `#${hex}ffaa`; ctx.fillRect(0, 0, width, 2); } // Example of callback for image onLoad/onError function imageLoaded() { console.log("Image is loaded!"); } function imageError(error) { console.log(error.name); console.log(error.message); } class App extends Component { constructor(props) { super(props); this._onMuteToggled = this._onMuteToggled.bind(this); this.state = { muted: false, }; } _onMuteToggled(toggled) { this.setState({ muted: toggled, }); } render() { // Uncomment here to watch the animated flex box example in action // return ( // // // // ); const muteBackgroundColor = this.state.muted ? "#66FDCF" : "hsla(162, 97%, 70%, 0)"; const muteTextColor = this.state.muted ? "#17191f" : "hsla(162, 97%, 70%, 1)"; const logo_url = "https://raw.githubusercontent.com/nick-thompson/react-juce/master/examples/GainPlugin/jsui/src/logo.png"; //const logo_png = require('./logo.png'); //const logo_svg = require('./logo.svg'); return ( MUTE ); } } const styles = { container: { width: "100%", height: "100%", backgroundColor: "linear-gradient(45deg, hsla(225, 15%, 11%, 0.3), #17191f 50%)", justifyContent: "center", alignItems: "center", }, content: { flex: 1.0, flexDirection: "column", justifyContent: "space-around", alignItems: "center", padding: 24.0, maxWidth: 600, aspectRatio: 400.0 / 240.0, }, logo: { flex: 0.0, width: "80%", aspectRatio: 281.6 / 35.0, placement: Image.PlacementFlags.centred, }, meter: { flex: 0.0, width: 100.0, height: 16.0, }, canvas: { flex: 0.0, width: 100.0, height: 2, marginTop: 10, }, mute_button: { justifyContent: "center", alignItems: "center", borderRadius: 5.0, borderWidth: 2.0, borderColor: "rgba(102, 253, 207, 1)", marginTop: 10, minWidth: 30.0, minHeight: 30.0, width: "20%", height: "17.5%", }, mute_button_text: { fontSize: 20.0, lineSpacing: 1.6, fontStyle: Text.FontStyleFlags.bold, }, }; export default App; ================================================ FILE: examples/GainPlugin/jsui/src/Knob.js ================================================ import React from "react"; import ParameterSlider from "./ParameterSlider"; import { Slider } from "react-juce"; import Label from "./Label"; import { useParameter } from "./ParameterValueContext"; import { View } from "react-juce"; const sliderFillColor = "#66FDCF"; const sliderTrackColor = "#626262"; const drawRotary = Slider.drawRotary(sliderTrackColor, sliderFillColor); const Knob = ({ paramId }) => { const { stringValue, currentValue } = useParameter(paramId); return ( ); }; const styles = { container: { minWidth: 100.0, minHeight: 100.0, width: "55%", height: "55%", marginTop: 15, marginBottom: 15, justifyContent: "center", alignItems: "center", }, slider: { width: "100%", height: "100%", }, label: { width: "100%", height: "100%", flex: 1.0, justifyContent: "center", alignItems: "center", interceptClickEvents: false, position: "absolute", }, }; export default Knob; ================================================ FILE: examples/GainPlugin/jsui/src/Label.js ================================================ import React, { memo } from "react"; import { Text, View } from "react-juce"; const Label = ({ value, ...props }) => { return ( {value} ); }; const styles = { labelText: { color: "#626262", fontSize: 16.0, lineSpacing: 1.6, }, }; export default memo(Label); ================================================ FILE: examples/GainPlugin/jsui/src/Meter.js ================================================ import React, { Component } from "react"; import { EventBridge, Image, Text, View } from "react-juce"; class Meter extends Component { constructor(props) { super(props); this._onMeasure = this._onMeasure.bind(this); this._onMeterValues = this._onMeterValues.bind(this); this.state = { width: 0, height: 0, lcPeak: 0.0, rcPeak: 0.0, }; } componentDidMount() { EventBridge.addListener("gainPeakValues", this._onMeterValues); } componentWillUnmount() { EventBridge.removeListener("gainPeakValues", this._onMeterValues); } _onMeterValues(lcPeak, rcPeak) { this.setState({ lcPeak, rcPeak, }); } _onMeasure(e) { this.setState({ width: e.width, height: e.height, }); } _renderVectorGraphics(lcPeak, rcPeak, width, height) { // Similar to the audio side of this, this is a pretty rudimentary // way of drawing a gain meter; we'd get a much nicer response by using // a peak envelope follower with instant attack and a smooth release for // each channel, but this is just a demo plugin. return ` `; } render() { const { lcPeak, rcPeak, width, height } = this.state; return ( ); } } const styles = { canvas: { flex: 1.0, height: "100%", width: "100%", position: "absolute", left: 0.0, top: 0.0, interceptClickEvents: false, }, }; export default Meter; ================================================ FILE: examples/GainPlugin/jsui/src/ParameterSlider.js ================================================ import React, { memo, useCallback } from "react"; import { Slider } from "react-juce"; import { beginParameterChangeGesture, endParameterChangeGesture, setParameterValueNotifyingHost, } from "./nativeMethods"; const ParameterSlider = ({ value, paramId, children, ...props }) => { const onMouseDown = (e) => { beginParameterChangeGesture(paramId); }; const onMouseUp = (e) => { endParameterChangeGesture(paramId); }; const onSliderValueChange = (value) => { setParameterValueNotifyingHost(paramId, value); }; return ( {children} ); }; // TODO: PropTypes Validation // paramId should be required and has type of string // https://www.npmjs.com/package/prop-types export default memo(ParameterSlider); ================================================ FILE: examples/GainPlugin/jsui/src/ParameterToggleButton.js ================================================ import ParameterValueStore from "./ParameterValueStore"; import React, { Component } from "react"; import { Button } from "react-juce"; import { beginParameterChangeGesture, endParameterChangeGesture, setParameterValueNotifyingHost, } from "./nativeMethods"; class ParameterToggleButton extends Component { constructor(props) { super(props); this._handleClick = this._handleClick.bind(this); this._handleEnter = this._handleEnter.bind(this); this._handleLeave = this._handleLeave.bind(this); this._onParameterValueChange = this._onParameterValueChange.bind(this); const paramState = ParameterValueStore.getParameterState( this.props.paramId ); const initialDefaultValue = typeof paramState.defaultValue === "number" ? paramState.defaultValue : 0.0; const initialValue = typeof paramState.currentValue === "number" ? paramState.currentValue : 0.0; this.defaultBorderColor = "#66FDCF"; this.hoverBorderColor = "#66CFFD"; this.state = { defaultValue: initialDefaultValue, value: initialValue, borderColor: this.defaultBorderColor, }; if (typeof this.props.onToggled === "function") { this.props.onToggled(initialValue !== 0.0); } } componentDidMount() { ParameterValueStore.addListener( ParameterValueStore.CHANGE_EVENT, this._onParameterValueChange ); } componentWillUnmount() { ParameterValueStore.removeListener( ParameterValueStore.CHANGE_EVENT, this._onParameterValueChange ); } _handleClick(e) { const newValue = this.state.value === 0.0 ? 1.0 : 0.0; this.setState({ value: newValue, }); if ( typeof this.props.paramId === "string" && this.props.paramId.length > 0 ) { setParameterValueNotifyingHost(this.props.paramId, newValue); } if (typeof this.props.onToggled === "function") { this.props.onToggled(newValue !== 0.0); } } _handleEnter(e) { beginParameterChangeGesture(this.props.paramId); this.setState({ borderColor: this.hoverBorderColor, }); } _handleLeave(e) { endParameterChangeGesture(this.props.paramId); this.setState({ borderColor: this.defaultBorderColor, }); } _onParameterValueChange(paramId) { const shouldUpdate = typeof this.props.paramId === "string" && this.props.paramId.length > 0 && this.props.paramId === paramId; if (shouldUpdate) { const state = ParameterValueStore.getParameterState(paramId); const newDefaultValue = state.defaultValue; const newValue = state.currentValue; this.setState({ defaultValue: newDefaultValue, value: newValue, }); if (typeof this.props.onToggled === "function") { this.props.onToggled(newValue !== 0.0); } } } render() { const { parameterId, onToggled, ...other } = this.props; return ( ); } } export default ParameterToggleButton; ================================================ FILE: examples/GainPlugin/jsui/src/ParameterValueContext.js ================================================ import React, { createContext, useContext, useState, useCallback, useEffect, } from "react"; import { EventBridge } from "react-juce"; export const ParamIds = { MainGain: "MainGain", MainMute: "MainMute", }; const defaultValues = { MainGain: { defaultValue: 0.0, currentValue: 0.0, stringValue: "loading", }, MainMute: { defaultValue: false, currentValue: false, stringValue: "loading", }, }; export const ParameterValueContext = createContext(defaultValues); export const useParameter = (paramId) => { return useContext(ParameterValueContext)[paramId]; }; export const ParameterValueProvider = ({ children }) => { const [params, setParams] = useState(defaultValues); const onParameterValueChange = useCallback( (index, changedParamId, defaultValue, currentValue, stringValue) => { if (!ParamIds[changedParamId]) return; setParams((prevParams) => ({ ...prevParams, [changedParamId]: { index, defaultValue, currentValue, stringValue, }, })); }, [] ); useEffect(() => { EventBridge.addListener("parameterValueChange", onParameterValueChange); return () => { EventBridge.removeListener( "parameterValueChange", onParameterValueChange ); }; }, [onParameterValueChange]); return ( {children} ); }; ================================================ FILE: examples/GainPlugin/jsui/src/ParameterValueStore.js ================================================ import EventEmitter from "events"; import { EventBridge } from "react-juce"; /** This is more or less a proxy to the EventBridge's parameter events that * caches last known values and provides components a way to access the * cache. */ class ParameterValueStore extends EventEmitter { constructor() { super(); this.CHANGE_EVENT = "change"; this.setMaxListeners(100); this._onParameterValueChange = this._onParameterValueChange.bind(this); EventBridge.addListener( "parameterValueChange", this._onParameterValueChange ); this.state = {}; } getParameterState(paramId) { if (!this.state.hasOwnProperty(paramId)) { return {}; } return this.state[paramId]; } _onParameterValueChange( index, paramId, defaultValue, currentValue, stringValue ) { this.state[paramId] = { parameterIndex: index, parameterId: paramId, defaultValue: defaultValue, currentValue: currentValue, stringValue: stringValue, }; this.emit(this.CHANGE_EVENT, paramId); } } const __singletonInstance = new ParameterValueStore(); export default __singletonInstance; ================================================ FILE: examples/GainPlugin/jsui/src/index.js ================================================ import React from "react"; import ReactJUCE from "react-juce"; import App from "./App"; import { ParameterValueProvider } from "./ParameterValueContext"; ReactJUCE.render( , ReactJUCE.getRootContainer() ); ================================================ FILE: examples/GainPlugin/jsui/src/nativeMethods.js ================================================ export const beginParameterChangeGesture = (paramId) => global.beginParameterChangeGesture(paramId); export const endParameterChangeGesture = (paramId) => global.endParameterChangeGesture(paramId); export const setParameterValueNotifyingHost = (paramId, value) => global.setParameterValueNotifyingHost(paramId, value); ================================================ FILE: examples/GainPlugin/jsui/webpack.config.js ================================================ const webpack = require("webpack"); module.exports = { entry: "./src/index.js", output: { path: __dirname + "/build/js", filename: "main.js", sourceMapFilename: "[file].map", devtoolModuleFilenameTemplate: (info) => `webpack:///${info.absoluteResourcePath.replace(/\\/g, "/")}`, }, // Required for duktape to ensure webpack chunk do not contain arrow functions target: ["web", "es5"], devtool: "source-map", module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: ["babel-loader"], }, { test: /\.svg$/, exclude: /node_modules/, use: ["svg-inline-loader"], }, { test: /\.(png|jpeg|jpg|gif)$/, use: [ { loader: "url-loader", options: { limit: true, esModule: false, }, }, ], }, ], }, plugins: [ new webpack.DefinePlugin({ "process.env.NODE_DEBUG": JSON.stringify(process.env.NODE_DEBUG), }), ], }; ================================================ FILE: package.json ================================================ { "name": "blueprint", "version": "0.1.0", "description": "Write cross-platform native apps with React and JUCE", "directories": { "example": "examples" }, "repository": { "type": "git", "url": "git+https://github.com/nick-thompson/react-juce.git" }, "devDependencies": { "husky": "^4.3.8", "lint-staged": "^10.5.3", "prettier": "2.2.1" }, "scripts": { "beautify": "prettier --write .", "lint": "prettier --check ." }, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.{ts,tsx,js,jsx,css,md,yml,yaml}": "prettier --write" } } ================================================ FILE: packages/react-juce/.gitignore ================================================ dist ================================================ FILE: packages/react-juce/babel.config.js ================================================ module.exports = { presets: [ "@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript", ], plugins: [ "@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-object-rest-spread", [ "@babel/plugin-transform-runtime", { absoluteRuntime: false, corejs: 3, version: "^7.11.2", }, ], ], }; ================================================ FILE: packages/react-juce/package.json ================================================ { "name": "react-juce", "version": "0.2.16", "description": "Write cross-platform native apps with React.js and JUCE.", "repository": { "type": "git", "url": "https://github.com/nick-thompson/react-juce.git" }, "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist/index.js", "dist/index.d.ts", "dist/lib", "dist/components", "dist/src" ], "author": "Nick Thompson", "devDependencies": { "@babel/cli": "^7.10.0", "@babel/core": "^7.11.1", "@babel/plugin-proposal-class-properties": "^7.11.0", "@babel/plugin-transform-runtime": "^7.11.0", "@babel/preset-env": "^7.11.0", "@babel/preset-react": "^7.10.4", "@babel/preset-typescript": "^7.10.4", "@types/core-js": "^2.5.4", "@types/invariant": "^2.2.33", "@types/node": "^14.6.4", "@types/react": "^16.9.41", "@types/react-reconciler": "^0.18.0", "awesome-typescript-loader": "^5.2.1", "chalk": "^2.4.2", "fs-extra": "^8.1.0", "typescript": "^3.9.6", "webpack": "^5.46.0", "webpack-cli": "^4.7.2" }, "dependencies": { "@babel/runtime-corejs3": "^7.11.2", "camelcase": "^6.2.0", "color-string": "^1.5.4", "core-js": "2.6.0", "invariant": "^2.2.4", "known-css-properties": "^0.20.0", "matrix-js": "^1.5.1", "object-inspect": "^1.11.0", "react-reconciler": "^0.25.1" }, "peerDependencies": { "react": "^16.13.1" }, "scripts": { "build": "webpack --mode=production", "watch": "webpack --watch --mode=development", "init": "node scripts/init.js" } } ================================================ FILE: packages/react-juce/scripts/init.js ================================================ #!/usr/bin/env node var assert = require("assert"); var chalk = require("chalk"); var cp = require("child_process"); var fs = require("fs-extra"); var path = require("path"); var args = process.argv.slice(2); assert( typeof args[0] === "string" && args[0].length > 0, "Must provide a path to the directory in which to initialize the template." ); var targetDir = path.resolve(args[0]); var packageDir = path.resolve(__dirname, ".."); var templateDir = path.resolve(packageDir, "template"); console.log("Initializing a React-JUCE template in:", chalk.green(targetDir)); console.log("Directory tree will be created if it does not exist."); fs.mkdirp(targetDir, function (err) { if (err) { console.error(chalk.red(err)); process.exit(1); } console.log("[*] Copying template files"); fs.copy(templateDir, targetDir, function (err) { if (err) { console.error(chalk.red(err)); process.exit(1); } console.log("[*] Installing dependencies"); cp.exec("npm install", { cwd: targetDir }, function (err, stdout, stderr) { if (err) { console.error(chalk.red(err)); console.error(stderr); process.exit(1); } console.log(); console.log(` ${chalk.blue( "Success!" )} Initialized a React-JUCE template in ${chalk.green(targetDir)} You can now get started by typing: ${chalk.blue("cd")} ${args[0]} ${chalk.blue("npm start")} Then adding the reactjuce::ReactApplicationRoot component to your project. `); }); }); }); ================================================ FILE: packages/react-juce/src/components/Button.tsx ================================================ import React, { Component } from "react"; import { View } from "./View"; import { SyntheticMouseEvent } from "../lib/SyntheticEvents"; import { ViewInstance } from "../lib/Backend"; //TODO: Once ViewProps work is complete we can probably // remove this in favour of ViewProps. export interface ButtonProps { onClick: (e: SyntheticMouseEvent) => void; onMouseDown?: (e: SyntheticMouseEvent) => void; onMouseUp?: (e: SyntheticMouseEvent) => void; onMouseEnter?: (e: SyntheticMouseEvent) => void; onMouseLeave?: (e: SyntheticMouseEvent) => void; } type ButtonState = { down: boolean; }; /** * A simple Button component which can be used as a building block * for more complex button types. * * @example * * * * const styles = { * text: { * 'font-size': 18.0, * 'line-spacing': 1.6, * 'color': 'ff626262' * }, * button: { * 'justify-content': 'center', * 'align-items': 'center', * 'width': '100%', * 'height': '100%', * 'border-radius': 5.0, * 'border-width': 2.0, * 'border-color': 'ff626262' * } * }; * */ export class Button extends Component { private _ref: React.RefObject; constructor(props: ButtonProps) { super(props); this._ref = React.createRef(); this.state = { down: false, }; } handleDown = (e: SyntheticMouseEvent): void => { if (typeof this.props.onMouseDown === "function") this.props.onMouseDown.call(null, e); this.setState({ down: true, }); }; handleUp = (e: SyntheticMouseEvent): void => { if (typeof this.props.onMouseUp === "function") this.props.onMouseUp.call(null, e); this.setState({ down: false, }); if (typeof this.props.onClick === "function") { const instance = this._ref ? this._ref.current : null; if (instance && instance.contains(e.relatedTarget)) { this.props.onClick(e); } } }; render = () => { const { onMouseDown, onMouseUp, onClick, ...other } = this.props; const opacity = this.state.down ? 0.8 : 1.0; return ( {this.props.children} ); }; } ================================================ FILE: packages/react-juce/src/components/Canvas.ts ================================================ import React, { Component } from "react"; import Colors from "../lib/MacroProperties/Colors"; // TODO: Need to explicitly bind this to members? export class CanvasRenderingContext { private _drawCommands: any[]; constructor() { this._drawCommands = []; } reset() { this._drawCommands = []; } getDrawCommands(): any[] { return this._drawCommands; } //================================================================================ // Properties // TODO: Support fillStyle/strokeStyle pattern. // TODO: Support fillStyle/strokeStyle gradient. set fillStyle(value: string) { value = Colors.colorStringToAlphaHex(value); this._drawCommands.push(["setFillStyle", value]); } set strokeStyle(value: string) { value = Colors.colorStringToAlphaHex(value); this._drawCommands.push(["setStrokeStyle", value]); } set lineWidth(value: number) { this._drawCommands.push(["setLineWidth", value]); } set font(value: string) { this._drawCommands.push(["setFont", value]); } set textAlign(value: string) { this._drawCommands.push(["setTextAlign", value]); } //================================================================================ // Rect functions fillRect(x: number, y: number, width: number, height: number): void { this._drawCommands.push(["fillRect", x, y, width, height]); } strokeRect(x: number, y: number, width: number, height: number): void { this._drawCommands.push(["strokeRect", x, y, width, height]); } strokeRoundedRect( x: number, y: number, width: number, height: number, cornerSize: number ): void { this._drawCommands.push([ "strokeRoundedRect", x, y, width, height, cornerSize, ]); } fillRoundedRect( x: number, y: number, width: number, height: number, cornerSize: number ): void { this._drawCommands.push([ "fillRoundedRect", x, y, width, height, cornerSize, ]); } clearRect(x: number, y: number, width: number, height: number): void { this._drawCommands.push(["clearRect", x, y, width, height]); } //================================================================================ // Path functions // // TODO: Should we split things out into CanvasRenderingContextPath // so things like close()/stroke() are more obvious when looking at API. // If you look at the ts type definitions for CanvasRenderingContext2D // CanvasRenderingContext2D is composed of other objects like CanvasRenderingContextPath // which contains all path methods. What is the best way to do this in JS and share // a drawCommands instance? beginPath(): void { this._drawCommands.push(["beginPath"]); } lineTo(x: number, y: number): void { this._drawCommands.push(["lineTo", x, y]); } moveTo(x: number, y: number): void { this._drawCommands.push(["moveTo", x, y]); } arc( x: number, y: number, radius: number, startAngle: number, endAngle: number ): void { //TODO: Add support for optional antiClockWise?: boolean arg this._drawCommands.push(["arc", x, y, radius, startAngle, endAngle]); } quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void { this._drawCommands.push(["quadraticCurveTo", cpx, cpy, x, y]); } closePath(): void { this._drawCommands.push(["close"]); } stroke(): void { this._drawCommands.push(["stroke"]); } fill(): void { this._drawCommands.push(["fill"]); } //================================================================================ // Transform functions rotate(angle: number): void { this._drawCommands.push(["rotate", angle]); } translate(x: number, y: number): void { this._drawCommands.push(["translate", x, y]); } setTransform( a: number, b: number, c: number, d: number, e: number, f: number ): void { this._drawCommands.push(["setTransform", a, b, c, d, e, f]); } resetTransform(): void { this._drawCommands.push(["resetTransform"]); } //================================================================================ // Image functions // //TODO: Add support for other drawImage overloads. // Currently only support SVG string. What is correct // type to use here? drawImage(image: string, dx: number, dy: number): void { this._drawCommands.push(["drawImage", image, dx, dy]); } //================================================================================ // Text functions strokeText(text: string, x: number, y: number, maxWidth?: number): void { if (maxWidth === undefined) this._drawCommands.push(["strokeText", text, x, y]); else this._drawCommands.push(["strokeText", text, x, y, maxWidth]); } fillText(text: string, x: number, y: number, maxWidth?: number): void { if (maxWidth === undefined) this._drawCommands.push(["fillText", text, x, y]); else this._drawCommands.push(["fillText", text, x, y, maxWidth]); } //================================================================================ } export interface CanvasProps { onDraw: (ctx: CanvasRenderingContext) => void; onMeasure?: (e: any) => void; stateful?: boolean; } interface CanvasState { width: number; height: number; } export class Canvas extends Component { private _ctx: CanvasRenderingContext; constructor(props: CanvasProps) { super(props); this._ctx = new CanvasRenderingContext(); this._onMeasure = this._onMeasure.bind(this); this._onDraw = this._onDraw.bind(this); this.state = { width: 0, height: 0, }; } _onMeasure(e: any) { this.setState({ width: e.width, height: e.height, }); if (typeof this.props.onMeasure === "function") { this.props.onMeasure(e); } } _onDraw(): any[] { if (typeof this.props.onDraw === "function") { this._ctx.reset(); this.props.onDraw(this._ctx); return this._ctx.getDrawCommands(); } return []; } render() { return React.createElement( "CanvasView", Object.assign({}, this.props, { onDraw: () => { return this._onDraw(); }, onMeasure: (e: any) => { this._onMeasure(e); }, }), this.props.children ); } } ================================================ FILE: packages/react-juce/src/components/Image.ts ================================================ import React from "react"; export function Image(props: any) { return React.createElement("Image", props, props.children); } Image.PlacementFlags = { xLeft: 1, xRight: 2, xMid: 4, yTop: 8, yBottom: 16, yMid: 32, stretchToFit: 64, fillDestination: 128, onlyReduceInSize: 256, onlyIncreaseInSize: 512, doNotResize: 256 | 512, centred: 4 + 32, }; ================================================ FILE: packages/react-juce/src/components/ListView.tsx ================================================ import React, { Component, PropsWithChildren, ReactElement } from "react"; import { ViewInstance } from "../lib/Backend"; import { ScrollView, ScrollEvent } from "./ScrollView"; import invariant from "invariant"; type ListViewState = { scrollTopPosition: number; width: number; height: number; }; type VirtualPositions = { innerHeight: number; startIndex: number; endIndex: number; }; /** * ScrollParams is passed to ListView's scrollToIndex * function. * * @property index - The index of the item in the ListView's data * source to scroll to. * * @property offset - A normalised value between 0.0 and 1.0 which * will offset the position of the ListView item * (specified by index) from the top of the ListView. * i.e. offset == 0.5 positions the item at index in * the center of the ListView. */ export interface ScrollParams { index: number; offset: number; } /** * Prop type for ListView. * * @property data - Array of objects to use when populating ListView items * via the renderItem callback. * * @property renderItem - Callback to render a ListView item given an element * from the supplied data source. * * @property itemHeight - Fixed height in pixels for a single item in the * ListView. */ export interface ListViewProps { data: any[]; renderItem: (any) => ReactElement; itemHeight: number; } /** * A lightweight "virtualised list" implementation that allows for * the efficient rendering of a large number of objects within a * ScrollView. ListView supports the full set of ScrollViewProps * for styling etc. * * Note, at present ListView requires the user to specify a fixed * itemHeight property in order to calculate which items to render * within the list based on scroll position. This may change in * future versions. * * @example * * function getListViewItems() { * let items = []; * * for (let i = 0; i < 5000; ++i) { * const name = "Item " + i; * const category = i % 2 ? "A" : "B"; * * items.push({id: i, name: name, category: category}); * } * * return items; * } * * } * itemHeight={50} * /> * */ export class ListView extends Component< PropsWithChildren, ListViewState > { private _ref: React.RefObject; constructor(props: PropsWithChildren) { super(props); this._ref = React.createRef(); this._onMeasure = this._onMeasure.bind(this); this._calculateVirtualPositions = this._calculateVirtualPositions.bind( this ); this._setScrollTopPosition = this._setScrollTopPosition.bind(this); this.scrollToIndex = this.scrollToIndex.bind(this); this.state = { scrollTopPosition: 0, width: 0, height: 0, }; } _onMeasure(e: any): void { this.setState({ width: e.width, height: e.height, }); } _calculateVirtualPositions(): VirtualPositions { const totalItems = this.props.data.length; const innerHeight = this.props.itemHeight * totalItems; invariant( this.props.itemHeight > 0, "Zero or negative itemHeight passed to ListView" ); // Pad num rendered items by 1 so we always render the same number of items // which allows us to use a fixed key value on each item in the list. const numItems = Math.floor(this.state.height / this.props.itemHeight) + 1; const startIndex = Math.floor( this.state.scrollTopPosition / this.props.itemHeight ); const endIndex = Math.min(totalItems - 1, startIndex + numItems); return { innerHeight: innerHeight, startIndex: startIndex, endIndex: endIndex, }; } _setScrollTopPosition(e: ScrollEvent): void { this.setState({ scrollTopPosition: e.scrollTop, }); if (typeof this.props.onScroll === "function") { this.props.onScroll(e); } } scrollToIndex(scrollParams: ScrollParams) { invariant( scrollParams.index >= 0 && scrollParams.index <= this.props.data.length, "scrollParams.index must be between 0 and props.data.length" ); invariant( scrollParams.offset >= 0.0 && scrollParams.offset <= 1.0, "scrollParams.offset must be normalised between 0.0 and 1.0" ); const numItems = Math.floor(this.state.height / this.props.itemHeight); const xPos = 0; let yPos = this.props.itemHeight * scrollParams.index - this.props.itemHeight * numItems * scrollParams.offset; const scrollViewInstance = this._ref ? this._ref.current : null; if (scrollViewInstance) { //@ts-ignore scrollViewInstance.scrollToPosition(xPos, yPos); } } render() { const positions: VirtualPositions = this._calculateVirtualPositions(); const items: any = []; // List items must have a key but we ensure the key remains fixed here // so that we simply re-render the existing views/components rather than // replacing them. This stops issues occuring when dynamically adding and removing // components inside a juce::Viewport. if (this.state.height > 0) { for (let i = positions.startIndex; i <= positions.endIndex; ++i) { items.push( this.props.renderItem(this.props.data[i], i, { position: "absolute", top: this.props.itemHeight * i, key: positions.endIndex - i, }) ); } } return ( {items} ); } } const styles = { scrollViewContent: { flexDirection: "column", flex: 1.0, flexShrink: 0.0, }, }; ================================================ FILE: packages/react-juce/src/components/ScrollView.ts ================================================ import React, { PropsWithChildren } from "react"; import invariant from "invariant"; function ScrollViewContentView(props: any) { return React.createElement("ScrollViewContentView", props, props.children); } function parseScrollbarColorProp(scrollbarColorProp: string) { const values = scrollbarColorProp.split(" "); invariant( values.length === 2, 'scrollbar-color should be a space separated string with two values: "{thumbColor} {trackColor}".' ); return { "scrollbar-thumb-color": values[0], "scrollbar-track-color": values[1], }; } function parseScrollbarWidthProp(scrollBarWidthProp: string | number) { invariant( typeof scrollBarWidthProp === "string" || typeof scrollBarWidthProp === "number", "scrollbar-width should be a string or a number." ); let props = {}; if (typeof scrollBarWidthProp === "string") { switch (scrollBarWidthProp) { // JUCE's look and feel V2 uses 18px as default scrollbar thickness. case "auto": props["scrollbar-width"] = 18; return props; case "thin": props["scrollbar-width"] = 9; return props; case "none": props["overflow-x"] = "hidden"; props["overflow-y"] = "hidden"; props["scroll-on-drag"] = true; return props; default: invariant( false, "scrollbar-width as string should be one of 'auto', 'thin' or 'none'" ); } } props["scrollbar-width"] = scrollBarWidthProp; return props; } function parseOverflowProp( overflowProp: string | undefined, overflowXProp: string | undefined, overflowYProp: string | undefined ) { let props = {}; props["overflow-x"] = overflowXProp || overflowProp; props["overflow-y"] = overflowYProp || overflowProp; return props; } export interface ScrollEvent { scrollTop: number; scrollLeft: number; } export interface ScrollViewProps { overflow?: string; "overflow-x"?: string; "overflow-y"?: string; "scrollbar-color"?: string; "scrollbar-width"?: string | number; "scroll-on-drag"?: boolean; onScroll: (e: ScrollEvent) => void; } //TODO: Remove any once ViewProps typed export function ScrollView(props: PropsWithChildren) { const child = React.Children.only(props.children); invariant( child && child["type"] === ScrollViewContentView, "ScrollView must have only one child, and that child must be a ScrollView.ContentView." ); // Unpack non-native props let { overflow, "scrollbar-color": scrollbarColor, "scrollbar-width": scrollBarWidth, ...other } = props; if (typeof scrollbarColor !== "undefined") Object.assign(other, parseScrollbarColorProp(scrollbarColor)); if (typeof scrollBarWidth !== "undefined") Object.assign(other, parseScrollbarWidthProp(scrollBarWidth)); if (typeof overflow !== "undefined") Object.assign( other, parseOverflowProp(overflow, other["overflow-x"], other["overflow-y"]) ); return React.createElement("ScrollView", other, child); } ScrollView.ContentView = ScrollViewContentView; ================================================ FILE: packages/react-juce/src/components/Slider.tsx ================================================ import React, { Component, PropsWithChildren } from "react"; import invariant from "invariant"; import { Canvas, CanvasRenderingContext } from "./Canvas"; import { View } from "./View"; import { SyntheticMouseEvent } from "../lib/SyntheticEvents"; // Some simple helpers for slider drag gesture -> value mapping const _linearHorizontalGestureMap = ( mouseDownX: number, mouseDownY: number, sensitivity: number, valueAtDragStart: number, dragEvent: SyntheticMouseEvent ): number => { const dx = dragEvent.x - mouseDownX; return Math.max(0.0, Math.min(1.0, valueAtDragStart + dx * sensitivity)); }; const _linearVerticalGestureMap = ( mouseDownX: number, mouseDownY: number, sensitivity: number, valueAtDragStart: number, dragEvent: SyntheticMouseEvent ): number => { const dy = dragEvent.y - mouseDownY; return Math.max(0.0, Math.min(1.0, valueAtDragStart - dy * sensitivity)); }; const _rotaryGestureMap = ( mouseDownX: number, mouseDownY: number, sensitivity: number, valueAtDragStart: number, dragEvent: SyntheticMouseEvent ): number => { const dx = dragEvent.x - mouseDownX; const dy = mouseDownY - dragEvent.y; return Math.max( 0.0, Math.min(1.0, valueAtDragStart + (dx + dy) * sensitivity) ); }; const _drawLinearHorizontalSlider = (trackColor: string, fillColor: string) => { return ( ctx: CanvasRenderingContext, width: number, height: number, value: number ): void => { const lineWidth = 2.0; const x = 0 + lineWidth / 2; const y = 0 + lineWidth / 2; width = width - lineWidth; height = height - lineWidth; const fillWidth = value * width; ctx.lineWidth = lineWidth; ctx.strokeStyle = trackColor; ctx.fillStyle = fillColor; ctx.fillRect(x, y, fillWidth, height); ctx.strokeRect(x, y, width, height); }; }; const _drawLinearVerticalSlider = (trackColor: string, fillColor: string) => { return ( ctx: CanvasRenderingContext, width: number, height: number, value: number ): void => { const lineWidth = 2.0; const x = 0 + lineWidth / 2; const y = 0 + lineWidth / 2; width = width - lineWidth; height = height - lineWidth; const fillHeight = value * height; const fillY = y + (height - fillHeight); ctx.lineWidth = lineWidth; ctx.strokeStyle = trackColor; ctx.fillStyle = fillColor; ctx.fillRect(x, fillY, width, fillHeight); ctx.strokeRect(x, y, width, height); }; }; function _drawArc( ctx, centerX, centerY, radius, arcSize, startAngle, endAngle, lineWidth ) { const deltaX = centerX; const deltaY = centerY; const rotateAngle = ((180 + 180 * (1 - arcSize)) * Math.PI) / 180; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.moveTo(centerX - lineWidth / 2, 0); ctx.arc( centerX - lineWidth / 2, centerY - lineWidth / 2, radius, startAngle, endAngle ); ctx.translate(deltaX, deltaY); ctx.rotate(rotateAngle); ctx.translate(-deltaX, -deltaY); ctx.stroke(); ctx.resetTransform(); } const _drawRotarySlider = (trackColor, fillColor) => { return (ctx, width, height, value) => { const lineWidth = 3; const arcSize = 0.8; const radius = Math.min(width, height) * 0.5 - lineWidth / 2; const centerX = width / 2; const centerY = height / 2; const strokeStart = 0; const strokeEnd = 2 * Math.PI * arcSize; const fillStart = -(2.5 * Math.PI * arcSize); const fillEnd = fillStart + value * (2 * Math.PI * arcSize); ctx.strokeStyle = trackColor; _drawArc( ctx, centerX, centerY, radius, arcSize, strokeStart, strokeEnd, lineWidth ); ctx.strokeStyle = fillColor; _drawArc( ctx, centerX, centerY, radius, arcSize, fillStart, fillEnd, lineWidth ); }; }; export interface SliderProps { value?: number; sensitivity?: number; onChange?: (value: number) => void; onDraw?: ( ctx: CanvasRenderingContext, width: number, height: number, value: number ) => void; mapDragGestureToValue?: ( mouseDownX: number, mouseDownY: number, sensitivity: number, valueAtDragStart: number, dragEvent: SyntheticMouseEvent ) => number; } type SliderState = { width: number; height: number; value: number; }; /** * A generic slider component which can be used as a building block for more complex * sliders. * * Slider takes an onDraw function prop for rendering the Slider using a CanvasRenderingContext. * Slider also accepts a mapDragGestureToValue pop which allows customisation when converting mouse drag * positions/events to the Slider's normalised value reported via the onChange prop callback. * * Some default onDraw and mapDragGestureToValue implementations are provided: * @see Slider.drawLinearHorizontal * @see Slider.drawLinearVertical * @see Slider.drawRotary * * @see Slider.linearHorizontalGestureMap * @see Slider.linearVerticalGestureMap * @see Slider.rotaryGestureMap * * @example * * console.log("Slider value changed: " + value)} * onDraw={Slider.drawRotary('ff66FDCF', 'ff626262')} * mapDragGestureToValue={Slider.rotaryGestureMap} * /> * */ export class Slider extends Component< PropsWithChildren, SliderState > { static linearHorizontalGestureMap = _linearHorizontalGestureMap; static linearVerticalGestureMap = _linearVerticalGestureMap; static rotaryGestureMap = _rotaryGestureMap; static drawLinearHorizontal = _drawLinearHorizontalSlider; static drawLinearVertical = _drawLinearVerticalSlider; static drawRotary = _drawRotarySlider; private _valueAtDragStart = 0.0; private _mouseDownX = 0; private _mouseDownY = 0; static defaultProps = { sensitivity: 1 / 200, onDraw: _drawRotarySlider("ff626262", "ff66FDCF"), mapDragGestureToValue: _rotaryGestureMap, }; constructor(props: PropsWithChildren) { super(props); this._onMeasure = this._onMeasure.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseDrag = this._onMouseDrag.bind(this); this._onDraw = this._onDraw.bind(this); this.state = { width: 0, height: 0, value: 0.0, }; } _onMeasure(e: any) { this.setState({ width: e.width, height: e.height, }); } _onMouseDown(e: SyntheticMouseEvent) { this._valueAtDragStart = this.props.hasOwnProperty("value") ? this.props.value : this.state.value; this._mouseDownX = e.x; this._mouseDownY = e.y; if (typeof this.props.onMouseDown === "function") { this.props.onMouseDown(e); } } _onMouseDrag(e: SyntheticMouseEvent) { let value = 0.0; if (typeof this.props.mapDragGestureToValue !== "function") { invariant(false, "Invalid gesture mapping function supplied."); return; } value = this.props.mapDragGestureToValue( this._mouseDownX, this._mouseDownY, this.props.sensitivity, this._valueAtDragStart, e ); if (!this.props.hasOwnProperty("value")) { this.setState({ value: value, }); } if (typeof this.props.onChange === "function") { this.props.onChange(value); } if (typeof this.props.onMouseDrag === "function") { this.props.onMouseDrag(e); } } _onDraw(ctx: CanvasRenderingContext) { const value = this.props.hasOwnProperty("value") ? this.props.value : this.state.value; if (typeof this.props.onDraw === "function") { return this.props.onDraw(ctx, this.state.width, this.state.height, value); } } render() { return ( {this.props.children} ); } } const styles = { canvas: { width: "100%", height: "100%", position: "absolute", interceptClickEvents: false, }, }; ================================================ FILE: packages/react-juce/src/components/Text.ts ================================================ import React from "react"; export function Text(props: any) { return React.createElement("Text", props, props.children); } Text.WordWrap = { none: 0, byWord: 1, byChar: 2, }; Text.FontStyleFlags = { plain: 0, bold: 1, italic: 2, underlined: 4, }; Text.JustificationFlags = { left: 1, right: 2, horizontallyCentred: 4, top: 8, bottom: 16, verticallyCentred: 32, horizontallyJustified: 64, centred: 36, centredLeft: 33, centredRight: 34, centredTop: 12, centredBottom: 20, topLeft: 9, topRight: 10, bottomLeft: 17, bottomRight: 18, }; ================================================ FILE: packages/react-juce/src/components/TextInput.tsx ================================================ import React, { PropsWithChildren } from "react"; export interface InputEvent { value: string; } export interface ChangeEvent { value: string; } export interface TextInputProps { value?: string; color?: string; fontSize?: number; fontStyle?: number; fontFamily?: string; justification?: number; "kerning-factor"?: number; placeholder?: string; "placeholder-color"?: string; maxlength?: number; readonly?: boolean; "outline-color"?: string; "focused-outline-color"?: string; "highlighted-text-color"?: string; "highlight-color"?: string; "caret-color"?: string; onChange?: (e: ChangeEvent) => void; onInput?: (e: InputEvent) => void; } export function TextInput(props: PropsWithChildren) { return React.createElement("TextInput", props, props.children); } ================================================ FILE: packages/react-juce/src/components/View.ts ================================================ import React from "react"; // We'll need to wrap the default native components in stuff like this so that // you can use in your JSX. Otherwise we need the dynamic friendliness // of the createElement call (note that the type is a string...); export function View(props: any) { return React.createElement("View", props, props.children); } View.ClickEventFlags = { disableClickEvents: 0, allowClickEvents: 1, allowClickEventsExcludingChildren: 2, allowClickEventsOnlyOnChildren: 3, }; View.EasingFunctions = { linear: 0, quadraticIn: 1, quadraticOut: 2, quadraticInOut: 3, }; ================================================ FILE: packages/react-juce/src/index.tsx ================================================ /** Polyfill ES2015 data structures with core-js. */ import "core-js/es6/set"; import "core-js/es6/map"; import Backend from "./lib/Backend"; import Renderer, { TracedRenderer } from "./lib/Renderer"; export { default as EventBridge } from "./lib/EventBridge"; export * from "./components/View"; export * from "./components/ScrollView"; export * from "./components/Canvas"; export * from "./components/Text"; export * from "./components/TextInput"; export * from "./components/Image"; export * from "./components/Button"; export * from "./components/Slider"; export * from "./components/ListView"; export * from "./lib/SyntheticEvents"; let __renderStarted = false; let __preferredRenderer = Renderer; export default { getRootContainer() { return Backend.getRootContainer(); }, render( element: any, container: any, callback?: () => void | null | undefined ) { console.log("Render started..."); // Create a root Container if it doesnt exist if (!container._rootContainer) { //TODO: Double check passing false for final param "hydrate correct" container._rootContainer = __preferredRenderer.createContainer( container, false, false ); } // Update the root Container return __preferredRenderer.updateContainer( element, container._rootContainer, null, // TODO: callback in __preferredRenderer.updateContainer is not optional. // @ts-ignore callback ); }, enableMethodTrace() { if (__renderStarted) { throw new Error("Cannot enable method trace after initial render."); } __preferredRenderer = TracedRenderer; }, }; ================================================ FILE: packages/react-juce/src/lib/Backend.ts ================================================ import { all as allCssProps } from "known-css-properties"; import camelCase from "camelcase"; import NativeMethods from "./NativeMethods"; import SyntheticEvents, { SyntheticMouseEvent, SyntheticKeyboardEvent, } from "./SyntheticEvents"; import { macroPropertyGetters } from "./MacroProperties"; import Colors from "./MacroProperties/Colors"; //TODO: Keep this union or introduce a common base class ViewInstanceBase? export type Instance = ViewInstance | RawTextViewInstance; let __rootViewInstance: ViewInstance | null = null; let __viewRegistry: Map = new Map(); let __lastMouseDownViewId: string | null = null; // get any css properties not beginning with a "-", // and build a map from any camelCase versions to // the hyphenated version const cssPropsMap = allCssProps .filter((s) => !s.startsWith("-") && s.includes("-")) .reduce((acc, v) => Object.assign(acc, { [camelCase(v)]: v }), {}); export class ViewInstance { private _id: string; private _type: string; public _children: Instance[]; public _props: any = null; public _parent: any = null; constructor(id: string, type: string, props?: any, parent?: ViewInstance) { this._id = id; this._type = type; this._children = []; this._props = props; this._parent = parent; //TODO: This has been added to resolve a bug in // our Button component when calling contains() // on a viewRef. This is a result of wrapping our // viewRefs in a Proxy object which means this is // no longer bound to the original ViewInstance object // during the contains call. Ideally we would use Reflect.get() // here but Duktape does not fully support Reflect at the moment. this.contains = this.contains.bind(this); } getViewId(): string { return this._id; } getType(): string { return this._type; } getChildIndex(childInstance: Instance): number { for (let i = 0; i < this._children.length; ++i) { if (this._children[i] === childInstance) { return i; } } return -1; } appendChild(childInstance: Instance): any { childInstance._parent = this; this._children.push(childInstance); //@ts-ignore return NativeMethods.insertChild(this._id, childInstance._id, -1); } insertChild(childInstance: Instance, index: number): any { childInstance._parent = this; this._children.splice(index, 0, childInstance); //@ts-ignore return NativeMethods.insertChild(this._id, childInstance._id, index); } removeChild(childInstance: Instance): any { const index = this._children.indexOf(childInstance); if (index >= 0) { this._children.splice(index, 1); __viewRegistry.delete(childInstance.getViewId()); //@ts-ignore return NativeMethods.removeChild(this._id, childInstance._id); } } setProperty(propKey: string, value: any): any { // if the supplied propkey is a camelCase equivalent // of a css prop, first convert it to kebab-case propKey = cssPropsMap[propKey] || propKey; // convert provided color string to alpha-hex code for JUCE let nativeValue; if (Colors.isColorProperty(propKey)) { value = Colors.colorStringToAlphaHex(value); if (value.startsWith("linear-gradient")) { nativeValue = Colors.convertLinearGradientStringToNativeObject(value); } } this._props = Object.assign({}, this._props, { [propKey]: value, }); // Our React Ref equivalent. This is needed // as it appears the 'ref' prop isn't passed through // to our renderer's setProperty from the reconciler. // We wrap the ViewInstance in a proxy object here to allow // invocation of native ViewInstance methods via React refs. // If a property is not present on the ViewInstance object // we assume the caller is attempting to access/invoke a // native View method. if (propKey === "viewRef") { value.current = new Proxy(this, { get: function (target, prop, receiver) { if (prop in target) { return target[prop]; } return function (...args) { //@ts-ignore return NativeMethods.invokeViewMethod(target._id, prop, ...args); }; }, }); return; } if (macroPropertyGetters.hasOwnProperty(propKey)) { //@ts-ignore for (const [k, v] of macroPropertyGetters[propKey](value)) NativeMethods.setViewProperty(this._id, k, v); return; } //@ts-ignore return NativeMethods.setViewProperty( this._id, propKey, nativeValue ? nativeValue : value ); } contains(node: Instance): boolean { if (node === this) { return true; } for (let i = 0; i < this._children.length; ++i) { const child = this._children[i]; // A ViewInstance may hold RawTextViewInstances but a // RawTextViewInstance contains no children. if (child instanceof ViewInstance && child.contains(node)) return true; } return false; } } export class RawTextViewInstance { private _id: string; private _text: string; public _parent: ViewInstance; constructor(id: string, text: string, parent: ViewInstance) { this._id = id; this._text = text; this._parent = parent; } getViewId(): string { return this._id; } getText() { return this._text; } setTextValue(text: string): any { this._text = text; //@ts-ignore return NativeMethods.setRawTextValue(this._id, text); } } function __getRootContainer(): ViewInstance { if (__rootViewInstance !== null) return __rootViewInstance; //@ts-ignore const id = NativeMethods.getRootInstanceId(); __rootViewInstance = new ViewInstance(id, "View"); return __rootViewInstance; } function __hasFunctionProp(view: ViewInstance, prop: string) { return ( view._props.hasOwnProperty(prop) && typeof view._props[prop] === "function" ); } function __callEventHandlerIfPresent( view: Instance, eventType: string, event: any ) { if (view instanceof ViewInstance && __hasFunctionProp(view, eventType)) { view._props[eventType](event); } } function __bubbleEvent(view: Instance, eventType: string, event: any): void { if (view && view !== __getRootContainer()) { // Always call the event callback on the target before bubbling. // Some events may not bubble or have bubble defined. i.e. onMeasure __callEventHandlerIfPresent(view, eventType, event); if (event.bubbles) __bubbleEvent(view._parent, eventType, event); } } //@ts-ignore NativeMethods.dispatchViewEvent = function dispatchEvent( viewId: string, eventType: string, event: any ) { if (__viewRegistry.hasOwnProperty(viewId)) { const instance = __viewRegistry[viewId]; // Convert target/relatedTarget to concrete ViewInstance refs if (event.target && __viewRegistry.hasOwnProperty(event.target)) { event.target = __viewRegistry[event.target]; } if ( event.relatedTarget && __viewRegistry.hasOwnProperty(event.relatedTarget) ) { event.relatedTarget = __viewRegistry[event.relatedTarget]; } // Convert native event object into it's SyntheticEvent equivalent if required. if (SyntheticEvents.isMouseEventHandler(eventType)) event = new SyntheticMouseEvent(event); else if (SyntheticEvents.isKeyboardEventHandler(eventType)) event = new SyntheticKeyboardEvent(event); // If mouseDown event we store the target viewId as the last view // to recieve a mouseDown for "onClick" book-keeping. if (eventType === "onMouseDown") { __lastMouseDownViewId = viewId; __bubbleEvent(instance, eventType, event); return; } if (eventType === "onMouseUp") { __bubbleEvent(instance, eventType, event); if (__lastMouseDownViewId && viewId === __lastMouseDownViewId) { __lastMouseDownViewId = null; __bubbleEvent(instance, "onClick", event); } return; } __bubbleEvent(instance, eventType, event); } }; export default { getRootContainer(): ViewInstance { return __getRootContainer(); }, createViewInstance( viewType: string, props: any, parentInstance: ViewInstance ): ViewInstance { //@ts-ignore const id = NativeMethods.createViewInstance(viewType); const instance = new ViewInstance(id, viewType, props, parentInstance); __viewRegistry[id] = instance; return instance; }, createTextViewInstance(text: string, parentInstance: ViewInstance) { //@ts-ignore const id = NativeMethods.createTextViewInstance(text); const instance = new RawTextViewInstance(id, text, parentInstance); __viewRegistry[id] = instance; return instance; }, resetAfterCommit() { //@ts-ignore return NativeMethods.resetAfterCommit(); }, }; ================================================ FILE: packages/react-juce/src/lib/EventBridge.ts ================================================ import EventEmitter from "events"; import NativeMethods from "./NativeMethods"; const EventBridge = new EventEmitter.EventEmitter(); EventBridge.setMaxListeners(30); // An internal hook for the native side, from which we propagate events through // the EventEmitter interface. // @ts-ignore NativeMethods.dispatchEvent = function dispatchEvent( eventType: string, ...args: any ): void { EventBridge.emit(eventType, ...args); }; export default EventBridge; ================================================ FILE: packages/react-juce/src/lib/MacroProperties/Colors.ts ================================================ import ColorString from "color-string"; import ColorNames from "color-name"; const COLOR_PROPERTIES = ["border-color", "background-color", "color"]; const isColorProperty = (propKey: string): boolean => { return COLOR_PROPERTIES.includes(propKey); }; const colorStringToAlphaHex = (colorString: string): string => { //From Hex if (colorString.startsWith("#") && colorString.length === 7) { return `ff${colorString}`.replace("#", ""); } //From RGB or RGBA else if (colorString.startsWith("rgb")) { const rgbValues = colorString .split("(")[1] .split(")")[0] .replace(/ /g, "") .split(","); const alphaValue = rgbValues.length === 4 ? //@ts-ignore percentToHex(100 * rgbValues.pop()) : "ff"; const hex = rgbValues.map((color) => { const hex = Number.parseInt(color).toString(16); return hex.length == 1 ? "0" + hex : hex; }); return alphaValue + hex.join(""); } //From HSL or HSLA else if (colorString.startsWith("hsl")) { const hslValues = colorString .split("(")[1] .split(")")[0] .replace(/ |[%]/g, "") .split(","); const alphaValue = hslValues.length === 4 ? //@ts-ignore percentToHex(100 * hslValues.pop()) : "ff"; const hex = hslToHex( Number.parseInt(hslValues[0]), Number.parseInt(hslValues[1]), Number.parseInt(hslValues[2]) ); return alphaValue + hex; } //From Linear Gradient else if (colorString.startsWith("linear-gradient")) { const lgValues = colorString .split(/\((.+)/)[1] .split(/\)$/)[0] .split(/,+(?![^\(]*\))/) .map((item) => { return item.split(" "); }); //concat any arrays that contain hsl or rba strings lgValues.forEach((itemArr, idx) => { if (itemArr.find((item) => item.includes("(")) !== undefined) { const itemArrClone = itemArr.slice(); const startIdx = itemArrClone.findIndex((item) => item.includes("(")); const endIdx = itemArrClone.findIndex((item) => item.includes(")")); const colorHSLRGB = itemArrClone.slice(startIdx, endIdx + 1).join(""); itemArr.splice(startIdx, endIdx + 1, colorHSLRGB); lgValues[idx] = itemArr; } else { lgValues[idx] = itemArr; } }); //flatten array const lgValuesCleaned: Array = [].concat // @ts-ignore .apply([], lgValues) .filter((item) => { return item != ""; }); //converts any colorStrings in linear gradient to alpha-hex let skipCurrentIdx = -1; const lgValuesConverted = lgValuesCleaned .map((value, idx) => { if (skipCurrentIdx == idx) return; // @ts-ignore let convertedColorString = colorStringToAlphaHex(value); //check if the next element is a percent and attach to current and remove it const nextIdx = idx + 1; if ( lgValuesCleaned[nextIdx] !== undefined && lgValuesCleaned[nextIdx].includes("%") && !lgValuesCleaned[nextIdx].includes("(") ) { convertedColorString += lgValuesCleaned[nextIdx]; skipCurrentIdx = nextIdx; } return convertedColorString; }) .filter((item) => { return item !== undefined; }); return "linear-gradient(" + lgValuesConverted.join(",") + ")"; } //From Named Colours else if (Object.keys(ColorNames).includes(colorString)) { const rgbValues = ColorString.get.rgb(colorString); const hex = ColorString.to.hex(rgbValues); return colorStringToAlphaHex(hex); } else { return colorString; } }; const convertLinearGradientStringToNativeObject = ( lgColorStringHex: string ): object => { const linearGradientNativeObject = {}; const lgValues = lgColorStringHex .split(/\((.+)/)[1] .split(/\)$/)[0] .replace(/ /g, "") .split(/,+(?![^\(]*\))/); //Check for directional words let angle: number = 0; let rotation: string = lgValues[0]; if (rotation == "toleft") angle = 270; if (rotation == "toright") angle = 90; if (rotation == "tobottom") angle = 180; if (rotation == "totop") angle = 0; //These are not the exact match to CSS as boundary dimensions are required for calculation if (rotation == "totopleft" || rotation == "tolefttop") angle = 295; if (rotation == "totopright" || rotation == "torighttop") angle = 45; if (rotation == "tobottomright" || rotation == "torightbottom") angle = 115; if (rotation == "tobottomleft" || rotation == "toleftbottom") angle = 205; //Check for Turns if (rotation.includes("turn")) { rotation = rotation.replace("turn", ""); const turns: number = parseFloat(rotation); angle = 360 * turns; } //Check for Degrees if (rotation.includes("deg")) { rotation = rotation.replace("deg", ""); angle = parseInt(rotation); } linearGradientNativeObject["angle"] = angle; linearGradientNativeObject["colours"] = []; const colorPositions: Array = lgValues.slice(1); colorPositions.forEach((colorPos, idx) => { const hexPosObj = { id: idx }; const hex = colorPos.slice(0, 8); //check string is an alpha hex const isValidAlphaHex = // @ts-ignore hex.match( /^[0-9a-fA-F]{8}$|#[0-9a-fA-F]{6}$|#[0-9a-fA-F]{4}$|#[0-9a-fA-F]{3}$/ ).length > 0; if (!isValidAlphaHex) return; colorPos = colorPos.replace(hex, ""); if (colorPos.includes("%")) { colorPos = colorPos.replace("%", ""); // @ts-ignore let colorPercent = parseInt(colorPos) / 100; //check if previous percent is greater or equal to the current if (linearGradientNativeObject["colours"].length > 0) { const previousPos = linearGradientNativeObject["colours"][idx - 1]["position"]; if (previousPos >= colorPercent) colorPercent = previousPos + 0.001; } hexPosObj["position"] = colorPercent; } else { if (idx == 0) { hexPosObj["position"] = 0.0; } else if (idx == colorPositions.length - 1) { hexPosObj["position"] = 1.0; } //assign null and iterate over again later else { hexPosObj["position"] = undefined; } } hexPosObj["hex"] = hex; linearGradientNativeObject["colours"].push(hexPosObj); }); //Find half way between previous and next given percent if no percentage assigned linearGradientNativeObject["colours"].forEach((colorPos, idx) => { if (colorPos["position"] === undefined) { //find next color with percentage const colorsClone = JSON.parse( JSON.stringify(linearGradientNativeObject["colours"]) ); let currentArrayChunk = colorsClone.splice(idx); const nextColorPercent = currentArrayChunk.find( (colorPos) => colorPos["position"] != undefined && colorPos["id"] !== 0 ); const nextColorPercentIdx = currentArrayChunk.findIndex( (colorPos) => colorPos["position"] != undefined && colorPos["id"] !== 0 ); currentArrayChunk = currentArrayChunk.splice(0, nextColorPercentIdx + 1); const previousColorPercent = colorsClone[idx - 1]; const y = nextColorPercent["position"]; const x = previousColorPercent["position"]; const n = currentArrayChunk.length + 1; //assign evenly distributed values to each undefined color in current array chunk currentArrayChunk.forEach((colorPosChunk, idx) => { idx += 1; if (colorPosChunk["position"] === undefined) { const colorPosition = x + ((y - x) / (n - 1)) * idx; const colorPosObj = linearGradientNativeObject["colours"][colorPosChunk["id"]]; colorPosObj["position"] = colorPosition; } }); } }); return linearGradientNativeObject; }; const hslToHex = (h: number, s: number, l: number): string => { l /= 100; const a = (s * Math.min(l, 1 - l)) / 100; const f = (n) => { const k = (n + h / 30) % 12; const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); return Math.round(255 * color) .toString(16) .padStart(2, "0"); }; return `${f(0)}${f(8)}${f(4)}`; }; const percentToHex = (p: number): string => { p = p > 100 ? 100 : p; const intValue = Math.round((p / 100) * 255); const hexValue = intValue.toString(16); return hexValue.padStart(2, "0").toLowerCase(); }; export default { isColorProperty, colorStringToAlphaHex, convertLinearGradientStringToNativeObject, }; ================================================ FILE: packages/react-juce/src/lib/MacroProperties/Transform.ts ================================================ import { TPropertyAssignment } from "./types"; import { getMacroCalls } from "./util"; import matrix from "matrix-js"; const rotateMultipliers = { deg: Math.PI / 180.0, grad: Math.PI / 200.0, rad: 1, turn: 2 * Math.PI, }; const angleUnitMatcher = new RegExp( `(-?[0-9]*\.?[0-9]+)(${Object.keys(rotateMultipliers).join("|")}|)$` ); const toRadians = (arg: string) => { // @ts-ignore let [, angle, unit] = angleUnitMatcher.exec(arg); let angleFloat = parseFloat(angle); if (angleFloat === NaN) return NaN; if (unit === "" && angleFloat !== 0) return NaN; angleFloat *= rotateMultipliers[unit] || 1; return angleFloat; }; const stringArgsToFloat = (...args: string[]) => args.map(parseFloat); const stringArgsToRadians = (...args: string[]) => args.map(toRadians); const identity = matrix([ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], ]); const rotate3d = (x: number, y: number, z: number, theta: number) => { const sinTheta = Math.sin(theta); const cosTheta = Math.cos(theta); const r = Math.sqrt(x ** 2 + y ** 2 + z ** 2); if (r === 0) { return identity; } const [u, v, w] = [x / r, y / r, z / r]; return matrix([ [ cosTheta + (1 - cosTheta) * u ** 2, u * v * (1 - cosTheta) - w * sinTheta, u * w * (1 - cosTheta) + v * sinTheta, 0, ], [ v * u * (1 - cosTheta) + w * sinTheta, cosTheta + (1 - cosTheta) * v ** 2, v * w * (1 - cosTheta) - u * sinTheta, 0, ], [ w * u * (1 - cosTheta) - v * sinTheta, w * v * (1 - cosTheta) + u * sinTheta, cosTheta + (1 - cosTheta) * w ** 2, 0, ], [0, 0, 0, 1], ]); }; const rotateX = (theta: number) => rotate3d(1, 0, 0, theta); const rotateY = (theta: number) => rotate3d(0, 1, 0, theta); const rotateZ = (theta: number) => rotate3d(0, 0, 1, theta); const scale3d = (sx = 1, sy = 1, sz = 1) => matrix([ [sx, 0, 0, 0], [0, sy, 0, 0], [0, 0, sz, 0], [0, 0, 0, 1], ]); const scale = (sx = 1, sy = 1) => scale3d(sx, sy); const scaleX = (sx = 1) => scale(sx); const scaleY = (sy = 1) => scale(1, sy); const scaleZ = (sz = 1) => scale3d(1, 1, sz); const skew = (alpha = 0, beta = 0) => { const tanAlpha = Math.tan(alpha) || 0; const tanBeta = Math.tan(beta) || 0; return matrix([ [1 + tanAlpha * tanBeta, tanAlpha, 0, 0], [tanBeta, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], ]); }; const skewX = (theta: number) => skew(theta); const skewY = (theta: number) => skew(0, theta); const translate3d = (dx = 0, dy = 0, dz = 0) => matrix([ [1, 0, 0, dx], [0, 1, 0, dy], [0, 0, 1, dz], [0, 0, 0, 1], ]); const translate = (dx = 0, dy = 0) => translate3d(dx, dy, 0); const translateX = (dx = 0) => translate3d(dx); const translateY = (dy = 0) => translate3d(0, dy); const translateZ = (dz = 0) => translate3d(0, 0, dz); const argsTransformMap = { matrix: stringArgsToFloat, matrix3d: stringArgsToFloat, rotate: stringArgsToRadians, rotate3d: (x, y, z, t) => [...stringArgsToFloat(x, y, z), toRadians(t)], rotateX: stringArgsToRadians, rotateY: stringArgsToRadians, rotateZ: stringArgsToRadians, scale: stringArgsToFloat, scale3d: stringArgsToFloat, scaleX: stringArgsToFloat, scaleY: stringArgsToFloat, scaleZ: stringArgsToFloat, skew: stringArgsToRadians, skewX: stringArgsToRadians, skewY: stringArgsToRadians, translate: stringArgsToFloat, translate3d: stringArgsToFloat, translateX: stringArgsToFloat, translateY: stringArgsToFloat, translateZ: stringArgsToFloat, }; const transformMatrixMap = { none: identity, matrix: (l11, l12, dx, l21, l22, dy) => matrix([ [l11, l12, 0, dx], [l21, l22, 0, dy], [0, 0, 1, 0], [0, 0, 0, 1], ]), matrix3d: ( m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44 ) => matrix([ [m11, m12, m13, m14], [m21, m22, m23, m24], [m31, m32, m33, m34], [m41, m42, m43, m44], ]), // perspective: ()=>identity, // TODO rotate: rotateZ, rotateX, rotateY, rotateZ, scale, scale3d, scaleX, scaleY, scaleZ, skew, skewX, skewY, translate, translate3d, translateX, translateY, translateZ, }; const matrixToArray = (m) => m().reduce((acc, v) => [...acc, ...v]); export default function (value: string): TPropertyAssignment[] { const calls = getMacroCalls(value); // operations performed right to left // i.e. transform: translateY(20) rotate(90deg) // means rotate 90 degrees, then translate up 20 // so we reverse in-place the calls array which initially // has the translation first followed by the rotate. calls.reverse(); //@ts-ignore const transformMatrix = calls.reduce((acc, { macro: f, args }) => { if (!transformMatrixMap.hasOwnProperty(f)) { return acc; } const argsTransform = argsTransformMap[f] || (() => args); const transform = transformMatrixMap[f](...argsTransform(...args)); return matrix(transform.prod(acc)); }, identity); return [["transform-matrix", matrixToArray(transformMatrix)]]; } ================================================ FILE: packages/react-juce/src/lib/MacroProperties/index.ts ================================================ import transformPropertiesGetter from "./Transform"; export const macroPropertyGetters = { transform: transformPropertiesGetter, }; ================================================ FILE: packages/react-juce/src/lib/MacroProperties/types.d.ts ================================================ export type TMacroCall = { macro: string; args: string[]; }; export type TPropertyAssignment = [string, string | number]; ================================================ FILE: packages/react-juce/src/lib/MacroProperties/util.ts ================================================ import { TMacroCall } from "./types"; // don't try too hard, we don't need to support qouted strings containing commas etc. export const splitArgs = (a: string) => { const argArray = a.trim().split(/\s*,\s*/); if (argArray.length === 1 && argArray[0] === "") return []; return argArray; }; export const getMacroCalls = (s: string): TMacroCall[] => { const macroMatcher = String.raw`([a-zA-Z0-9]+)\(([^)]*)\)`; let r = new RegExp(macroMatcher, "g"); // the rest would be trivial with matchAll // which duktape sadly lacks const matches = s.match(r); if (!matches) return []; // unset "g" flag, otherwise r.exec returns null when multiple matches occurred r = new RegExp(macroMatcher); const macroCalls = []; for (const match of matches) { // @ts-ignore const [, macro, args] = r.exec(match); macroCalls.push({ // @ts-ignore macro, // @ts-ignore args: splitArgs(args), }); } return macroCalls; }; ================================================ FILE: packages/react-juce/src/lib/MethodTracer.ts ================================================ //@ts-nocheck var inspect = require("object-inspect"); /** An object to be used as an ES6 Proxy handler to trace method calls and undefined property accesses on the target object. */ export default { get(target, propKey, receiver) { const f = target[propKey]; if (typeof f === "undefined") { console.log( "MethodTrace: Stubbing undefined property access for", propKey ); return function _noop(...args) { console.log( "MethodTrace Stub:", propKey, ...args.map((arg) => { return inspect(arg, { depth: 1 }); }) ); }; } if (typeof f === "function") { return function _traced(...args) { console.log( "MethodTrace:", propKey, ...args.map((arg) => { return inspect(arg, { depth: 1 }); }) ); return f.apply(this, args); }; } return f; }, }; ================================================ FILE: packages/react-juce/src/lib/NativeMethods.ts ================================================ let Native = global["__NativeBindings__"] || {}; let DefaultExport = Native; declare var process: { env: { NODE_ENV: string; }; }; if (process.env.NODE_ENV !== "production") { // @ts-ignore DefaultExport = new Proxy(Native, { get: function (target, propKey, receiver) { if ( target.hasOwnProperty(propKey) && typeof target[propKey] === "function" ) { return function __NativeMethodWrapper__(...args: any): any { return target[propKey].call(null, ...args); }; } return function __NativeMethodWrapper__() { console.warn( `WARNING: Attempt to access undefined native method ${target}` ); }; }, }); } export default DefaultExport; ================================================ FILE: packages/react-juce/src/lib/Renderer.ts ================================================ import MethodTracer from "./MethodTracer"; import ReactReconciler from "react-reconciler"; import Backend, { ViewInstance, RawTextViewInstance } from "./Backend"; import invariant from "invariant"; type HostContext = { isInTextParent: boolean; }; //TODO: This should really be types against ReactReconciler.HostConfig with the generics typed out. const HostConfig = { /** Time provider. */ now: Date.now, /** Indicates to the reconciler that our DOM tree supports mutating operations * like appendChild, removeChild, etc. */ supportsMutation: true, /** Provides the context for rendering the root level element. * * Really only using this and `getChildHostContext` for enforcing nesting * constraints, such as that raw text content must be a child of a * element. */ getRootHostContext(rootContainerInstance: ViewInstance): HostContext { return { isInTextParent: false, }; }, /** Provides the context for rendering a child element. */ getChildHostContext( parentHostContext: HostContext, elementType: string, rootContainerInstance: ViewInstance ): HostContext { const isInTextParent = parentHostContext.isInTextParent || elementType === "Text"; return { isInTextParent }; }, prepareForCommit: (...args: any) => {}, resetAfterCommit: (...args: any) => { Backend.resetAfterCommit(); }, /** Called to determine whether or not a new text value can be set on an * existing node, or if a new text node needs to be created. * * This is essentially born from the fact in that in a Web DOM, there are certain * nodes, such as